MSTestのCollectionAssertでdoubleの配列を比較する際に精度を指定したい

Assert.AreEqualは精度を指定できるメソッドがあるけれど、CollectionAssertにはないっぽい。まぁ、それはそうか。

// Assert.AreEqualのオーバーロード:
public static void AreEqual (double expected, double actual, double delta);

IComparerを引数にとるメソッドがあるので、これが使えそう。しかし、どうせならIComparer<T>にしてほしかった…。

public class DoubleComparer : IComparer
{
  public int Compare(object x, object y)
  {
    return (x, y) switch
    {
      (double dx, double dy) => Math.Abs(dx - dy) < double.Epsilon ? 0 : dx.CompareTo(dy),
      _ => -1
    };
  }
}

public void TestMethod()
{
  double[] excepted = ...;
  double[] actual = ...;
  CollectionAssert.AreEqual(excepted, actual, new DoubleComparer());
}

useEffect完全ガイドを読んだ

useEffect完全ガイド

React Hooksでタイマーで定期的にインクリメントされるカウンターみたいなのを作るのにrefを使っていましたが、もっと楽にできたみたいです。

function useTimer(): [number, React.Dispatch<React.SetStateAction<number>>] {
  const [count, setCount] = React.useState(0);
  
  const countRef = React.useRef(count);
  React.useEffect(() => {
    countRef.current = count;
  }, [count]);

  React.useEffect(() => {
      const timerId = setInterval(() => {
          setCount(countRef.current + 1);
      }, 1000);

      return () => clearInterval(timerId);
  }, []);

  return [ count, setCount ];
}

なんて書いていましたが、「関数型の更新」を使えば:

function useTimer(): [number, React.Dispatch<React.SetStateAction<number>>] {
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
      const timerId = setInterval(() => {
          setCount(c => c + 1);
      }, 1000);

      return () => clearInterval(timerId);
  }, []);

  return [ count, setCount ];
}

で済んでしまう。うーん、マニュアルはちゃんと読まないといけませんね。Reducerも使ってみないとだ。

BenchmarkDotNet

BenchmarkDotNet

その名の通り、任意のプログラムのベンチマークを取るためのライブラリ。C#で書いたメソッドのパフォーマンスを計るのに、いちいちStopwatch.StartNewして…とかしなくてよくなります。メソッドを複数回実行してその平均を結果として出力する、みたいなことも全自動でやってくれます。

ちょっと前のxUnitみたいな使い方で、ベンチマークを計るためのクラスを作って

BenchmarkDotNet.Running.BenchmarkRunner.Run<ArraySum>();

と書いて実行するとArraySumクラス内に書いたベンチマークが実行され、コンソールやファイルに結果が出力される。

"ベンチマークを計るためのクラス"には、やはりxUnitのように属性でメソッドにベンチマークを計るものである旨を指定しておく。

[BenchmarkDotNet.Attributes.MemoryDiagnoser] // メモリ使用量も計る
public class ArraySum
{
  // 異なる条件で実行
  [BenchmarkDotNet.Attributes.Params(10, 100, 1000, 10000, 100000, 1000000)]
  public int Count;

  public int Start => 1;
  public int Stop => Count - 1;
  private long[] _array;

  // ベンチマークを計る前に実行するメソッド
  [BenchmarkDotNet.Attributes.GlobalSetup]
  public void Setup()
  {
    _array = Enumerable.Range(0, Count).Select(x => (long)x).ToArray();
  }

  // このメソッドが処理を返すまでの時間を計る
  [BenchmarkDotNet.Attributes.Benchmark]
  public long Sum_Index()
  {
    long sum = 0;
    for (var i = Start; i < Stop; i++)
    {
      sum += _array[i];
    }

    return sum;
  }

  [BenchmarkDotNet.Attributes.Benchmark]
  public long Sum_Linq()
  {
    return _array.Skip(Start).Take(Stop - Start).Sum();
  }

  [BenchmarkDotNet.Attributes.Benchmark]
  public long Sum_Range()
  {
    return _array[Start..Stop].Sum();
  }

  [BenchmarkDotNet.Attributes.Benchmark]
  public long Sum_Span()
  {
    long sum = 0;
    foreach (var n in _array.AsSpan(Start..Stop))
    {
      sum += n;
    }

    return sum;
  }
}

実行すると実行ファイルのあるディレクトリの下に BenchmarkDotNet.Artifacts/results ディレクトリが作られ、デフォルト設定ではcsv/html/md/Rファイルが作成されます。

mdファイルにはMarkdown形式で結果が出力されるので、以下の通り、そのままコピペで表にできます。

Method Count Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Sum_Index 10 5.619 ns 0.0054 ns 0.0047 ns - - - -
Sum_Linq 10 200.515 ns 0.8479 ns 0.7931 ns 0.0203 - - 96 B
Sum_Range 10 83.320 ns 1.4429 ns 1.3497 ns 0.0254 - - 120 B
Sum_Span 10 14.047 ns 0.0652 ns 0.0610 ns - - - -
Sum_Index 100 81.580 ns 0.1521 ns 0.1348 ns - - - -
Sum_Linq 100 1,168.466 ns 3.7833 ns 3.3538 ns 0.0191 - - 96 B
Sum_Range 100 666.825 ns 10.7970 ns 10.0995 ns 0.1783 - - 840 B
Sum_Span 100 79.600 ns 0.1045 ns 0.0926 ns - - - -
Sum_Index 1000 763.410 ns 0.6491 ns 0.5420 ns - - - -
Sum_Linq 1000 10,923.124 ns 24.0253 ns 21.2978 ns 0.0153 - - 96 B
Sum_Range 1000 6,333.975 ns 21.1066 ns 19.7431 ns 1.7014 - - 8040 B
Sum_Span 1000 729.695 ns 0.6874 ns 0.6429 ns - - - -
Sum_Index 10000 7,618.156 ns 6.3278 ns 5.6094 ns - - - -
Sum_Linq 10000 107,975.718 ns 47.8050 ns 39.9194 ns - - - 97 B
Sum_Range 10000 62,339.152 ns 242.7192 ns 215.1643 ns 16.8457 - - 80040 B
Sum_Span 10000 7,209.591 ns 4.5585 ns 4.2640 ns - - - -
Sum_Index 100000 76,212.827 ns 41.3885 ns 34.5613 ns - - - -
Sum_Linq 100000 1,078,386.523 ns 550.1768 ns 459.4226 ns - - - 99 B
Sum_Range 100000 1,020,249.661 ns 18,760.3401 ns 17,548.4338 ns 248.0469 248.0469 248.0469 800048 B
Sum_Span 100000 72,185.765 ns 163.9998 ns 153.4055 ns - - - 1 B
Sum_Index 1000000 841,592.321 ns 26,717.0160 ns 29,695.8925 ns - - - -
Sum_Linq 1000000 10,821,178.646 ns 30,006.0666 ns 28,067.6933 ns - - - 172 B
Sum_Range 1000000 8,130,877.404 ns 52,434.7503 ns 43,785.3913 ns 328.1250 328.1250 328.1250 8000053 B
Sum_Span 1000000 655,597.043 ns 12,825.4812 ns 19,967.7291 ns - - - -

Rのファイル(BuildPlots.R)はR言語で評価するとベンチマーク結果のチャートを描画できます。なぜRなのかはわからないけどw

f:id:wata_d:20191203103244p:plain

CSSで要素に対するクリックを無効化する(pointer-events)

CSSでやるの違和感あるけど、便利。

button {
  pointer-events: none;
}

とするとボタンに対するクリックが効かなくなる。ボタンならdiasbled=falseにする方が綺麗だと思いますが、なかなか手の届かないところにあるコンポーネントを指定したいときとか、CSSだとシュッといけます(?)。

SVGが相手だとstorokeとかfillとか指定できてすごい。当たり判定とかもう自前で書く時代じゃないのだ。

HxD(バイナリエディタ)

バイナリファイルをちょっと覗くくらいの大した用途で使ってこなかったので10年来StrlingTSXBINで満足していたのですが、4GBを超えるファイルを開けないという問題にぶつかってしまい、代わりを探していたところ見つけたのがHxD

見た目も綺麗で日本語化されていて使いやすいです。比較(Diff)もできます!

f:id:wata_d:20191202121019p:plain

カーソル位置の値をshortやdoubleで評価したらいくつになるか、みたいなのがぱっと見える機能はTSXBINにもありますが、結構便利ですよね。

で、ここまでやってから改めてTSXBINの配布サイトを見たら64bit版が公開されていて、4GBのファイルも開けるようになっていたというオチ。

はてなブログの管理ページにある広告を隠す

独自ドメイン設定してもお名前.comの広告がバーンと出続けていてアレなのでユーザCSSで非表示にしました。なんでProにしたのに広告が増えるのか。

ChromeにはUser Stylesheetの機能がない(消された)っぽいので、Stylus拡張を入れました。

Stylus - Chrome ウェブストア

で、はてなブログの管理ページ(blog.hatena.ne.jp)にアクセスしてツールバー上のStylusアイコンをクリック→「次のスタイルを書く」のリンクをクリックして以下のスタイルを追加。

.upgrade-pro,
.gmo-campaign-banner {
    display: none;
}

左側の「保存」ボタンをクリックすれば以下の通り。

f:id:wata_d:20191129153200p:plain
これが…

f:id:wata_d:20191129153237p:plain
こうなる

C#のRangeとIndex

C# 8.0 の新機能 - C# ガイド | Microsoft Docs

C# 8.0で追加されたRangeやIndexによって、配列の一部の要素を参照したいときに以下のように書けるようになりました。Rubyっぽい。

int[] arr = {0, 1, 2, 3, 4, 5};
arr[^1].Dump();    // => 5 (最後の要素が取れる)
arr[1..^1].Dump(); // => [1,2,3,4]

^1とだけ書くとSystem.Index型、1..2だとSystem.Range型で評価される。...はありません。

配列の添え字にRangeを渡した場合、RuntimeHelpers.GetSubArrayメソッドが呼ばれるみたいだけど、コピーを作成するみたいなのであまり効率は良くなさそう?

AsSpanメソッドを挟むと速くなるとか。雑に試してみました。

Method Mean Error StdDev Allocated
Sum_Index 85.88 ms 0.330 ms 0.309 ms 812 B
Sum_Linq 1,082.57 ms 2.199 ms 2.057 ms 96 B
Sum_Range 969.83 ms 1.186 ms 1.051 ms 799999256 B
Sum_Span 79.02 ms 0.333 ms 0.312 ms 6 B

Spanは普通にforループ回すよりも速いみたいです。すごい。

素数多くなるほどLINQは遅くなるのが厳しいですね。イテレータ噛ませているからある程度は仕方無いんですが…。