はじめに
Python のリストや numpy ndarray の基本操作に対応する処理を、C# でどう書けばよいか?
毎回ウェブをあちこち探すのはとても時間がかかります。
普段 Python を使っていてたまに C# で書くとき、すぐに対応が見つかるように、一通りまとめておきます。
バージョン
Python 3、C# 6.0 (.NET Framework 4.6) を前提にします。.NET Framework 3.5 でも一部を除き対応していると思われます。
配列とリスト
- Python は標準リストと numpy.ndarray を扱います。
- C# はリストを主に扱います。
C# の配列とリストはどちらも IEnumerable
を実装しているので、LINQ の説明の多くは配列でも使えます。
(Python 標準ライブラリの array.array はあまり使われないと思いますので省略します。)
サンプルコードでの配列名とリスト名
オブジェクト名に以下の文字を入れておきます。
宣言や宣言時の型は、誤解がない範囲で省略します。
- Python
- 標準リスト:
lst
- 配列:
arr
- 標準リスト:
- C#
- リスト:
list
- 配列:
array
- リスト:
# 以下が import されていると仮定します
import numpy as np # for numpy.ndarray
lst = [1, 2, 3] # 標準リスト
arr = np.array([1, 2, 3]) # numpy.ndarray
// 以下が using されていると仮定します
using System;
using System.Collections.Generic; // for List
using System.Linq;
List<int> list = new List<int> {1, 2, 3}; // リスト (Collections.Generic)
int[] array = new int[] {1, 2, 3}; // 配列
おことわり
この記事は、数値計算や機械学習の置き換え、パフォーマンスを目的とはしていません。あくまで、Python のリストや numpy.ndarray の基本操作に類似した操作を C# で書く場合の参考です。
本来の numpy 的用途(速度重視)なら、BLAS や MKL など専用演算ライブラリがベースにあるモジュールやパッケージを使った方がもちろん高速なはず。たとえば Math.NET Numerics (MKL版) とか。
Python 基本操作
range
# range(stop) / range(start, stop[, step])
range(5) # 0, 1, 2, 3, 4 (= range(0, 5))
range(1, 5) # 1, 2, 3, 4
# 1つ飛ばし
range(3, 10, 2) # 3, 5, 7, 9 (3-9の奇数)
for val in range(1, 5):
print(val)
// IEnumerable<int> Range (int start, int count);
Enumerable.Range(0, 5) // 0, 1, 2, 3, 4
Enumerable.Range(1, 4) // 1, 2, 3, 4 (第2引数は要素数)
// 1つ飛ばし
Enumerable.Range(0, 4).Select(i => i * 2 + 3) // 3, 5, 7, 9 (*1)
Enumerable.Range(3, 7).Where(i => i % 2 == 1) // 3, 5, 7, 9 (*2)
foreach (var val in Enumerable.Range(1, 4))
{
Console.WriteLine(val);
}
Python3 の range
(Python2 だと xrange
)と C# の Enumerable.Range
はどちらも遅延評価されます。
range/Range もそうですが、Python では (開始値、終了値+1) とするのに対し、C# では (開始値、要素数) とするメソッドが多いようです。
Python の range に合わせる
Python の range(start, stop, step)
に合わせた実装を考えてみます。
// (*1) を拡張してみる(負の数や step < 0 にも対応.step = 0 のエラー処理省略)
Enumerable.Range(0, (int)Math.Ceiling((stop-start)/(double)step))
.Select(i => i * step + start)
// (*2) を拡張してみる(これだと負の数には対応できないし効率悪そう)
// Enumerable.Range(start, stop-start).Where(i => i % step == start % step)
// いっそのこと for ループで静的メソッドを定義してしまう方が分かりやすいかも
public static IEnumerable<int> MyRange(int start, int stop, int step)
{
if (step > 0) for (int i = start; i < stop; i += step) yield return i;
else if (step < 0) for (int i = start; i > stop; i += step) yield return i;
else yield break;
}
遅延評価せずリストや配列にする
lst = list(range(0, 5))
arr = np.array(range(0, 5))
list = Enumerable.Range(0, 5).ToList();
array = Enumerable.Range(0, 5).ToArray();
numpy.arange
np.arange(0, 1, 0.1) # 0, 0.1, ..., 0.9
Enumerable.Range(0, 10).Select(i => i * 0.1); // 0, 0.1,..., 0.9
一般には、先の「(*1)を拡張してみる」の実装で、小数の場合でも行けるのではと思います。
一方、先の MyRange
を double 版の実装にして 0.1 ずつインクリメントなどしてしまうと、丸め誤差で大小比較の判定がうまくいかないです。decimal 版なら大丈夫でしょう。
内包表現や各要素に対する演算
lst2 = [x * x for x in lst1] # もしくは [x ** 2 for x in lst1]
arr2 = arr1 * arr1 # もしくは arr1 ** 2 (powerは遅いはず)
list2 = list1.Select(x => x * x).ToList();
内包表現 (list comprehension) を使っていたところは、LINQ の Select で何とかなることが多いです。あとは、文脈をよく考えて、遅延評価をするかしないか判断が必要になります。
遅延評価した方がよいなら ToList()
せず IEnumerable
のままにします。
例:要素の総和で正規化
要素の総和が1になるように正規化する方法をまとめておきます。確率計算でもよく使います。
lst2 = [x / sum(lst1) for x in lst1]
arr2 = arr1 / sum(arr1) # もしくは arr1 / arr1.sum()
list2 = list1.Select(x => x / list1.Sum()).ToList();
map, filter
Python では、イテレータが欲しい、遅延評価したい、という場合はリスト内包表現ではなく map
や filter
を使えますが、これは LINQ だとそのまま Select
と Where
に対応しそうです。
for x in map(lambda x: x * x, lst): # 各要素の二乗を列挙して表示
print(x)
for x in filter(lambda x: x < 4, lst): # 4未満の要素を表示
print(x)
# 組み合わせ:二乗した要素が10未満なら二乗した値を表示
for x in filter(lambda x : x < 10, map(lambda x: x * x, lst)):
print(x)
foreach(var x in list.Select(x => x * x)) // 各要素の二乗を列挙して表示
Console.WriteLine(x);
foreach(var x in list.Where(x => x < 4)) // 4未満の要素を表示
Console.WriteLine(x);
// 組み合わせ:二乗した要素が10未満なら二乗した値を表示
foreach(var x in list.Select(x => x * x).Where(x => x < 10))
Console.WriteLine(x);
filter
と map
が組み合わさってくると、LINQ のようにメソッドチェーンで書く方が分かりやすいですね。
enumerate
リストや配列の値だけでなくそのインデックスも欲しい場合、Python では enumerate
ですが、C# では Select
を使えます。
lst1 = ['foo', 'bar']
lst2 = ['[{0}] {1}'.format(i, s) for i, s in enumerate(lst1)]
var list1 = new List<string> {"foo", "bar"};
var list2 = list1.Select((s, i) => string.Format("[{0}] {1}", i, s)).ToList()
LINQ の Select
は、value, index
の順序が Python の enumerate
と逆なので注意が必要です。
zip および要素ごとの演算
lst3 = [x - y for x, y in zip(lst1, lst2)] # 要素ごとの引き算
sum([x * y for x, y in zip(lst1, lst2)]) # 内積計算
arr3 = arr1 - arr2 # 要素ごとの引き算
arr1.dot(arr2) # もしくは np.dot(arr1, arr2) # 内積計算
list3 = list1.Zip(list2, (x, y) => x - y).ToList(); // 要素ごとの引き算
list1.Zip(list2, (x, y) => x * y).Sum(); // 内積計算
// もしくは
list3 = Enumerable.Zip(list1, list2, (x, y) => x - y).ToList(); // 要素ごとの引き算
Enumerable.Zip(list1, list2, (x, y) => x * y).Sum(); // 内積計算
3つ以上のリスト(配列)を組み合わせる
lst4 = [x + y + z for x, y, z in zip(lst1, lst2, lst3)] # 要素ごとの足し算
arr4 = arr1 + arr2 + arr3 # 要素ごとの足し算
list4 = list1.Zip(list2, (x, y) => x + y)
.Zip(list3, (zip1, z) => zip1 + z).ToList(); // 要素ごとの足し算
LINQ の Zip
は3つ以上のリストをとれないので、Zip
を繰り返して使うことになるようです。
一段目の Zip
でペアをそのまま次段へ匿名型で渡せば、二段目の Zip
でまとめることもできます。
list4 = list1.Zip(list2, (x, y) => new {x, y})
.Zip(list3, (zip1, z) => zip1.x + zip1.y + z).ToList(); // 要素ごとの足し算
初期化
0 で初期化
lst = [0] * 10 # 1次元ならこれでOK (入れ子など参照が絡む場合は落とし穴)
lst = [0 for _ in range(10)] # これもOK (入れ子など参照が絡んでくるならこちら)
arr = np.zeros(10) # 0 で初期化された要素10の ndarray
int[] array = new int[10]; // 0 で初期化された要素10の配列
List<int> list = new int[10].ToList();
List<int> list = new List<int> (new int[10]); // コンストラクタの引数に配列を渡す
同じ値で埋めて初期化
# False で埋めた配列
lst = [False] * 10 # 1次元ならこれでOK (入れ子など参照が絡む場合は落とし穴)
lst = [False for _ in range(10)] # 入れ子リストなど参照が絡んでくるならこちら
arr = np.full(10, False) # False で初期化された要素10の ndarray
List<bool> list = Enumerable.Repeat(false, 10).ToList(); // こちら落とし穴あり(*1)
List<bool> list = Enumerable.Range(0, 10).Select(_ => false).ToList();
List<bool> list = new bool[10].Select(_ => false).ToList();
// いずれも ToList() を ToArray() にすれば配列に
(*1) リストを入れ子で作る場合など、参照型で ’Repeat’ してしまうと、ひとつ変えるとすべて変わります。この落とし穴は Python の [値] * 要素数
と同じです。
https://qiita.com/yosizo@github/items/1adcff1fc974cde5256a
連番の値で初期化
「遅延評価せずリストや配列にする」の通り、Range
を ToList()
や ToArray()
で連番の値を持ったリストや配列を作ることができます。Python で range
から list
や np.array
を作るのに対応します。
乱数で初期化
整数乱数で初期化
import random # 標準ライブラリ
# randint(a,b)は randrange(a,b+1)のエイリアス
lst = [random.randrange(0, 10) for _ in range(5)] # 0以上9以下で5個
lst = [random.randint(0, 9) for _ in range(5)] # 0以上9以下で5個
# lst = random.sample(range(10), k=5)) # 0以上9以下で5個(重複無しなので上とは異なる)
arr = np.random.randint(0, 10, 5) # 0以上9以下で5個
numpy の random.randint
は、標準ライブラリの random.randint
とは異なり(むしろ randrange
と同じで)指定値未満の乱数を返すので注意!
System.Random rg = new System.Random(); // random number generator (usingしておいてもよい)
int[] rndarray = Enumerable.Range(0, 5).Select(_ => rg.Next(0, 10)).ToArray(); // 0以上9以下で5個
List<int> rndlist = Enumerable.Range(0, 5).Select(_ => rg.Next(0, 10)).ToList(); // 0以上9以下で5個
Next()
の第一引数(最小値)は省略すると0以上となります。
実数乱数で初期化
import random # 標準ライブラリ
lst = [random.random() for _ in range(5)] # 0以上1未満で5個
arr = np.random.rand(5) # 0以上1未満で5個
double[] array = Enumerable.Range(0, 5).Select(_ => rng.NextDouble()).ToArray(); // 0以上1未満で5個
List<double> list = Enumerable.Range(0, 5).Select(_ => rng.NextDouble()).ToList(); // 0以上1未満で5個
乱数生成アルゴリズム自体を変える方法はこちらで解説されています。
値をコピーして初期化
lst1 = [3, 1, 4] # コピー元を適当に作っておく
lst2 = list(lst1) # lst1, lst2 のオブジェクトIDは異なる
lst2 = lst1[:] # これも可 (lst1, lst2 のオブジェクトIDは異なる)
# lst2 = lst1 # lst1, lst2 のオブジェクトIDは一致 (コピーではない)
import copy
lst2 = lst1.copy() # これも可 (lst1, lst2 のオブジェクトIDは異なる)
arr1 = np.array([3, 1, 4]) # コピー元を適当に作っておく
arr2 = np.copy(arr1) # lst1, lst2 のオブジェクトIDは異なる
arr2 = arr1.copy() # これも可 (lst1, lst2 のオブジェクトIDは異なる)
# arr2 = arr1 # arr1, arr2 のオブジェクトIDは一致 (コピーではない)
var list2 = new List<int>(list1); // コピーコンストラクタで初期化
var list2 = list1.ToList(); // これも可
var array2 = new int[array1.Length]; // あらかじめ領域確保
array1.CopyTo(array2, 0); // 第二引数はコピー先の開始 index
Array.Copy(array1, array2, array1.Length); // これも可
var array2 = array1.ToArray(); // これも可
var array2 = array1.Clone() as int []; // これも可 (キャスト必要)
要素へのアクセスやインデックス取得
スライス (Slice)
array[0:5] # 0-4番目の5要素
array[5:] # 0-4の5要素を飛ばして 5-
array[3:5] # 0-2を飛ばして 3-4 の2要素
array.Take(5) // 0-4番目の5要素
array.Skip(5) // 0-4の5要素を飛ばして 5-
array.Skip(3).Take(2) // 0-2を飛ばして 3-4 の2要素
list.GetRange(3, 2) // Listならこれも可
条件を満たす要素のインデックスを取得
idx_lst = np.where(lst == val)
var indexList = list.Select((x, i) => new { x, i })
.Where(xi => xi.x == val).Select(xi => xi.i);
最小(最大)の値を取る要素のインデックスを取得
arr = np.array([2, 1, 8, 4, 0, 8]) # 空でないとする
idx = np.argmin(arr) # 最小値をとる最初の要素(0)のインデックス (4)
idx = np.argmax(arr) # 最大値をとる最初の要素(8)のインデックス (2)
var list = new List<int> { 2, 1, 8, 4, 0, 8 };
// var list = new List<int>(); // 空のケースも考慮するならコメントアウトした方
var argmin = list.Select((x, i) => new { x, i })
.Aggregate((min, xi) => xi.x < min.x ? xi : min).i; // 4
// .Aggregate(new { x = int.MaxValue, i = -1 }, (min, xi) => xi.x < min.x ? xi : min).i; // 4
var argmax = list.Select((x, i) => new { x, i })
.Aggregate((max, xi) => xi.x > max.x ? xi : max).i; // 2
// .Aggregate(new { x = int.MinValue, i = -1 }, (max, xi) => xi.x > max.x ? xi : max).i; // 2
空のリストにも対応するなら Aggregate
の第一引数にシードを与えます。
「特定のキーで Min()
や Max()
すると、そのキーの最小(最大)値だけが戻ってきてしまうが,要素のインデックスもしくは(キーを含む)オブジェクトそのものを取得したい」という質問は昔から StackOverflow でも何度も出ているようです。(Qiita でも。)
別途実装せず LINQ でシンプルに書くには、例えば以下のやり方があります。
-
Aggregate
を使う(上のコード) -> O(n) なのでよいが可読性やや難あり -
OrderBy
やOrderByDescending
してFirst
やTake
-> ソートで O(n log(n)) になる -
Min
やMax
で取得した最小(最大)値でWhere
-> 2回走査してしまう
n が小さければ、可読性重視で 2, 3 もありかも。
再利用性も考え、素直にループ(イテレータ)で実装しておくか、すでにある実装を使うのもよさそう(MoreLINQ の MinBy
や MaxBy
など)。
リスト操作
よく使うリスト操作の対応です。
lst.append(val) # 末尾へ値を追加
lst.extend(lst2) # 末尾へ別のリストを追加
lst.insert(idx, val) # idx へ値を挿入
lst[idx:idx] = lst2 # idx位置に lst2の要素すべてを挿入
lst.clear() # 要素をすべて削除
lst.remove(val) # 最初に見つかった val の値をもつ要素削除
del lst[idx:idx+len] # idx位置から len個の要素を削除
del lst[-1] # 末尾を削除
list.Add(val); // 末尾へ値を追加
list.AddRange(list2); // 末尾へ別のリストを追加
list.Insert(idx, val); // idx へ値を挿入
list.InsertRange(idx, list2); // idx位置に list2の要素すべてを挿入
list.Clear(); // 要素をすべて削除
list.Remove(val); // 最初に見つかった val の値をもつ要素削除
list.RemoveRange(idx, len); // idx位置から len個の要素を削除
list.RemoveAt(list.Count - 1); // 末尾を削除
// その他の操作
list.RemoveAll(v => v == val); // ラムダ式で削除する要素の条件を指定 (左例は val 値をもつ要素全て削除)
文字列操作
リストが絡む文字列操作に限定して取り上げます。
split, join
str1 = 'pen-pineapple-apple-pen'
str1_list = str1.split('-')
str2_list = ['pen', 'pineapple', 'apple', 'pen'] # str1_list
str2 = '-'.join(str2_list) # str1
string str1 = "pen-pineapple-apple-pen";
string[] str1Array = str1.Split('-'); // Split は配列を返す
List<string> str1List = str1Array.ToList(); // リストが必要なら変換
List<string> str2List = new List<string> {"pen", "pineapple", "apple", "pen"};
string str2 = string.Join("-", str2List); // 可変長引数で文字列を渡すことも可能
// string str2 = string.Join("-", str2List.ToArray()); // .NET Framework 3.5は配列を渡すこと
// string str2 = str2List.Aggregate((x, y) => x + "-" + y); // 空リストだとInvalidOperation
// string str2 = str2List.Aggregate(string.Empty, (x, y) => x + "-" + y).TrimStart('-');
しばらく別の言語で書いていると、Python の join
は書き方に迷います。
C# の Split
では、セパレータに char を渡すときは単純ですが、文字列を渡す場合は第二引数のオプション指定(StringSplitOptions.None
など)が必要です。
Join
は基本文字列を渡します・・・ややこしいですね。
string.Join
の代わりに Aggregate
を使うとメソッドチェーンで書けますが、こちらの記事で議論されているようにいくつか問題があります。
まず、空のリストが渡されると例外が投げられます。初期値として空文字を渡すと例外を回避できますが、今度は先頭にもセパレータがついてしまうため、これを削除(上の例では TrimStart
)する必要があります。
StackOverflow では以下のような書き方も紹介されていました。パフォーマンスも string.Join
並みによいということです。
string strJoin = strList.Aggregate(new StringBuilder(),
(x, y) => x.Append(x.Length==0 ? "" : "-").Append(y)).ToString();
ただ、いくつかの記事やスレッドを追った感じでは、あえて Aggregate
にせず、素直に string.Join
する方がよい、という結論が多いようです。
print とフォーマット指定
lst = [1/2, 3/4, 5/6]
print(lst) # [0.5, 0.75, 0.8333333333333334]
print("[" + ", ".join("%.2f" % x for x in lst) + "]") # [0.50, 0.75, 0.83]
arr = np.array([1/2, 3/4, 5/6])
print(arr) # [0.5 0.75 0.83333333]
print(np.array_str(arr, precision=2)) # [0.5 0.75 0.83]
print(np.array2string(arr, precision=2, floatmode='fixed')) # [0.50 0.75 0.83]
# array2string はオプション豊富.小数点以下の桁数を固定して0埋めできる
# array_str の中身は array2string とのこと
# numpy は以下でも可(一時的に使うなら元の設定に戻しておく)
orig_printopt = np.get_printoptions() # 現在の設定を辞書へ
np.set_printoptions(precision=2, floatmode='fixed')
print(arr) # [0.50 0.75 0.83]
np.set_printoptions(**orig_printopt) # 設定を戻す(辞書を展開して引数へ)
var list_print = new List<double> {1.0/2, 3.0/4, 5.0/6};
Console.WriteLine("[" + string.Join(", ", list) + "]"); // [0.5, 0.75, 0.833333333333333]
Console.WriteLine("[" + string.Join(", ", list.Select(x => x.ToString("0.00"))) + "]"); // [0.50, 0.75, 0.83]
ソート
昇順・降順ソートと安定性
sorted_lst = sort(lst) # 昇順ソートされたリストが返る
lst.sorted() # lstそのものが昇順ソートされる (in-place)
sorted_data = np.sort(arr) # 昇順ソート
sorted_data = np.sort(arr)[::-1] # 降順ソート
Array.Sort(array); // arrayそのものが昇順ソートされる (inplace)
Array.Reverse(array); // 昇順ソート後にひっくり返して逆順へ
list.Sort(); // listそのものが昇順ソートされる
list.Reverse(); // 昇順ソート後にひっくり返して逆順へ
// ラムダ式を使うと逆順ソートも一回で
Array.Sort(array, (x, y) => y - x); // arrayそのものが逆順ソートされる
list.Sort((x, y) => y - x); // listそのものが逆順ソートされる
// LINQを使ってもソートできる(特に複数のキーがある場合は便利.リストでも利用可)
sorted_array = array.OrderBy(x => x); // 昇順ソート(戻り値で受け取る)
sorted_array = array.OrderByDescending(x => x); // 降順ソート(戻り値で受け取る)
List の内部は配列なのでソートのアルゴリズムは同じとか。
ソートアルゴリズムの安定性について:Reverse()
はちょっと強引ですが、もともと Array.Sort()
も安定ソートではないので、同じ値のデータの順序を気にしない場合はありでしょう。
一方で、LINQ の OrderBy()
は安定ソートとのこと(Stack Overflow より)。安定なソートが欲しい場合は LINQ が手っ取り早いかも。
ちなみに Python では list のソートは安定で、numpy でも 'stable' をオプションで渡すと安定性が保証されます。
ソートされたデータの添え字
idx = np.argsort(arr)
// Array.Sort で
int[] array2 = new int[array1.Length]; // ソート結果を別配列にするための準備
array1.CopyTo(array2, 0); // array1 を array2 にコピー
int[] idx = Enumerable.Range(0, array2.Length).ToArray(); // 連番の配列 0,1,...
Array.Sort(array2, idx); // 別配列も一緒に並べ替えるオーバーロードがあるのでこれを使う
for(var i=0; i < array2.Length; i++) // ついでに表示
{
Console.WriteLine($"key: {array2[i]}, idx: {idx[i]}");
}
// LINQ で
var sortedPair = array1
.Select((x, i) => new KeyValuePair<int, int>(x, i))
.OrderBy(x => x.Key);
var array2 = sortedPair.Select(x => x.Key).ToList();
var idx = sortedPair.Select(x => x.Value).ToList();
// 分かりやすい名前でタプル(ペア)を作りそのまま使ってしまうのもあり
var sortedPair = array1
.Select((x, i) => (Key: x, Index: i)) // C# 7 (.NET Framework 4.7 など)
.OrderBy(x => x.Key)
.ToList();
sortedPair.ForEach(x => { // ついでに表示
Console.WriteLine($"key: {x.Key}, idx: {x.Index}");
});
この Stack Overflow の記事を参考にしました。
二つの系列をまとめてソート
var list1 = new List<int> { 3, 4, 1, 2 };
var list2 = new List<int> { 6, 7, 8, 9 };
var zipped = list1.Zip(list2, (x1, x2) => new {key1 = x1, key2 = x2});
var sorted = zipped.OrderBy(x => x.key1).Select(x => x.key2); // 8, 9, 6, 7
基本的な統計量や数学関数
基本的な統計量
np.mean()
np.std()
np.max()
np.min()
np.argmax()
double mean = data.Average();
double varP = data.Select(x => (x - mean) * (x - mean)).Sum() / data.Length;
double stdevP = Math.Sqrt(var);
そろそろ Math.NET などパッケージを入れたほうがよいかもしれません。
bool型の統計量
lst = [True, False, True]
sum(lst) # 2
any(lst) # True
all(lst) # False
var list = new bool[] { true, false, true };
list.Count() // Sum() ではない
list.Any(v => v) // true
list.All(v => v) // false
数学関数
np.round()
Math.Round()
多次元配列
以下書きかけです。リストのリストを矩形行列としてみた操作(つまり内側のリストはすべて同じ長さと仮定)。
初期化
mat = np.zeros((NROWS, NCOLS))
List<List<double>> mat = Enumerable.Range(0, NROWS).Select(_ => new double[NCOLS].ToList()).ToList();
Repeat
ではなくそれぞれ実体化していく。
行や列をコピー
arr = mat[i,:].copy()
arr = mat[:,j].copy()
arr = mat.flatten()
var list = mat[i];
var list = mat.Select(r => r[j]).ToList();
var list = mat.SelectMany(r => r).ToList(); // flatten
列ごとの平均
mat.mean(0)
var ret = mat.SelectMany(r => r.Select((val, j) => new {val, j}))
.GroupBy(x => x.j, (key, y) => {return y.Average(z => z.val);});
いったん {val, j}
を要素とする x
へ平坦化したあとに、インデックス j
でグルーピングして y
とし、それぞれのグループで平均を計算している。
参考: https://teratail.com/questions/76764
その他(未テスト)
DeepCopy
- https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-of-an-object-in-net-c-specifically
- http://www.atmarkit.co.jp/ait/articles/1705/24/news040.html