问:
why flutter image did not release the memory when widget closed
I am facing a problem recently that my flutter(2.2.0) app always crashed after running a while, then I am debbuing and found that when the article detail page contains many image, close and reopen for many times, the memory are always increase, why the image did not dispose when close the article detail page? I am tried this way to clear image cache before exit article detail page(this should not use any image cache, not a good practice):

```<code>if (PaintingBinding.instance != null && PaintingBinding.instance!.imageCache != null) {
      PaintingBinding.instance!.imageCache!.clear();
      PaintingBinding.instance!.imageCache!.clearLiveImages();
    }
</code>```

but now the log shows this info:

```<code>I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 574us total 8.073ms
I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 734us total 7.350ms
I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 612us total 8.267ms
I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 746us total 7.442m
</code>```

it is the right way to handle this problem, sees it has a side effect using this way to clear image cache. what should I do to fix it? By the way, this is the image change trend I captured:

I tried use this code snip to monitor the image cache:

```<code>var imageCache= PaintingBinding.instance!.imageCache;

  print("dd:"+imageCache!.currentSizeBytes.toString());
  print("size:" + imageCache.currentSize.toString());
</code>```

found the image cache did not changed when close and reopen the article detail page but the memory still rising. This is the key code of showing the image:

```<code>if (item.content != "")
              Html(
                  data: item.content,
                  style: {
                    "body": Style(
                      fontSize: FontSize(19.0),
                    ),
                  },
                  customImageRenders: defaultImageRenders,
                  onLinkTap: (String? url, RenderContext context, Map<String, String> attributes, dom.Element? element) {
                    CommonUtils.launchUrl(url);
                  }),
</code>```

the image is in the html content and I am using flutter html to show it. This is my full code:

```<code>import 'package:cruise/src/common/article_action.dart';
import 'package:cruise/src/common/helpers.dart';
import 'package:cruise/src/common/net/rest/http_result.dart';
import 'package:cruise/src/common/repo.dart';
import 'package:cruise/src/common/utils/common_utils.dart';
import 'package:cruise/src/models/Channel.dart';
import 'package:cruise/src/models/Item.dart';
import 'package:cruise/src/models/api/fav_status.dart';
import 'package:cruise/src/models/api/upvote_status.dart';
import 'package:cruise/src/page/channel/channelpg_component/page.dart';
import 'package:cruise/src/page/home/components/articledetail_component/action.dart';
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_html/style.dart';
import 'package:flutter_icons/flutter_icons.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:html/dom.dart' as dom;

import 'state.dart';

Widget buildView(ArticleDetailState state, Dispatch dispatch, ViewService viewService) {
  Item item = state.article;
  BuildContext context = viewService.context;
  Offset? _initialSwipeOffset;
  Offset? _finalSwipeOffset;

  void _onHorizontalDragStart(DragStartDetails details) {
    _initialSwipeOffset = details.globalPosition;
  }

  void _onHorizontalDragUpdate(DragUpdateDetails details) {
    _finalSwipeOffset = details.globalPosition;
  }

  void _onHorizontalDragEnd(DragEndDetails details) {
    if (_initialSwipeOffset != null) {
      final offsetDifference = _initialSwipeOffset!.dx - _finalSwipeOffset!.dx;
      if (offsetDifference < 0) {
        if (PaintingBinding.instance != null && PaintingBinding.instance!.imageCache != null) {
          // https://mp.weixin.qq.com/s/yUm4UFggYLgDbj4_JCjEdg
          // https://musicfe.dev/flutter/
          PaintingBinding.instance!.imageCache!.clear();
          PaintingBinding.instance!.imageCache!.clearLiveImages();
        }
        Navigator.pop(context);
      }
    }
  }

  void touchUpvote(String action, UpvoteStatus upvoteStatus) async {
    HttpResult result = (await ArticleAction.upvote(articleId: item.id.toString(), action: action))!;

    if (result.result == Result.error) {
      Fluttertoast.showToast(
          msg: "点赞失败",
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.CENTER,
          timeInSecForIosWeb: 1,
          backgroundColor: Colors.red,
          textColor: Colors.white,
          fontSize: 16.0);
    } else {
      if (upvoteStatus.statusCode == "upvote") {
        dispatch(ArticleDetailActionCreator.onVote(UpvoteStatus.UPVOTE));
      }
      if (upvoteStatus.statusCode == "unupvote" && item.upvoteCount > 0) {
        dispatch(ArticleDetailActionCreator.onVote(UpvoteStatus.UNUPVOTE));
      }
      Fluttertoast.showToast(
          msg: upvoteStatus.statusCode == "upvote" ? "点赞成功" : "取消点赞成功",
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.CENTER,
          timeInSecForIosWeb: 1,
          backgroundColor: Colors.red,
          textColor: Colors.white,
          fontSize: 16.0);
    }
  }

  void touchFav(String action, FavStatus favStatus) async {
    HttpResult result = (await ArticleAction.fav(articleId: item.id.toString(), action: action))!;

    if (result.result == Result.error) {
      Fluttertoast.showToast(
          msg: favStatus.statusCode == "fav" ? "添加收藏失败" : "取消收藏失败",
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.CENTER,
          timeInSecForIosWeb: 1,
          backgroundColor: Colors.red,
          textColor: Colors.white,
          fontSize: 16.0);
    } else {
      if (favStatus.statusCode == "fav") {
        dispatch(ArticleDetailActionCreator.onFav(FavStatus.FAV));
      }
      if (favStatus.statusCode == "unfav" && item.favCount > 0) {
        dispatch(ArticleDetailActionCreator.onFav(FavStatus.UNFAV));
      }
      Fluttertoast.showToast(
          msg: favStatus.statusCode == "fav" ? "添加收藏成功" : "取消收藏成功",
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.CENTER,
          timeInSecForIosWeb: 1,
          backgroundColor: Colors.red,
          textColor: Colors.white,
          fontSize: 16.0);
    }
  }

  void navToChannelDetail() async {
    Channel channel = (await Repo.fetchChannelItem(int.parse(item.subSourceId)))!;
    var data = {'name': "originalstories", "channel": channel};
    Widget page = ChannelpgPage().buildPage(data);
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => page),
    );
  }

  /// 是否是编辑选择频道链接显示不同的颜色
  TextStyle getDomainStyle(Item article) {
    if (article.editorPick == 1) {
      return new TextStyle(color: Color(0xFFFFA826), fontSize: 15);
    } else {
      return new TextStyle(color: Color(0xFF0A0A0A), fontSize: 15);
    }
  }

  ImageSourceMatcher base64UriMatcher() =>
      (attributes, element) => attributes["src"] != null && attributes["src"]!.startsWith("data:image") && attributes["src"]!.contains("base64,");

  Widget loadingWidget() {
    return Center(
      child: Container(
          height: 400.0,
          width: 120.0,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              SizedBox(
                child: CircularProgressIndicator(),
                height: 50.0,
                width: 50.0,
              )
            ],
          )),
    );
  }

  final Map<ImageSourceMatcher, ImageRender> defaultImageRenders = {
    base64UriMatcher(): base64ImageRender(),
    assetUriMatcher(): assetImageRender(),
    networkSourceMatcher(extension: "svg"): svgNetworkImageRender(),
    networkSourceMatcher(): networkImageRender(height: 400, loadingWidget: loadingWidget),
  };

  SingleChildScrollView buildListView(Item item, BuildContext context) {
    return SingleChildScrollView(
        key: PageStorageKey("detail" + item.id),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            InkWell(
              onTap: () => CommonUtils.launchUrl(item.link),
              child: Padding(
                padding: const EdgeInsets.only(bottom: 8.0),
                child: Container(
                  child: Text(
                    item.title,
                    style: Theme.of(context).textTheme.headline5!.copyWith(
                          fontWeight: FontWeight.w600,
                        ),
                  ),
                ),
              ),
            ),
            if (item.domain != "")
              Padding(
                padding: const EdgeInsets.only(bottom: 8.0),
                child: InkWell(
                    onTap: () async {
                      navToChannelDetail();
                    },
                    child: Text(
                      item.domain,
                      style: getDomainStyle(item),
                    )),
              ),
            InkWell(
              onTap: () {},
              child: RichText(
                text: TextSpan(
                  children: <TextSpan>[
                    TextSpan(
                      text: item.author,
                      style: Theme.of(context).textTheme.caption,
                    ),
                    TextSpan(
                      text: " ${String.fromCharCode(8226)} ",
                      style: Theme.of(context).textTheme.caption,
                    ),
                    TextSpan(
                      text: item.ago,
                      style: Theme.of(context).textTheme.caption,
                    ),
                  ],
                ),
              ),
            ),
            if (item.content != "")
              Html(
                  data: item.content,
                  style: {
                    "body": Style(
                      fontSize: FontSize(19.0),
                    ),
                  },
                  customImageRenders: defaultImageRenders,
                  onLinkTap: (String? url, RenderContext context, Map<String, String> attributes, dom.Element? element) {
                    CommonUtils.launchUrl(url);
                  }),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Row(
                  children: [
                    Padding(
                      padding: const EdgeInsets.only(right: 16.0),
                      child: Row(
                        children: [
                          if (item.isFav == 1)
                            IconButton(
                              icon: Icon(Icons.bookmark, color: Theme.of(context).primaryColor),
                              onPressed: () => touchFav("unfav", FavStatus.UNFAV),
                            ),
                          if (item.isFav != 1)
                            IconButton(
                              icon: Icon(Icons.bookmark),
                              onPressed: () => touchFav("fav", FavStatus.FAV),
                            ),
                          Padding(
                            padding: const EdgeInsets.only(left: 0.0),
                            child: Text(
                              "${item.favCount}",
                              textAlign: TextAlign.center,
                              style: Theme.of(context).textTheme.caption!.copyWith(
                                    color: Theme.of(context).primaryColor,
                                  ),
                            ),
                          ),
                        ],
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(bottom: 0.0),
                      child: Row(
                        children: [
                          if (item.isUpvote == 1)
                            IconButton(
                              icon: Icon(Icons.thumb_up, color: Theme.of(context).primaryColor),
                              onPressed: () => touchUpvote("unupvote", UpvoteStatus.UNUPVOTE),
                            ),
                          if (item.isUpvote != 1)
                            IconButton(
                              icon: Icon(Icons.thumb_up),
                              onPressed: () => touchUpvote("upvote", UpvoteStatus.UPVOTE),
                            ),
                          Padding(
                            padding: const EdgeInsets.only(left: 8.0),
                            child: Text(
                              "${item.upvoteCount}",
                              textAlign: TextAlign.center,
                              style: Theme.of(context).textTheme.caption!.copyWith(
                                    color: Theme.of(context).primaryColor,
                                  ),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
                IconButton(
                  icon: Icon(
                    Feather.share_2,
                  ),
                  onPressed: () => handleShare(id: item.id, title: item.title, postUrl: item.link),
                ),
              ],
            ),
          ],
        ));
  }

  var imageCache= PaintingBinding.instance!.imageCache;

  print("dd:"+imageCache!.currentSizeBytes.toString());
  print("size:" + imageCache.currentSize.toString());

  return GestureDetector(
      onHorizontalDragStart: _onHorizontalDragStart,
      onHorizontalDragUpdate: _onHorizontalDragUpdate,
      onHorizontalDragEnd: _onHorizontalDragEnd,
      child: Container(
        constraints: BoxConstraints(
          minHeight: MediaQuery.of(context).size.height * 0.9,
        ),
        color: Theme.of(context).scaffoldBackgroundColor,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: buildListView(item, context),
        ),
      ));
}
</code>```

Latest update: still trigger OOM when clear image cache, the picture seems not unload from memory, load more picture, the memory increase more(and the memory never be GC).

答:
I was having the same issue and added your question as a bookmark waiting for an answer, recently I found a simple way to fix it.

the basic idea is to use ImageProvider for every image you want to display and put it in the memory using precacheImage function and then you can easily remove it from memory by calling evict function on the ImageProvider

// any class that extend ImageProvider
ImageProvider().evict();
// example
MemoryImage(bytes).evict();
```

A full working code as an answer would be too long. But I have already released a package to pub.dev that can do the same and have other features disposable_cached_images.

You can review the source code to better understand the implementation.

edit:

After more tests, it turns out that calling evict on the image provider sometimes doesn't work, especially if it was called during resolve so I made changes, and found it's better to use ui.image directly without the provider image.

ui.image.dispose() works very well.

Body:
I am facing a problem recently that my flutter(2.2.0) app always crashed after running a while, then I am debbuing and found that when the article detail page contains many image, close and reopen for many times, the memory are always increase, why the image did not dispose when close the article detail page? I am tried this way to clear image cache before exit article detail page(this should not use any image cache, not a good practice):

if (PaintingBinding.instance != null && PaintingBinding.instance!.imageCache != null) {
      PaintingBinding.instance!.imageCache!.clear();
      PaintingBinding.instance!.imageCache!.clearLiveImages();
    }

but now the log shows this info:

I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 574us total 8.073ms
I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 734us total 7.350ms
I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 612us total 8.267ms
I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 746us total 7.442m

it is the right way to handle this problem, sees it has a side effect using this way to clear image cache. what should I do to fix it? By the way, this is the image change trend I captured:

I tried use this code snip to monitor the image cache:

var imageCache= PaintingBinding.instance!.imageCache;

  print("dd:"+imageCache!.currentSizeBytes.toString());
  print("size:" + imageCache.currentSize.toString());

found the image cache did not changed when close and reopen the article detail page but the memory still rising. This is the key code of showing the image:

if (item.content != "")
              Html(
                  data: item.content,
                  style: {
                    "body": Style(
                      fontSize: FontSize(19.0),
                    ),
                  },
                  customImageRenders: defaultImageRenders,
                  onLinkTap: (String? url, RenderContext context, Map<String, String> attributes, dom.Element? element) {
                    CommonUtils.launchUrl(url);
                  }),

the image is in the html content and I am using flutter html to show it. This is my full code:

import 'package:cruise/src/common/article_action.dart';
import 'package:cruise/src/common/helpers.dart';
import 'package:cruise/src/common/net/rest/http_result.dart';
import 'package:cruise/src/common/repo.dart';
import 'package:cruise/src/common/utils/common_utils.dart';
import 'package:cruise/src/models/Channel.dart';
import 'package:cruise/src/models/Item.dart';
import 'package:cruise/src/models/api/fav_status.dart';
import 'package:cruise/src/models/api/upvote_status.dart';
import 'package:cruise/src/page/channel/channelpg_component/page.dart';
import 'package:cruise/src/page/home/components/articledetail_component/action.dart';
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_html/style.dart';
import 'package:flutter_icons/flutter_icons.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:html/dom.dart' as dom;

import 'state.dart';

Widget buildView(ArticleDetailState state, Dispatch dispatch, ViewService viewService) {
  Item item = state.article;
  BuildContext context = viewService.context;
  Offset? _initialSwipeOffset;
  Offset? _finalSwipeOffset;

  void _onHorizontalDragStart(DragStartDetails details) {
    _initialSwipeOffset = details.globalPosition;
  }

  void _onHorizontalDragUpdate(DragUpdateDetails details) {
    _finalSwipeOffset = details.globalPosition;
  }

  void _onHorizontalDragEnd(DragEndDetails details) {
    if (_initialSwipeOffset != null) {
      final offsetDifference = _initialSwipeOffset!.dx - _finalSwipeOffset!.dx;
      if (offsetDifference < 0) {
        if (PaintingBinding.instance != null && PaintingBinding.instance!.imageCache != null) {
          // https://mp.weixin.qq.com/s/yUm4UFggYLgDbj4_JCjEdg
          // https://musicfe.dev/flutter/
          PaintingBinding.instance!.imageCache!.clear();
          PaintingBinding.instance!.imageCache!.clearLiveImages();
        }
        Navigator.pop(context);
      }
    }
  }

  void touchUpvote(String action, UpvoteStatus upvoteStatus) async {
    HttpResult result = (await ArticleAction.upvote(articleId: item.id.toString(), action: action))!;

    if (result.result == Result.error) {
      Fluttertoast.showToast(
          msg: "点赞失败",
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.CENTER,
          timeInSecForIosWeb: 1,
          backgroundColor: Colors.red,
          textColor: Colors.white,
          fontSize: 16.0);
    } else {
      if (upvoteStatus.statusCode == "upvote") {
        dispatch(ArticleDetailActionCreator.onVote(UpvoteStatus.UPVOTE));
      }
      if (upvoteStatus.statusCode == "unupvote" && item.upvoteCount > 0) {
        dispatch(ArticleDetailActionCreator.onVote(UpvoteStatus.UNUPVOTE));
      }
      Fluttertoast.showToast(
          msg: upvoteStatus.statusCode == "upvote" ? "点赞成功" : "取消点赞成功",
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.CENTER,
          timeInSecForIosWeb: 1,
          backgroundColor: Colors.red,
          textColor: Colors.white,
          fontSize: 16.0);
    }
  }

  void touchFav(String action, FavStatus favStatus) async {
    HttpResult result = (await ArticleAction.fav(articleId: item.id.toString(), action: action))!;

    if (result.result == Result.error) {
      Fluttertoast.showToast(
          msg: favStatus.statusCode == "fav" ? "添加收藏失败" : "取消收藏失败",
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.CENTER,
          timeInSecForIosWeb: 1,
          backgroundColor: Colors.red,
          textColor: Colors.white,
          fontSize: 16.0);
    } else {
      if (favStatus.statusCode == "fav") {
        dispatch(ArticleDetailActionCreator.onFav(FavStatus.FAV));
      }
      if (favStatus.statusCode == "unfav" && item.favCount > 0) {
        dispatch(ArticleDetailActionCreator.onFav(FavStatus.UNFAV));
      }
      Fluttertoast.showToast(
          msg: favStatus.statusCode == "fav" ? "添加收藏成功" : "取消收藏成功",
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.CENTER,
          timeInSecForIosWeb: 1,
          backgroundColor: Colors.red,
          textColor: Colors.white,
          fontSize: 16.0);
    }
  }

  void navToChannelDetail() async {
    Channel channel = (await Repo.fetchChannelItem(int.parse(item.subSourceId)))!;
    var data = {'name': "originalstories", "channel": channel};
    Widget page = ChannelpgPage().buildPage(data);
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => page),
    );
  }

  /// 是否是编辑选择频道链接显示不同的颜色
  TextStyle getDomainStyle(Item article) {
    if (article.editorPick == 1) {
      return new TextStyle(color: Color(0xFFFFA826), fontSize: 15);
    } else {
      return new TextStyle(color: Color(0xFF0A0A0A), fontSize: 15);
    }
  }

  ImageSourceMatcher base64UriMatcher() =>
      (attributes, element) => attributes["src"] != null && attributes["src"]!.startsWith("data:image") && attributes["src"]!.contains("base64,");

  Widget loadingWidget() {
    return Center(
      child: Container(
          height: 400.0,
          width: 120.0,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              SizedBox(
                child: CircularProgressIndicator(),
                height: 50.0,
                width: 50.0,
              )
            ],
          )),
    );
  }

  final Map<ImageSourceMatcher, ImageRender> defaultImageRenders = {
    base64UriMatcher(): base64ImageRender(),
    assetUriMatcher(): assetImageRender(),
    networkSourceMatcher(extension: "svg"): svgNetworkImageRender(),
    networkSourceMatcher(): networkImageRender(height: 400, loadingWidget: loadingWidget),
  };

  SingleChildScrollView buildListView(Item item, BuildContext context) {
    return SingleChildScrollView(
        key: PageStorageKey("detail" + item.id),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            InkWell(
              onTap: () => CommonUtils.launchUrl(item.link),
              child: Padding(
                padding: const EdgeInsets.only(bottom: 8.0),
                child: Container(
                  child: Text(
                    item.title,
                    style: Theme.of(context).textTheme.headline5!.copyWith(
                          fontWeight: FontWeight.w600,
                        ),
                  ),
                ),
              ),
            ),
            if (item.domain != "")
              Padding(
                padding: const EdgeInsets.only(bottom: 8.0),
                child: InkWell(
                    onTap: () async {
                      navToChannelDetail();
                    },
                    child: Text(
                      item.domain,
                      style: getDomainStyle(item),
                    )),
              ),
            InkWell(
              onTap: () {},
              child: RichText(
                text: TextSpan(
                  children: <TextSpan>[
                    TextSpan(
                      text: item.author,
                      style: Theme.of(context).textTheme.caption,
                    ),
                    TextSpan(
                      text: " ${String.fromCharCode(8226)} ",
                      style: Theme.of(context).textTheme.caption,
                    ),
                    TextSpan(
                      text: item.ago,
                      style: Theme.of(context).textTheme.caption,
                    ),
                  ],
                ),
              ),
            ),
            if (item.content != "")
              Html(
                  data: item.content,
                  style: {
                    "body": Style(
                      fontSize: FontSize(19.0),
                    ),
                  },
                  customImageRenders: defaultImageRenders,
                  onLinkTap: (String? url, RenderContext context, Map<String, String> attributes, dom.Element? element) {
                    CommonUtils.launchUrl(url);
                  }),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Row(
                  children: [
                    Padding(
                      padding: const EdgeInsets.only(right: 16.0),
                      child: Row(
                        children: [
                          if (item.isFav == 1)
                            IconButton(
                              icon: Icon(Icons.bookmark, color: Theme.of(context).primaryColor),
                              onPressed: () => touchFav("unfav", FavStatus.UNFAV),
                            ),
                          if (item.isFav != 1)
                            IconButton(
                              icon: Icon(Icons.bookmark),
                              onPressed: () => touchFav("fav", FavStatus.FAV),
                            ),
                          Padding(
                            padding: const EdgeInsets.only(left: 0.0),
                            child: Text(
                              "${item.favCount}",
                              textAlign: TextAlign.center,
                              style: Theme.of(context).textTheme.caption!.copyWith(
                                    color: Theme.of(context).primaryColor,
                                  ),
                            ),
                          ),
                        ],
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(bottom: 0.0),
                      child: Row(
                        children: [
                          if (item.isUpvote == 1)
                            IconButton(
                              icon: Icon(Icons.thumb_up, color: Theme.of(context).primaryColor),
                              onPressed: () => touchUpvote("unupvote", UpvoteStatus.UNUPVOTE),
                            ),
                          if (item.isUpvote != 1)
                            IconButton(
                              icon: Icon(Icons.thumb_up),
                              onPressed: () => touchUpvote("upvote", UpvoteStatus.UPVOTE),
                            ),
                          Padding(
                            padding: const EdgeInsets.only(left: 8.0),
                            child: Text(
                              "${item.upvoteCount}",
                              textAlign: TextAlign.center,
                              style: Theme.of(context).textTheme.caption!.copyWith(
                                    color: Theme.of(context).primaryColor,
                                  ),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
                IconButton(
                  icon: Icon(
                    Feather.share_2,
                  ),
                  onPressed: () => handleShare(id: item.id, title: item.title, postUrl: item.link),
                ),
              ],
            ),
          ],
        ));
  }

  var imageCache= PaintingBinding.instance!.imageCache;

  print("dd:"+imageCache!.currentSizeBytes.toString());
  print("size:" + imageCache.currentSize.toString());

  return GestureDetector(
      onHorizontalDragStart: _onHorizontalDragStart,
      onHorizontalDragUpdate: _onHorizontalDragUpdate,
      onHorizontalDragEnd: _onHorizontalDragEnd,
      child: Container(
        constraints: BoxConstraints(
          minHeight: MediaQuery.of(context).size.height * 0.9,
        ),
        color: Theme.of(context).scaffoldBackgroundColor,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: buildListView(item, context),
        ),
      ));
}

Latest update: still trigger OOM when clear image cache, the picture seems not unload from memory, load more picture, the memory increase more(and the memory never be GC).

Body:
I was having the same issue and added your question as a bookmark waiting for an answer, recently I found a simple way to fix it.

the basic idea is to use ImageProvider for every image you want to display and put it in the memory using precacheImage function and then you can easily remove it from memory by calling evict function on the ImageProvider

// any class that extend ImageProvider
ImageProvider().evict();
// example
MemoryImage(bytes).evict();

A full working code as an answer would be too long. But I have already released a package to pub.dev that can do the same and have other features disposable_cached_images.

You can review the source code to better understand the implementation.

edit:

After more tests, it turns out that calling evict on the image provider sometimes doesn't work, especially if it was called during resolve so I made changes, and found it's better to use ui.image directly without the provider image.

ui.image.dispose() works very well.


来源: stackexchange

问:
python 'speedtest' has no attribute 'Speedtest'
I am trying to get the download and upload speed in python by using the "speedtest" module, but it gives me that error when I use the module:

AttributeError: module 'speedtest' has no attribute 'Speedtest'.

and I was only declaring the variable,
that is my code :

```<code>import speedtest

speedtester = speedtest.Speedtest()
</code>```

The module actually doesn't have the functions for some reason.
Please tell me what is wrong with my code I was sure to import the module from the cmd and also the pycharm terminal and still got the same error.
Thanks in advance

答:
You should uninstall before speedtest whit the command 'pip uninstall speedtest' . After that, this use this code to find download and upload, speeds and ping:</p>
```<code>import speedtest

test = speedtest.Speedtest()

print("Loading server list...")
test.get_servers()
print("Choosing best server...")
best = test.get_best_server()

print(f"Found: {best['host']} located in {best['country']}")

print("Performing download test...")
download_result = test.download()
print("Performing upload test...")
upload_result = test.upload()
ping_result = test.results.ping

print(f"Download speed: {download_result / 1024 / 1024:.2f}Mbit/s")
print(f"Upload speed: {upload_result / 1024 / 1024:.2f}Mbit/s")
print(f"Ping: {ping_result}ms")
</code>```

ContentLicense: CC BY-SA 4.0

Body:
I am trying to get the download and upload speed in python by using the "speedtest" module, but it gives me that error when I use the module:

AttributeError: module 'speedtest' has no attribute 'Speedtest'.

and I was only declaring the variable,
that is my code :

import speedtest

speedtester = speedtest.Speedtest()

The module actually doesn't have the functions for some reason.
Please tell me what is wrong with my code I was sure to import the module from the cmd and also the pycharm terminal and still got the same error.
Thanks in advance

Body:
You should uninstall before speedtest whit the command 'pip uninstall speedtest' . After that, this use this code to find download and upload, speeds and ping:

import speedtest

test = speedtest.Speedtest()

print("Loading server list...")
test.get_servers()
print("Choosing best server...")
best = test.get_best_server()

print(f"Found: {best['host']} located in {best['country']}")

print("Performing download test...")
download_result = test.download()
print("Performing upload test...")
upload_result = test.upload()
ping_result = test.results.ping

print(f"Download speed: {download_result / 1024 / 1024:.2f}Mbit/s")
print(f"Upload speed: {upload_result / 1024 / 1024:.2f}Mbit/s")
print(f"Ping: {ping_result}ms")


问:
Java exception: Exception: java.lang.IllegalArgumentException: Comparison method violates its general contract in arraylist
To make this simple, I have a method which looks up a group of keys in a hash table and insert found ones into an arraylist. The object type associated with the arraylist is the following:

```<code>public class PartitionEdge implements Comparable<PartitionEdge>
{

    public void setWeight(float weight)
    {
        this.weight = weight;
    }

    public float getWeight()
    {
        return this.weight;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public int getId()
    {
        return this.id;
    }

    public void setFrom(PartitionVertex from)
    {
        this.from = from;
    }

    public PartitionVertex getFrom()
    {
        return this.from;
    }

    public void setTo(PartitionVertex to)
    {
        this.to = to;
    }

    public PartitionVertex getTo()
    {
        return this.to;
    }

    public void setLabel(int label)
    {
        this.label = label;
    }

    public int getLabel()
    {
        return this.label;
    }

    public int PathLength = 0;

    protected int id;
    protected PartitionVertex from;
    protected PartitionVertex to;
    protected float weight;
    protected int label;

    @Override
    public int compareTo(PartitionEdge e) {
        //return Integer.compare(this.label, e.label);
        return Float.compare(this.weight, e.weight);
    }

}
</code>```

It uses the default comparator from the Float class.
But in a method where I try to call sort, one of the following exceptions will occur (originally only the one in the title).

```<code>Exception in thread "Thread-3" Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.base/java.util.ComparableTimSort.mergeHi(ComparableTimSort.java:870)
    at java.base/java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:487)
    at java.base/java.util.ComparableTimSort.mergeCollapse(ComparableTimSort.java:413)
    at java.base/java.util.ComparableTimSort.sort(ComparableTimSort.java:213)
    at java.base/java.util.Arrays.sort(Arrays.java:1107)
    at java.base/java.util.Arrays.sort(Arrays.java:1301)
    at java.base/java.util.ArrayList.sort(ArrayList.java:1721)
    at java.base/java.util.Collections.sort(Collections.java:145)
    at dedp.DistanceOracles.BridgeEdgeDOThread.run(BridgeEdgeDOThread.java:44)
java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList.sort(ArrayList.java:1723)
    at java.base/java.util.Collections.sort(Collections.java:145)
    at dedp.indexes.edgedisjoint.ConnectedComponent.checkBridgeDO(ConnectedComponent.java:151)
    at dedp.Test.BridgeThreadTest.test1(BridgeThreadTest.java:109)
    at dedp.Test.BridgeThreadTest.main(BridgeThreadTest.java:122)
</code>```
```<code>Exception in thread "Thread-4" Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList.sort(ArrayList.java:1723)
    at java.base/java.util.Collections.sort(Collections.java:145)
    at dedp.indexes.edgedisjoint.ConnectedComponent.checkBridgeDO(ConnectedComponent.java:152)
    at dedp.Test.BridgeThreadTest.test1(BridgeThreadTest.java:109)
    at dedp.Test.BridgeThreadTest.main(BridgeThreadTest.java:122)
java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.base/java.util.ComparableTimSort.mergeLo(ComparableTimSort.java:748)
    at java.base/java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:485)
    at java.base/java.util.ComparableTimSort.mergeCollapse(ComparableTimSort.java:413)
    at java.base/java.util.ComparableTimSort.sort(ComparableTimSort.java:213)
    at java.base/java.util.Arrays.sort(Arrays.java:1107)
    at java.base/java.util.Arrays.sort(Arrays.java:1301)
    at java.base/java.util.ArrayList.sort(ArrayList.java:1721)
    at java.base/java.util.Collections.sort(Collections.java:145)
    at dedp.DistanceOracles.BridgeEdgeDOThread.run(BridgeEdgeDOThread.java:44)
</code>```

The following is my original code where the exception is raised:

```<code>public boolean checkBridgeDO(PartitionVertex source, ArrayList<PartitionEdge>bridgeList) throws ObjectNotFoundException {
        HashMap<Integer, PartitionVertex> potentialBridgeDestinations = new HashMap<>();
        boolean got=true;
        for(Map.Entry<Integer, PartitionVertex>set:bridgeVertices.entrySet()){
          float result= this.lookUp(source, set.getValue());
          if(result<0) {
              potentialBridgeDestinations.put(set.getKey(), set.getValue());
              got = false;
          }else{
              PartitionEdge e = new PartitionEdge();
              e.setFrom(source);
              e.setTo(set.getValue());
              e.setWeight(result);
              e.setLabel(this.partition.Label);
              bridgeList.add(e);
          }
        }
        if(!got){
            source.lock.lock();
            source.thread=new BridgeEdgeDOThread();
            source.underBridgeComputation=true;
       source.thread.setParameters(this,source,potentialBridgeDestinations,bridgeList,0);
            source.thread.start();
            source.lock.unlock();
        }
        Collections.sort(bridgeList);
        return got;
    }
</code>```

Here the thread is trying to add more entries to the arraylist.

答: The cause of the exception(in the title) is actually not related to the comparator like most other posts are talking about. It is actually because I call sort after invoking the worker thread which is also concurrently inserting entries into the same arraylist. So it is not really breaking any contracts but because of concurrency. This exception really leads me to the wrong direction for a while so I hope this can help someone in the future.</p>

Body:
To make this simple, I have a method which looks up a group of keys in a hash table and insert found ones into an arraylist. The object type associated with the arraylist is the following:

public class PartitionEdge implements Comparable<PartitionEdge>
{

    public void setWeight(float weight)
    {
        this.weight = weight;
    }

    public float getWeight()
    {
        return this.weight;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public int getId()
    {
        return this.id;
    }

    public void setFrom(PartitionVertex from)
    {
        this.from = from;
    }

    public PartitionVertex getFrom()
    {
        return this.from;
    }

    public void setTo(PartitionVertex to)
    {
        this.to = to;
    }

    public PartitionVertex getTo()
    {
        return this.to;
    }

    public void setLabel(int label)
    {
        this.label = label;
    }

    public int getLabel()
    {
        return this.label;
    }

    public int PathLength = 0;

    protected int id;
    protected PartitionVertex from;
    protected PartitionVertex to;
    protected float weight;
    protected int label;

    @Override
    public int compareTo(PartitionEdge e) {
        //return Integer.compare(this.label, e.label);
        return Float.compare(this.weight, e.weight);
    }

}

It uses the default comparator from the Float class.
But in a method where I try to call sort, one of the following exceptions will occur (originally only the one in the title).

Exception in thread "Thread-3" Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.base/java.util.ComparableTimSort.mergeHi(ComparableTimSort.java:870)
    at java.base/java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:487)
    at java.base/java.util.ComparableTimSort.mergeCollapse(ComparableTimSort.java:413)
    at java.base/java.util.ComparableTimSort.sort(ComparableTimSort.java:213)
    at java.base/java.util.Arrays.sort(Arrays.java:1107)
    at java.base/java.util.Arrays.sort(Arrays.java:1301)
    at java.base/java.util.ArrayList.sort(ArrayList.java:1721)
    at java.base/java.util.Collections.sort(Collections.java:145)
    at dedp.DistanceOracles.BridgeEdgeDOThread.run(BridgeEdgeDOThread.java:44)
java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList.sort(ArrayList.java:1723)
    at java.base/java.util.Collections.sort(Collections.java:145)
    at dedp.indexes.edgedisjoint.ConnectedComponent.checkBridgeDO(ConnectedComponent.java:151)
    at dedp.Test.BridgeThreadTest.test1(BridgeThreadTest.java:109)
    at dedp.Test.BridgeThreadTest.main(BridgeThreadTest.java:122)

Exception in thread "Thread-4" Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList.sort(ArrayList.java:1723)
    at java.base/java.util.Collections.sort(Collections.java:145)
    at dedp.indexes.edgedisjoint.ConnectedComponent.checkBridgeDO(ConnectedComponent.java:152)
    at dedp.Test.BridgeThreadTest.test1(BridgeThreadTest.java:109)
    at dedp.Test.BridgeThreadTest.main(BridgeThreadTest.java:122)
java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.base/java.util.ComparableTimSort.mergeLo(ComparableTimSort.java:748)
    at java.base/java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:485)
    at java.base/java.util.ComparableTimSort.mergeCollapse(ComparableTimSort.java:413)
    at java.base/java.util.ComparableTimSort.sort(ComparableTimSort.java:213)
    at java.base/java.util.Arrays.sort(Arrays.java:1107)
    at java.base/java.util.Arrays.sort(Arrays.java:1301)
    at java.base/java.util.ArrayList.sort(ArrayList.java:1721)
    at java.base/java.util.Collections.sort(Collections.java:145)
    at dedp.DistanceOracles.BridgeEdgeDOThread.run(BridgeEdgeDOThread.java:44)

The following is my original code where the exception is raised:

public boolean checkBridgeDO(PartitionVertex source, ArrayList<PartitionEdge>bridgeList) throws ObjectNotFoundException {
        HashMap<Integer, PartitionVertex> potentialBridgeDestinations = new HashMap<>();
        boolean got=true;
        for(Map.Entry<Integer, PartitionVertex>set:bridgeVertices.entrySet()){
          float result= this.lookUp(source, set.getValue());
          if(result<0) {
              potentialBridgeDestinations.put(set.getKey(), set.getValue());
              got = false;
          }else{
              PartitionEdge e = new PartitionEdge();
              e.setFrom(source);
              e.setTo(set.getValue());
              e.setWeight(result);
              e.setLabel(this.partition.Label);
              bridgeList.add(e);
          }
        }
        if(!got){
            source.lock.lock();
            source.thread=new BridgeEdgeDOThread();
            source.underBridgeComputation=true;
       source.thread.setParameters(this,source,potentialBridgeDestinations,bridgeList,0);
            source.thread.start();
            source.lock.unlock();
        }
        Collections.sort(bridgeList);
        return got;
    }

Here the thread is trying to add more entries to the arraylist.


问:
Kubernetes Kustomize patching - Can't patch a file located in base
I have a huge patch file that I want to apply to specific overlays. I usually patch files under overlays as it is supposed to be. But the file is same and I do not want to copy it to each overlay. If I could keep my patch file app-new-manifest.yaml under base and patch it under overlay with a single line in kustomization.yaml, it would be awesome.

```<code>├── base
│   ├── app-new-manifest.yaml # I am trying to patch this
│   ├── kustomization.yaml
│   ├── app
│   │   ├── app.yaml
│   │   └── kustomization.yaml
└── overlay
    └── environment1
    │    ├── kustomization.yaml # I want to patch app-new-manifest.yaml in base
    │
    └── environment2
    │    ├── kustomization.yaml # No patch. app.yaml will be as is
    │
    └── environment3
        ├── kustomization.yaml # I want to patch app-new-manifest.yaml in base
</code>```

When I'm trying to do so, I get this error:

```<code>'/base/app/app-new-manifest.yaml' is not in or below '/overlays/environment1'
</code>```

Which means, when you patch, the patch file has to be located under overlay not base. Is there any workaround to do this? Because copying the same file to each environment does not make sense to me.

Any ideas around this will highly be appreciated, thanks!

Edit:

Add /base/app/kustomization.yaml

```<code>resources:
  - app.yaml
</code>```

Add /overlays/environment1/kustomization.yaml

```<code>apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base/app

patchesStrategicMerge:
  - ../../base/app/app-new-manifest.yaml # Patch new manifest
</code>```

kustomize version:

```<code>{Version:kustomize/v4.2.0 GitCommit:d53a2ad45d04b0264bcee9e19879437d851cb778 BuildDate:2021-07-01T00:44:28+01:00 GoOs:darwin GoArch:amd64}
</code>```

答:
You can't include a file that is outside of you current directory, but you can include another directory that has a kustomize.yaml file. So organize your layout like this:

```<code>.
├── base
└── overlay
    ├── patched_based
    ├── environment1
    ├── environment2
    └── environment3
</code>```

In overlay/patched_base, place your patch file and a kustomization file like:

```<code>resources:
  - ../base

patchesStrategicMerge:
  - app-new-manifest.yaml
</code>```

In overlay/environment1 and overlay/environment3, you have:

```<code>resources:
   - ../patched_base
</code>```

Whereas in overlay/environment2, you have:

```<code>resources:
  - ../../base
</code>```

I think this solves all your requirements:

You only need a single instance of the patch

You can choose to use the patch or not from each individual overlay

Body:
I have a huge patch file that I want to apply to specific overlays. I usually patch files under overlays as it is supposed to be. But the file is same and I do not want to copy it to each overlay. If I could keep my patch file app-new-manifest.yaml under base and patch it under overlay with a single line in kustomization.yaml, it would be awesome.

├── base
│   ├── app-new-manifest.yaml # I am trying to patch this
│   ├── kustomization.yaml
│   ├── app
│   │   ├── app.yaml
│   │   └── kustomization.yaml
└── overlay
    └── environment1
    │    ├── kustomization.yaml # I want to patch app-new-manifest.yaml in base
    │
    └── environment2
    │    ├── kustomization.yaml # No patch. app.yaml will be as is
    │
    └── environment3
        ├── kustomization.yaml # I want to patch app-new-manifest.yaml in base

When I'm trying to do so, I get this error:

'/base/app/app-new-manifest.yaml' is not in or below '/overlays/environment1'

Which means, when you patch, the patch file has to be located under overlay not base. Is there any workaround to do this? Because copying the same file to each environment does not make sense to me.

Any ideas around this will highly be appreciated, thanks!

Edit:

Add /base/app/kustomization.yaml

resources:
  - app.yaml

Add /overlays/environment1/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base/app

patchesStrategicMerge:
  - ../../base/app/app-new-manifest.yaml # Patch new manifest

kustomize version:

{Version:kustomize/v4.2.0 GitCommit:d53a2ad45d04b0264bcee9e19879437d851cb778 BuildDate:2021-07-01T00:44:28+01:00 GoOs:darwin GoArch:amd64}

Body:
You can't include a file that is outside of you current directory, but you can include another directory that has a kustomize.yaml file. So organize your layout like this:

.
├── base
└── overlay
    ├── patched_based
    ├── environment1
    ├── environment2
    └── environment3

In overlay/patched_base, place your patch file and a kustomization file like:

resources:
  - ../base

patchesStrategicMerge:
  - app-new-manifest.yaml

In overlay/environment1 and overlay/environment3, you have:

resources:
   - ../patched_base

Whereas in overlay/environment2, you have:

resources:
  - ../../base

I think this solves all your requirements:

You only need a single instance of the patch

You can choose to use the patch or not from each individual overlay


问:
Is there an integrity constraint violation if a search with a different domain is made?
These are the tables and the relational schema given:

Would the following action cause a domain constraint violation?

Delete any tuple in Collaborators with ‘requestorNumericSuffix’ = 'QUR'.

I think it wouldn't, because even though the domain for the request is wrong, it's just not gonna find any matches.

But I'm not so sure, so I'd appreciate any help.

答:
Even if there was a match COLABORATORS are referencing USERS.
The delete shouldn't raise an error. The other way around will cause an error.</p>


问:
How to plot ARACNe plot as a dendrogram in R
I tried running ARACNe and got an output like this -

```<code>from      to         MI   pvalue
geneA   geneC   0.25         0.1
geneC   geneB   0.28         0.2
geneB   geneA     0.2       0.11
</code>```

And I want to plot a dendrogram plot like this
Dendplot here - https://www.r-bloggers.com/2016/09/customer-segmentation-part-3-network-visualization/

How should I use the p value or MI here to see which genes in "from" control the genes in "to".

答:
As Maurits Evers points out in the comments - there's probably a lot of domain knowledge that should be applied to the question of what the right way to visualize and interpret this data is...

But to the question of how:

A dendrogram is a representation of pairwise distances between nodes in the tree. It requires a distance matrix that is then passed to a hierarchical clustering function. There are many options for the distance metric and clustering algorithms, and there's a lot of nuance in how to select the correct one for your application. However here's a relatively straightforward way to use the MI scores you shared in your example as a similarity metric and turn that into a dendrogram.

library(tidyverse)

d <- structure(list(from = c("geneA", "geneC", "geneB"), to = c("geneC", "geneB", "geneA"), MI = c(0.25, 0.28, 0.2), pvalue = c(0.1, 0.2, 0.11)), class = "data.frame", row.names = c(NA, -3L))

bind_rows(d,
          d %>% select(from = to, to = from, everything())) %>%
  select(from, to, MI) %>%
  pivot_wider(names_from = to, values_from = MI) %>%
  mutate(across(everything(), ~replace_na(.x, 1))) %>%
  select(sort(colnames(.))) %>%
  arrange(from) %>%
  column_to_rownames("from") %>%
  as.matrix() %>%
  (\(x) 1-x) %>%
  as.dist() %>%
  hclust() %>%
  as.dendrogram() %>%
  plot(ylab = "1 - Mutual Information", main = "Dendrogram based on MI")
```

Created on 2022-02-07 by the reprex package (v2.0.1)

Body:
I tried running ARACNe and got an output like this -

from      to         MI   pvalue
geneA   geneC   0.25         0.1
geneC   geneB   0.28         0.2
geneB   geneA     0.2       0.11

And I want to plot a dendrogram plot like this
Dendplot here - https://www.r-bloggers.com/2016/09/customer-segmentation-part-3-network-visualization/

How should I use the p value or MI here to see which genes in "from" control the genes in "to".

Body:
As Maurits Evers points out in the comments - there's probably a lot of domain knowledge that should be applied to the question of what the right way to visualize and interpret this data is...

But to the question of how:

A dendrogram is a representation of pairwise distances between nodes in the tree. It requires a distance matrix that is then passed to a hierarchical clustering function. There are many options for the distance metric and clustering algorithms, and there's a lot of nuance in how to select the correct one for your application. However here's a relatively straightforward way to use the MI scores you shared in your example as a similarity metric and turn that into a dendrogram.

library(tidyverse)

d <- structure(list(from = c("geneA", "geneC", "geneB"), to = c("geneC", "geneB", "geneA"), MI = c(0.25, 0.28, 0.2), pvalue = c(0.1, 0.2, 0.11)), class = "data.frame", row.names = c(NA, -3L))

bind_rows(d,
          d %>% select(from = to, to = from, everything())) %>%
  select(from, to, MI) %>%
  pivot_wider(names_from = to, values_from = MI) %>%
  mutate(across(everything(), ~replace_na(.x, 1))) %>%
  select(sort(colnames(.))) %>%
  arrange(from) %>%
  column_to_rownames("from") %>%
  as.matrix() %>%
  (\(x) 1-x) %>%
  as.dist() %>%
  hclust() %>%
  as.dendrogram() %>%
  plot(ylab = "1 - Mutual Information", main = "Dendrogram based on MI")

Created on 2022-02-07 by the reprex package (v2.0.1)


问:
Writing value of FormGroup FormControls in Angular
I have an array of FormControls in an inventory application in which I am trying to update a single item in the form based on a change - by another user on another device - in the value in the back end.  (Firebase in my case.)

I have access to the FormControl and I am able to make that change... except not.  Here's the situation:

You'll not from the console output, that when I expand the FormGroup and look at the value I see 3.  This is the correct value from the back end.  But the FormGroup.controls.current.value is still 4... the old value in the form.

I've been trying to play with different methods of accessing this control to get it to update properly.

Here's the code:

```<code>      this.items.controls.forEach((item, index) => {
        if (item.value.current != this.inventoryElements[index].current) {

          //First attempt to change the value of the formControl
          this.items.value[index].current =
            this.inventoryElements[index].current;

          //Second attempt
          this.items.controls[index].value.current =
            this.inventoryElements[index].current;

          //Third attempt
          item.value.current = this.inventoryElements[index].current;

          console.log(this.items.controls[index]);
          console.log(this.items.controls[index].value.current);
          console.log(this.items.value[index].current);
        }
      });
</code>```

this.inventoryElements[index].current holds the value I want to put into the form control.

this.items is the array of Form Controls.

You can see that I made three attempts to get the value there without luck -

The first console.log results in 4.

The second console.log results in 4.

The third console.log results in the output pasted at the top of this question.

One more piece of background.  The FormArray is this {current: string, notes: string} and my attempt was to modify the current value.

By the way, the reason I'm going through all of this mess is that I was unhappy that an update of a single value in the back end would cause the entire page to reload.  So this was an attempt to see whether I could update a single value and avoid the whole page reload.

Thanks the the help.

Paul

答:
The third attempt was close.  Instead of assigning the value with an equals sign:

```<code>item.value.current = this.inventoryElements[index].current;
</code>```

Use patchValue

```<code>item.patchValue({
            current: this.inventoryElements[index].current});
</code>```

Body:
I have an array of FormControls in an inventory application in which I am trying to update a single item in the form based on a change - by another user on another device - in the value in the back end.  (Firebase in my case.)

I have access to the FormControl and I am able to make that change... except not.  Here's the situation:

You'll not from the console output, that when I expand the FormGroup and look at the value I see 3.  This is the correct value from the back end.  But the FormGroup.controls.current.value is still 4... the old value in the form.

I've been trying to play with different methods of accessing this control to get it to update properly.

Here's the code:

      this.items.controls.forEach((item, index) => {
        if (item.value.current != this.inventoryElements[index].current) {

          //First attempt to change the value of the formControl
          this.items.value[index].current =
            this.inventoryElements[index].current;

          //Second attempt
          this.items.controls[index].value.current =
            this.inventoryElements[index].current;

          //Third attempt
          item.value.current = this.inventoryElements[index].current;

          console.log(this.items.controls[index]);
          console.log(this.items.controls[index].value.current);
          console.log(this.items.value[index].current);
        }
      });

this.inventoryElements[index].current holds the value I want to put into the form control.

this.items is the array of Form Controls.

You can see that I made three attempts to get the value there without luck -

The first console.log results in 4.

The second console.log results in 4.

The third console.log results in the output pasted at the top of this question.

One more piece of background.  The FormArray is this {current: string, notes: string} and my attempt was to modify the current value.

By the way, the reason I'm going through all of this mess is that I was unhappy that an update of a single value in the back end would cause the entire page to reload.  So this was an attempt to see whether I could update a single value and avoid the whole page reload.

Thanks the the help.

Paul

Body:
The third attempt was close.  Instead of assigning the value with an equals sign:

item.value.current = this.inventoryElements[index].current;

Use patchValue

item.patchValue({
            current: this.inventoryElements[index].current});


问:
Fast Python outer difference of list
I want to compute the difference between every element in a Python list of equally long lists and put it into a Numpy array.

When I say difference between two lists I mean the number of  differences between corresponding elements between the two lists.
Here's an example difference function using a list comprehension:

```<code>def list_difference(list_a, list_b):
    len_lists = len(list_a)
    assert len_lists == len(list_b), "Lists must be the same length."
    return sum([list_a[i] != list_b[i] for i in range(len_lists)])
</code>```

Then I call the difference function on every pair in my list of lists and put it into a numpy array.
You could call it the outer difference of the list of lists, just like the outer product.
I do it in naive for loops:

```<code>import numpy as np
import time

sequences = [
    ["A", "A", "A", "B", "C"],
    ["B", "A", "B", "A", "B"],
    ["B", "A", "C", "C", "B"],
    ["B", "A", "C", "C", "C"],
]

start = time.time()
n_seq = len(sequences)
dists = np.zeros((n_seq, n_seq))
for row in range(n_seq):
    for col in range(n_seq):
        if row >= col:
            continue
        dists[row, col] = list_difference(sequences[row], sequences[col])
dists += dists.T
print(dists)
print(f"Time: {time.time() - start} seconds")
</code>```

The result is

```<code>[[0. 4. 4. 3.]
 [4. 0. 2. 3.]
 [4. 2. 0. 1.]
 [3. 3. 1. 0.]]
Time: 0.0003669261932373047 seconds
</code>```

This example is quick enough on my computer (best time of three above).
However, running this on 64 lists (sequences) each with length 1008 it takes 293.572190 seconds, which is a while.
Is there a faster way to do this?

Attempts:

1 I tried putting the inner for loop in a list comprehension:

```<code>dists = np.zeros((n_seq, n_seq))
for row in range(n_seq):
    dist_row = [list_difference(sequences[row], sequences[col]) for col in range(n_seq) if row >= col]
    dists[row, n_seq-row-1:] = dist_row
dists += dists.T
</code>```

But it actually made it slower, taking 0.000523 seconds (0.70 times faster).
On my larger dataset it takes 319.2168769 seconds (0.92 times faster).

2 I wondered if doing the for loops in native Python and then copying to Numpy at the end would help.

```<code>_dists = [ [0]*n_seq for i in range(n_seq)]
for row in range(n_seq):
    for col in range(n_seq):
        if col <= row:
            continue
        _dists[row][col] = list_difference(sequences[row], sequences[col])
dists = np.array(_dists)
dists += dists.T
</code>```

This takes 0.0001862 seconds, which is about twice as fast as the original code.
On my larger dataset the speedup isn't as significant at 229.945951 seconds (1.28 times faster), but still something.

3 Just a thought that there's probably a way to do the two outer for loops directly in Numpy.

答:
A much cleaner, simpler, and faster way to do it would be to use numpy broadcasting:

```<code>sequences = np.array(sequences)
dists = (sequences[:, None] != sequences).sum(axis=2)
</code>```

Output:

```<code>>>> dists
array([[0, 4, 4, 3],
       [4, 0, 2, 3],
       [4, 2, 0, 1],
       [3, 3, 1, 0]])
</code>```

Title: Fast Python outer difference of list

Body:
I want to compute the difference between every element in a Python list of equally long lists and put it into a Numpy array.

When I say difference between two lists I mean the number of  differences between corresponding elements between the two lists.
Here's an example difference function using a list comprehension:

def list_difference(list_a, list_b):
    len_lists = len(list_a)
    assert len_lists == len(list_b), "Lists must be the same length."
    return sum([list_a[i] != list_b[i] for i in range(len_lists)])

Then I call the difference function on every pair in my list of lists and put it into a numpy array.
You could call it the outer difference of the list of lists, just like the outer product.
I do it in naive for loops:

import numpy as np
import time

sequences = [
    ["A", "A", "A", "B", "C"],
    ["B", "A", "B", "A", "B"],
    ["B", "A", "C", "C", "B"],
    ["B", "A", "C", "C", "C"],
]

start = time.time()
n_seq = len(sequences)
dists = np.zeros((n_seq, n_seq))
for row in range(n_seq):
    for col in range(n_seq):
        if row >= col:
            continue
        dists[row, col] = list_difference(sequences[row], sequences[col])
dists += dists.T
print(dists)
print(f"Time: {time.time() - start} seconds")

The result is

[[0. 4. 4. 3.]
 [4. 0. 2. 3.]
 [4. 2. 0. 1.]
 [3. 3. 1. 0.]]
Time: 0.0003669261932373047 seconds

This example is quick enough on my computer (best time of three above).
However, running this on 64 lists (sequences) each with length 1008 it takes 293.572190 seconds, which is a while.
Is there a faster way to do this?

Attempts:

1 I tried putting the inner for loop in a list comprehension:

dists = np.zeros((n_seq, n_seq))
for row in range(n_seq):
    dist_row = [list_difference(sequences[row], sequences[col]) for col in range(n_seq) if row >= col]
    dists[row, n_seq-row-1:] = dist_row
dists += dists.T

But it actually made it slower, taking 0.000523 seconds (0.70 times faster).
On my larger dataset it takes 319.2168769 seconds (0.92 times faster).

2 I wondered if doing the for loops in native Python and then copying to Numpy at the end would help.

_dists = [ [0]*n_seq for i in range(n_seq)]
for row in range(n_seq):
    for col in range(n_seq):
        if col <= row:
            continue
        _dists[row][col] = list_difference(sequences[row], sequences[col])
dists = np.array(_dists)
dists += dists.T

This takes 0.0001862 seconds, which is about twice as fast as the original code.
On my larger dataset the speedup isn't as significant at 229.945951 seconds (1.28 times faster), but still something.

3 Just a thought that there's probably a way to do the two outer for loops directly in Numpy.

Body:
A much cleaner, simpler, and faster way to do it would be to use numpy broadcasting:

sequences = np.array(sequences)
dists = (sequences[:, None] != sequences).sum(axis=2)

Output:

>>> dists
array([[0, 4, 4, 3],
       [4, 0, 2, 3],
       [4, 2, 0, 1],
       [3, 3, 1, 0]])


问:
Created kubeconfig file but only able to access default namespace
I used below file to create service account

```<code>apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-reader
  namespace: default

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  name: reader-cr
rules:
- apiGroups:
  - ""
  resources:
  - '*'
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - extensions
  resources:
  - '*'
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - apps
  resources:
  - '*'
  verbs:
  - get
  - list
  - watch

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-only-rb
subjects:
- kind: ServiceAccount
  name: sa-reader
roleRef:
  kind: ClusterRole
  name: reader-cr
  apiGroup: rbac.authorization.k8s.io
</code>```

The kubeconfig I created is something similar

```<code>apiVersion: v1
kind: Config
preferences: {}

clusters:
- name: qa
  cluster:
    certificate-authority-data: ca
    server: https:/<server>:443

users:
- name: sa-reader
  user:
    as-user-extra: {}
    token: <token>

contexts:
- name: qa
  context:
    cluster: qa
    user: sa-reader
    namespace: default

current-context: qa
</code>```

With this kubeconfig file, I am able to access resources in the default namespace but not any other namespace. How to access resources in other namespaces as well?

答:
An empty namespace in metadata, defaults to namespace: default and so, your RoleBinding is only applied to the default namespace.

See ObjectMeta.

I suspect (!) you need to apply to RoleBinding to each of the namespaces in which you want the Service Account to be permitted.

Body:
I used below file to create service account

apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-reader
  namespace: default

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  name: reader-cr
rules:
- apiGroups:
  - ""
  resources:
  - '*'
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - extensions
  resources:
  - '*'
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - apps
  resources:
  - '*'
  verbs:
  - get
  - list
  - watch

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-only-rb
subjects:
- kind: ServiceAccount
  name: sa-reader
roleRef:
  kind: ClusterRole
  name: reader-cr
  apiGroup: rbac.authorization.k8s.io

The kubeconfig I created is something similar

apiVersion: v1
kind: Config
preferences: {}

clusters:
- name: qa
  cluster:
    certificate-authority-data: ca
    server: https:/<server>:443

users:
- name: sa-reader
  user:
    as-user-extra: {}
    token: <token>

contexts:
- name: qa
  context:
    cluster: qa
    user: sa-reader
    namespace: default

current-context: qa

With this kubeconfig file, I am able to access resources in the default namespace but not any other namespace. How to access resources in other namespaces as well?


问:
NavigationLink jumps back when @ObservedObject is changed and @AppStorage is used
I've tracked down an annoying bug in my code and managed to recreate the issue using xCode's default iOS app as a starting point and adding a few bits of code.

Problem

Modifying part of an @ObservedObject (core data entity) causes the View to immediately jump back, when the following conditions are true:

The View is at least 2 links deep

@AppStorage is declared in another view

I've confirmed that (1) if I skip SubView2 and link straight to SubView3 (the editing view) then the behavior does not occur and (2) if I comment out the @AppStorage line in SettingsView() then the behavior does not occur.

In SubView3, I tap "Increase" to increase item.value + 1. This causes the view to jump back to SubView2.

Code

I started a default Xcode project with CloudKit and SwiftUI, then added a SettingsView (which contains the @AppStorage) and two SubViews to view a specific item.

```<code>import SwiftUI
import CoreData

struct SettingsView: View {

    @AppStorage("userSettingOne") var userSettingOne = false

    var body: some View {
        Text("Hello")
    }
}

struct SubView2: View {

    @ObservedObject var item2: Item

    @State private var showSubView3 = false

    var body: some View {

        NavigationLink(destination: SubView3(item3: item2) , isActive: $showSubView3) { EmptyView() }

        Text("Value: \(item2.value)")

        Button("SubView3", action: {
            showSubView3 = true
        })
            .navigationTitle("SubView2")
    }
}

struct SubView3: View {

    @ObservedObject var item3: Item

    var body: some View {

        VStack {
            Text("Value: \(item3.value)")
            Button("Increase", action: {
                item3.value += 1
            })
        }
            .navigationTitle("SubView3")
    }
}

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
        animation: .default)
    private var items: FetchedResults<Item>

    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in

                    NavigationLink("Item", destination: SubView2(item2: item))
                }
                .onDelete(perform: deleteItems)
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    EditButton()
                }
                ToolbarItem {
                    HStack {
                        Button(action: addItem) {
                            Label("Add Item", systemImage: "plus")
                        }
                        NavigationLink(destination: SettingsView(), label: {
                            Label("", systemImage: "gear").labelStyle(.iconOnly)
                        })
                    }

                }
            }
            Text("Select an item")
        }
    }

    // Boilerplate/default code below here

    private func addItem() {
        withAnimation {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[$0] }.forEach(viewContext.delete)

            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}
</code>```

答: try adding .navigationViewStyle(.stack) to your NavigationView

Body:
I've tracked down an annoying bug in my code and managed to recreate the issue using xCode's default iOS app as a starting point and adding a few bits of code.

Problem

Modifying part of an @ObservedObject (core data entity) causes the View to immediately jump back, when the following conditions are true:

The View is at least 2 links deep

@AppStorage is declared in another view

I've confirmed that (1) if I skip SubView2 and link straight to SubView3 (the editing view) then the behavior does not occur and (2) if I comment out the @AppStorage line in SettingsView() then the behavior does not occur.

In SubView3, I tap "Increase" to increase item.value + 1. This causes the view to jump back to SubView2.

Code

I started a default Xcode project with CloudKit and SwiftUI, then added a SettingsView (which contains the @AppStorage) and two SubViews to view a specific item.

import SwiftUI
import CoreData

struct SettingsView: View {

    @AppStorage("userSettingOne") var userSettingOne = false

    var body: some View {
        Text("Hello")
    }
}

struct SubView2: View {

    @ObservedObject var item2: Item

    @State private var showSubView3 = false

    var body: some View {

        NavigationLink(destination: SubView3(item3: item2) , isActive: $showSubView3) { EmptyView() }

        Text("Value: \(item2.value)")

        Button("SubView3", action: {
            showSubView3 = true
        })
            .navigationTitle("SubView2")
    }
}

struct SubView3: View {

    @ObservedObject var item3: Item

    var body: some View {

        VStack {
            Text("Value: \(item3.value)")
            Button("Increase", action: {
                item3.value += 1
            })
        }
            .navigationTitle("SubView3")
    }
}

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
        animation: .default)
    private var items: FetchedResults<Item>

    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in

                    NavigationLink("Item", destination: SubView2(item2: item))
                }
                .onDelete(perform: deleteItems)
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    EditButton()
                }
                ToolbarItem {
                    HStack {
                        Button(action: addItem) {
                            Label("Add Item", systemImage: "plus")
                        }
                        NavigationLink(destination: SettingsView(), label: {
                            Label("", systemImage: "gear").labelStyle(.iconOnly)
                        })
                    }

                }
            }
            Text("Select an item")
        }
    }

    // Boilerplate/default code below here

    private func addItem() {
        withAnimation {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[$0] }.forEach(viewContext.delete)

            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}


问:
Database Design For Developing 'Quiz' Web Application using PHP and MySQL
So, I'm trying to learn PHP and MySQL (I have a basic understanding of both; I've read the first half of both Head First SQL and Head First PHP & MySQL) and I figure the best way to solidify my knowledge is by building something rather than reading.

With that in mind, I would like to create a basic webpage that connects to a MySQL database on a server.  I will build a basic HTML form and allow users to input basic information, such as: last_name, first_name, email, birthday, gender.

My problem is I don't know how to design a database that will record the results of a basic quiz - I want just 5 multiple-choice problems.  Eventually, I would like to display the results of the user versus the previous users' results.

If you could help me understand how to design table(s) for a 5-question Quiz I'd appreciate it.  Thanks!

答:
This was also the first project I did in PHP/MySQL about 8 years ago.

Your first solution is to code the database to exactly match your form. So, you want to record users and quiz submissions, so it's going to look something like this:

```<code>CREATE TABLE users (
  username VARCHAR(16) PRIMARY KEY,
  password VARCHAR(8),
  email VARCHAR(255),
  birthday DATE,
  gender ENUM('M', 'F')
);

CREATE TABLE quiz_answers (
  username VARCHAR(16) REFERENCES users,
  question1 VARCHAR(10),
  question2 INT,
  question3 ENUM('YES', 'NO', 'MAYBE'),
  question4 BOOLEAN,
  question5 VARCHAR(25),
  submitted_at DATETIME,
  PRIMARY KEY (username, submitted_at)
);
</code>```

So this is just recording the bare minimum: the user and the quiz submissions. I've given types for the answers which you would have to make specific to your actual quiz. I've also made a response keyed off the user and the moment they submitted it; you're be more likely to use a surrogate key (AUTO_INCREMENT), but I like to resist surrogates as much as possible.

Right off the bat there is a 1NF violation: questionN. If you were doing this right, you would name these columns after what they mean, not just which question they are. But normalizing this is really the next step towards forms that are extensible, but track history.

So the next thing you would notice is that really a quiz is a collection of questions, each of which has a collection of possible answers. And then a form submission really relates a set of selected answers to their questions, on a particular quiz form, by a particular quiz user. This sounds like a four-way relationship: user, quiz, question, answer. You can trim out one of those if you don't mind repeating questions on different quizes, but for the sake of completeness, let's go down this road. Replace quiz_answers above with this:

```<code>CREATE TABLE questions (
  id INTEGER AUTO_INCREMENT PRIMARY KEY,
  question TEXT
);

CREATE TABLE answers (
  id INTEGER AUTO_INCREMENT PRIMARY KEY,
  question_id INTEGER REFERENCES questions,
  answer VARCHAR(255)
);

CREATE TABLE quizzes (
  name VARCHAR(255) PRIMARY KEY,
);
</code>```

We don't really have any special metadata for a quiz, so it's just a name for now.

So now you need a one-to-many relationship from questions to answers and from quizzes to questions.

```<code>CREATE TABLE question_answers (
  question_id INTEGER REFERENCES questions,
  answer_id INTEGER REFERENCES answers,
  idx INTEGER,
  PRIMARY KEY (question_id, answer_id)
);

CREATE TABLE quiz_questions (
  quiz_name VARCHAR(255) REFERENCES quizzes,
  question_id INTEGER REFERENCES questions,
  idx INTEGER,
  PRIMARY KEY (quiz_name, question_id)
);
</code>```

The tricky part, like mentioned above, is the higher order relationship between the user and the form submission, and the link from the form questions to user answers. I've decided to separate this into two tables to avoid some repetition.

```<code>CREATE TABLE quiz_submissions (
  id INTEGER AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(16) REFERENCES users,
  quiz_name VARCHAR(255) REFERENCES quizzes,
  submitted_at DATETIME
);

CREATE TABLE submission_answer (
  submission_id INTEGER REFERENCES quiz_submissions,
  question_id INTEGER REFERENCES questions,
  answer_id INTEGER REFERENCES answers,
  PRIMARY KEY (submission_id, question_id)
);
</code>```

This is pretty well normalized at this point. You can see that it is also going to be a bit harder to query. To get all the questions out for a quiz, you'll have to join from the quiz to the questions. You can either join from there onto the answers to do one big query to get all the data you need to build the form (along with having to do more post-processing) or you can hit the database one more time for each question and do less post-processing. I can argue either way. To get all of a particular users answers out, you're going to have to select from user_submissions with the quiz ID and the user name to the submission_answer table to the question and the answer the user chose. So the querying is going to get interesting quickly. You'll lose your fear of joins, if you have one.

I hope this won't put you off relational databases too much; by doing this you are, in effect, doing a relational model inside the relational model, albeit a restricted form.

I realize using a lot of natural keys like I have done above is a bit unorthodox these days. However, I recommend you try it, at least while you're getting started, because it will make it much easier to see how the joins have to work if they're not all integers in the range 1-10.

ContentLicense: CC BY-SA 3.0

Body:
This was also the first project I did in PHP/MySQL about 8 years ago.

Your first solution is to code the database to exactly match your form. So, you want to record users and quiz submissions, so it's going to look something like this:

CREATE TABLE users (
  username VARCHAR(16) PRIMARY KEY,
  password VARCHAR(8),
  email VARCHAR(255),
  birthday DATE,
  gender ENUM('M', 'F')
);

CREATE TABLE quiz_answers (
  username VARCHAR(16) REFERENCES users,
  question1 VARCHAR(10),
  question2 INT,
  question3 ENUM('YES', 'NO', 'MAYBE'),
  question4 BOOLEAN,
  question5 VARCHAR(25),
  submitted_at DATETIME,
  PRIMARY KEY (username, submitted_at)
);

So this is just recording the bare minimum: the user and the quiz submissions. I've given types for the answers which you would have to make specific to your actual quiz. I've also made a response keyed off the user and the moment they submitted it; you're be more likely to use a surrogate key (AUTO_INCREMENT), but I like to resist surrogates as much as possible.

Right off the bat there is a 1NF violation: questionN. If you were doing this right, you would name these columns after what they mean, not just which question they are. But normalizing this is really the next step towards forms that are extensible, but track history.

So the next thing you would notice is that really a quiz is a collection of questions, each of which has a collection of possible answers. And then a form submission really relates a set of selected answers to their questions, on a particular quiz form, by a particular quiz user. This sounds like a four-way relationship: user, quiz, question, answer. You can trim out one of those if you don't mind repeating questions on different quizes, but for the sake of completeness, let's go down this road. Replace quiz_answers above with this:

CREATE TABLE questions (
  id INTEGER AUTO_INCREMENT PRIMARY KEY,
  question TEXT
);

CREATE TABLE answers (
  id INTEGER AUTO_INCREMENT PRIMARY KEY,
  question_id INTEGER REFERENCES questions,
  answer VARCHAR(255)
);

CREATE TABLE quizzes (
  name VARCHAR(255) PRIMARY KEY,
);

We don't really have any special metadata for a quiz, so it's just a name for now.

So now you need a one-to-many relationship from questions to answers and from quizzes to questions.

CREATE TABLE question_answers (
  question_id INTEGER REFERENCES questions,
  answer_id INTEGER REFERENCES answers,
  idx INTEGER,
  PRIMARY KEY (question_id, answer_id)
);

CREATE TABLE quiz_questions (
  quiz_name VARCHAR(255) REFERENCES quizzes,
  question_id INTEGER REFERENCES questions,
  idx INTEGER,
  PRIMARY KEY (quiz_name, question_id)
);

The tricky part, like mentioned above, is the higher order relationship between the user and the form submission, and the link from the form questions to user answers. I've decided to separate this into two tables to avoid some repetition.

CREATE TABLE quiz_submissions (
  id INTEGER AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(16) REFERENCES users,
  quiz_name VARCHAR(255) REFERENCES quizzes,
  submitted_at DATETIME
);

CREATE TABLE submission_answer (
  submission_id INTEGER REFERENCES quiz_submissions,
  question_id INTEGER REFERENCES questions,
  answer_id INTEGER REFERENCES answers,
  PRIMARY KEY (submission_id, question_id)
);

This is pretty well normalized at this point. You can see that it is also going to be a bit harder to query. To get all the questions out for a quiz, you'll have to join from the quiz to the questions. You can either join from there onto the answers to do one big query to get all the data you need to build the form (along with having to do more post-processing) or you can hit the database one more time for each question and do less post-processing. I can argue either way. To get all of a particular users answers out, you're going to have to select from user_submissions with the quiz ID and the user name to the submission_answer table to the question and the answer the user chose. So the querying is going to get interesting quickly. You'll lose your fear of joins, if you have one.

I hope this won't put you off relational databases too much; by doing this you are, in effect, doing a relational model inside the relational model, albeit a restricted form.

I realize using a lot of natural keys like I have done above is a bit unorthodox these days. However, I recommend you try it, at least while you're getting started, because it will make it much easier to see how the joins have to work if they're not all integers in the range 1-10.


问:
Is there a way to reduce the amont of SPI PSRAM memory used up during esp_camera_init on ESP32-Cam
Below code:-

```<code>Serial.printf("Before camera config ... SPIRam Total heap %d, SPIRam Free Heap %d", ESP.getPsramSize(), ESP.getFreePsram())
cam_err = esp_camera_init(&config);
Serial.printf("After camera config ... SPIRam Total heap %d, SPIRam Free Heap %d", ESP.getPsramSize(), ESP.getFreePsram())
</code>```

Will give the following printed:-

```<code>Before camera config ... SPIRam Total heap   4194252, SPIRam Free Heap   4194252
After  camera config ... SPIRam Total heap   4194204, SPIRam Free Heap   1314204
</code>```

So of 4MB, 1.3 MB remains free after esp_camera_init. Is there a possibility such that this can be reduced, and have relatively larger amount of free memory in PSRAM.

I am using AI-Thinker board ESP32-Cam.

(I am following this code: https://github.com/jameszah/ESP32-CAM-Video-Recorder-junior)

答:
Yes, you can reduce the amount of SPI PSRAM used by the camera.

The answer is in the config argument to esp_camera_init(), the contents of which you didn't include in your question.

The camera driver allocates frame buffers in PSRAM. You control the number of frame buffers (.fb_count) and the size of the frame buffers (.frame_size) in the config structure. If you want to use less PSRAM, reduce those numbers.

Reducing the number of frame buffers will reduce the speed at which the camera can capture frames. Reducing the frame size will obviously reduce the resolution of the capture.

You can also configure the camera to use non-PSRAM DRAM for the frame buffer, but there's precious little of it to start with and you won't be able to make this work for anything but low resolutions. The .fb_location attribute controls this. Set it to CAMERA_FB_IN_DRAM in order to use regular RAM but again, don't expect it to work for anything but the lowest resolutions.

Yes, the camera uses a lot of storage. Video storage needs go up rapidly with resolution.

The ESP32 camera code is open source, so you can read the code for esp_camera_init() to learn what it does, and the code for cam_dma_config(), which allocates the frame buffers.

Body:
Below code:-

Serial.printf("Before camera config ... SPIRam Total heap %d, SPIRam Free Heap %d", ESP.getPsramSize(), ESP.getFreePsram())
cam_err = esp_camera_init(&config);
Serial.printf("After camera config ... SPIRam Total heap %d, SPIRam Free Heap %d", ESP.getPsramSize(), ESP.getFreePsram())

Will give the following printed:-

Before camera config ... SPIRam Total heap   4194252, SPIRam Free Heap   4194252
After  camera config ... SPIRam Total heap   4194204, SPIRam Free Heap   1314204

So of 4MB, 1.3 MB remains free after esp_camera_init. Is there a possibility such that this can be reduced, and have relatively larger amount of free memory in PSRAM.

I am using AI-Thinker board ESP32-Cam.

(I am following this code: https://github.com/jameszah/ESP32-CAM-Video-Recorder-junior)


问:
Saving multiple files as .Rdata
I was wondering if there is a way of using the save() option to save multiple files of data. I wanted to save all these files in the form of .Rdata, but wasn't sure how to approach this without using save() multiple times. I am new to R.</p>

答:
Checking directory

As someone already mentioned above, you can save something as multiple objects and run it that way. Here I used the datasets included in the 'datasets' package within R. First check your directory to see where its getting saved:

```<code>getwd()
</code>```

Then see where that is from the output:

```<code>[1] "C:/Users/DELL/Dropbox/My PC (DESKTOP-SUOCLVS)/Desktop/Research Tools/R Directory"
</code>```

Creating basic rdata file

Then go ahead and run the code:

```<code>df1 <- iris
df2 <- mtcars
save(df1, df2,
     file = "mydata.rdata")
</code>```

You'll see now its saved in the directory:

Other file types

If you mean saving multiple objects of different types, that is a bit more of an issue, as something like a csv or spss file isn't easy to coerce. One option is to include the mapply function. I've also used R datasets here as an example:

```<code>library(tidyverse)

myList <- list(diamonds = diamonds,
               cars = cars)
mapply(write.csv, myList, file=paste0(names(myList), '.csv'))
</code>```

Which you can now see in the directory:

Title: Saving multiple files as .Rdata

Body:
Checking directory

As someone already mentioned above, you can save something as multiple objects and run it that way. Here I used the datasets included in the 'datasets' package within R. First check your directory to see where its getting saved:

getwd()

Then see where that is from the output:

[1] "C:/Users/DELL/Dropbox/My PC (DESKTOP-SUOCLVS)/Desktop/Research Tools/R Directory"

Creating basic rdata file

Then go ahead and run the code:

df1 <- iris
df2 <- mtcars
save(df1, df2,
     file = "mydata.rdata")

You'll see now its saved in the directory:

Other file types

If you mean saving multiple objects of different types, that is a bit more of an issue, as something like a csv or spss file isn't easy to coerce. One option is to include the mapply function. I've also used R datasets here as an example:

library(tidyverse)

myList <- list(diamonds = diamonds,
               cars = cars)
mapply(write.csv, myList, file=paste0(names(myList), '.csv'))

Which you can now see in the directory:


问:
Is there a way to have a loose interface with SOME strictness?
I was hoping I could have an interface like:

```<code>interface Item {
  [ index: string ] : string | number | Item,
  id: number,
}
</code>```

So that an Item, can have any properties with string/number/object values, BUT-- if an id property is used, it MUST be a number.

Is this possible?

EDIT

I originally asked this, but then tested it out and what I did seems to work fine, UNLESS I am using optional attributes...

If I do this, then I get a typescript error:

```<code>interface Item {
  [ index: string ] : string | number | Item,
  id?: number,
}
</code>```

In order to get this to work, I have to do:

```<code>interface Item {
  [ index: string ] : string | number | Item | undefined,
  id?: number,
}
</code>```

Which I don't think I want all my other properties to be potentially undefined...  Is there another way to have id be optional?

EDIT AGAIN

Turns out even with the | undefined this doesn't work..  If I do the following:

```<code>let i : Item = { id: 123, foo: { bar: { id: 555} } };
console.log(i.foo.bar.id);
</code>```

I get errors about "object is possibly undefined"

答:
According to the doc, you can as long as the type of id is a subtype of [index: string]

interface NumberDictionary {
  [index: string]: number;

  length: number; // ok
  name: string; // not ok
  // Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
}

interface NumberOrStringDictionary {
  [index: string]: number | string;
  length: number; // ok, length is a number
  name: string; // ok, name is a string
}
```
<p><strong>Edit:</strong></p>
<p>I'm not sure if this is the best way but this seems to work</p>
<pre class="lang-js prettyprint-override"><code>interface Item1 {
  [ index: string ] : string | number | Item;
}

interface Item2 {
  id?: number;
}

type Item = Item1 & Item2
</code>```

Also "object is possibly undefined" error can be solved with optional chaining

let i : Item = { id: 123, foo: { bar: { id: 555} } };
console.log(i.foo?.bar?.id);
```

Body:
I was hoping I could have an interface like:

interface Item {
  [ index: string ] : string | number | Item,
  id: number,
}

So that an Item, can have any properties with string/number/object values, BUT-- if an id property is used, it MUST be a number.

Is this possible?

EDIT

I originally asked this, but then tested it out and what I did seems to work fine, UNLESS I am using optional attributes...

If I do this, then I get a typescript error:

interface Item {
  [ index: string ] : string | number | Item,
  id?: number,
}

In order to get this to work, I have to do:

interface Item {
  [ index: string ] : string | number | Item | undefined,
  id?: number,
}

Which I don't think I want all my other properties to be potentially undefined...  Is there another way to have id be optional?

EDIT AGAIN

Turns out even with the | undefined this doesn't work..  If I do the following:

let i : Item = { id: 123, foo: { bar: { id: 555} } };
console.log(i.foo.bar.id);

I get errors about "object is possibly undefined"

Body:
According to the doc, you can as long as the type of id is a subtype of [index: string]

interface NumberDictionary {
  [index: string]: number;

  length: number; // ok
  name: string; // not ok
  // Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
}

interface NumberOrStringDictionary {
  [index: string]: number | string;
  length: number; // ok, length is a number
  name: string; // ok, name is a string
}

Edit:

I'm not sure if this is the best way but this seems to work

interface Item1 {
  [ index: string ] : string | number | Item;
}

interface Item2 {
  id?: number;
}

type Item = Item1 & Item2

Also "object is possibly undefined" error can be solved with optional chaining

let i : Item = { id: 123, foo: { bar: { id: 555} } };
console.log(i.foo?.bar?.id);