C# (LINQ) で Python (list, numpy) っぽくリスト操作

# はじめに

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`


```python:Python
# 以下が import されていると仮定します
import numpy as np  # for numpy.ndarray

lst = [1, 2, 3]  # 標準リスト
arr = np.array([1, 2, 3])  # numpy.ndarray
```

```csharp:C#
// 以下が 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版)](https://numerics.mathdotnet.com/MKL.html) とか。

# Python 基本操作

## range

```python:Python
# 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)
```

```csharp:C#
// 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)` に合わせた実装を考えてみます。

```csharp:C#でrange(python)に近いもの
// (*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;
}
```

### 遅延評価せずリストや配列にする

```python:Python
lst = list(range(0, 5))
arr = np.array(range(0, 5))
```

```csharp:C#
list = Enumerable.Range(0, 5).ToList();
array = Enumerable.Range(0, 5).ToArray();
```

### numpy.arange

```python:Python
np.arange(0, 1, 0.1)  # 0, 0.1, ..., 0.9
```

```csharp:C#
Enumerable.Range(0, 10).Select(i => i * 0.1);  // 0, 0.1,..., 0.9
```

一般には、先の「(*1)を拡張してみる」の実装で、小数の場合でも行けるのではと思います。

一方、先の `MyRange` を double 版の実装にして 0.1 ずつインクリメントなどしてしまうと、丸め誤差で大小比較の判定がうまくいかないです。decimal 版なら大丈夫でしょう。

## 内包表現や各要素に対する演算

```python:Python
lst2 = [x * x for x in lst1]  # もしくは [x ** 2 for x in lst1]
arr2 = arr1 * arr1  # もしくは arr1 ** 2 (powerは遅いはず)
```

```csharp:C#
list2 = list1.Select(x => x * x).ToList();
```

内包表現 (list comprehension) を使っていたところは、LINQ の Select で何とかなることが多いです。あとは、文脈をよく考えて、遅延評価をするかしないか判断が必要になります。
遅延評価した方がよいなら `ToList()` せず `IEnumerable` のままにします。

### 例：要素の総和で正規化

要素の総和が1になるように正規化する方法をまとめておきます。確率計算でもよく使います。

```python:Python
lst2 = [x / sum(lst1) for x in lst1]
arr2 = arr1 / sum(arr1)  # もしくは arr1 / arr1.sum()
```

```csharp:C#
list2 = list1.Select(x => x / list1.Sum()).ToList();
```

## map, filter

Python では、イテレータが欲しい、遅延評価したい、という場合はリスト内包表現ではなく `map` や `filter` を使えますが、これは LINQ だとそのまま `Select` と `Where` に対応しそうです。

```python:Python
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)
```

```csharp:C#
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` を使えます。

```python:Python
lst1 = ['foo', 'bar']
lst2 = ['[{0}] {1}'.format(i, s) for i, s in enumerate(lst1)]
```

```csharp:C#
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 および要素ごとの演算

```python:Pythonで2つのリストをzip
lst3 = [x - y for x, y in zip(lst1, lst2)]  # 要素ごとの引き算
sum([x * y for x, y in zip(lst1, lst2)])  # 内積計算
```

```python:NumPyで要素ごとの演算
arr3 = arr1 - arr2  # 要素ごとの引き算
arr1.dot(arr2) # もしくは np.dot(arr1, arr2)  # 内積計算
```

```csharp:C#
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つ以上のリスト（配列）を組み合わせる

```python:Python
lst4 = [x + y + z for x, y, z in zip(lst1, lst2, lst3)]  # 要素ごとの足し算
arr4 = arr1 + arr2 + arr3  # 要素ごとの足し算
```

```csharp:C#で3つのリストをZip
list4 = list1.Zip(list2, (x, y) => x + y)
    .Zip(list3, (zip1, z) => zip1 + z).ToList(); // 要素ごとの足し算
```

LINQ の `Zip` は3つ以上のリストをとれないので、`Zip` を繰り返して使うことになるようです。

一段目の `Zip` でペアをそのまま次段へ匿名型で渡せば、二段目の `Zip` でまとめることもできます。

```csharp:C#で3つのリストをZip（匿名型でつなげる）
list4 = list1.Zip(list2, (x, y) => new {x, y})
    .Zip(list3, (zip1, z) => zip1.x + zip1.y + z).ToList(); // 要素ごとの足し算
```


# 初期化

## 0 で初期化

```python:Python
lst = [0] * 10  # 1次元ならこれでOK (入れ子など参照が絡む場合は落とし穴)
lst = [0 for _ in range(10)]  # これもOK (入れ子など参照が絡んでくるならこちら)
arr = np.zeros(10)  # 0 で初期化された要素10の ndarray
```

```csharp:C#
int[] array = new int[10];  // 0 で初期化された要素10の配列
List<int> list = new int[10].ToList();
List<int> list = new List<int> (new int[10]);  // コンストラクタの引数に配列を渡す
```

## 同じ値で埋めて初期化

```python:Python
# False で埋めた配列
lst = [False] * 10  # 1次元ならこれでOK (入れ子など参照が絡む場合は落とし穴)
lst = [False for _ in range(10)]  # 入れ子リストなど参照が絡んでくるならこちら
arr = np.full(10, False)  # False で初期化された要素10の ndarray
```

```csharp:C#
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

## 連番の値で初期化

[「遅延評価せずリストや配列にする」](https://qiita.com/memakura/items/6b3e6e06eae51725fd5d#%E9%81%85%E5%BB%B6%E8%A9%95%E4%BE%A1%E3%81%9B%E3%81%9A%E3%83%AA%E3%82%B9%E3%83%88%E3%82%84%E9%85%8D%E5%88%97%E3%81%AB%E3%81%99%E3%82%8B)の通り、`Range` を `ToList()` や `ToArray()` で連番の値を持ったリストや配列を作ることができます。Python で `range` から `list` や `np.array` を作るのに対応します。

## 乱数で初期化

### 整数乱数で初期化

```python:Python
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` と同じで）指定値未満の乱数を返すので注意！

```csharp:C#
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以上となります。

### 実数乱数で初期化

```python:Python
import random  # 標準ライブラリ
lst = [random.random() for _ in range(5)]  # 0以上1未満で5個

arr = np.random.rand(5)  # 0以上1未満で5個
```

```csharp:C#
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個
```

乱数生成アルゴリズム自体を変える方法は[こちらで](https://dobon.net/vb/dotnet/programing/random.html)解説されています。

## 値をコピーして初期化

```python:Python
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は一致 (コピーではない)

```

```csharp:C#
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)

```python:Python(numpy)
array[0:5]  # 0-4番目の5要素
array[5:]  # 0-4の5要素を飛ばして 5-
array[3:5]  # 0-2を飛ばして 3-4 の2要素
```

```csharp:C#
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ならこれも可
```

## 条件を満たす要素のインデックスを取得

```python:Python(numpy)
idx_lst = np.where(lst == val)
```

```csharp:C#
var indexList = list.Select((x, i) => new { x, i })
    .Where(xi => xi.x == val).Select(xi => xi.i);
```

## 最小（最大）の値を取る要素のインデックスを取得

```python:Python(numpy)
arr = np.array([2, 1, 8, 4, 0, 8])  # 空でないとする

idx = np.argmin(arr)  # 最小値をとる最初の要素(0)のインデックス (4)
idx = np.argmax(arr)  # 最大値をとる最初の要素(8)のインデックス (2)
```

```csharp:C#
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 でも何度も出ている](https://stackoverflow.com/questions/914109/how-to-use-linq-to-select-object-with-minimum-or-maximum-property-value)ようです。（[Qiita でも](https://qiita.com/Zuishin/items/95e171eccc128bdd6429)。）

別途実装せず LINQ でシンプルに書くには、例えば以下のやり方があります。

1. `Aggregate` を使う（上のコード） -> O(n) なのでよいが可読性やや難あり
1. `OrderBy` や `OrderByDescending` して `First` や `Take` -> ソートで O(n log(n)) になる
1. `Min` や `Max` で取得した最小（最大）値で `Where` -> 2回走査してしまう

n が小さければ、可読性重視で 2, 3 もありかも。
再利用性も考え、素直にループ（イテレータ）で実装しておくか、すでにある実装を使うのもよさそう（[MoreLINQ](https://morelinq.github.io/) の `MinBy` や `MaxBy` など）。

## リスト操作

よく使うリスト操作の対応です。

```python:Python(標準リスト)
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]  # 末尾を削除
```

```csharp:C#
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

```python:Python
str1 = 'pen-pineapple-apple-pen'
str1_list = str1.split('-')

str2_list = ['pen', 'pineapple', 'apple', 'pen']  # str1_list
str2 = '-'.join(str2_list)  # str1
```

```csharp:C#
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` を使うとメソッドチェーンで書けますが、[こちらの記事](http://erresen.github.io/csharp/linq/strings/2016/08/25/aggregate-vs-string-join.html)で議論されているようにいくつか問題があります。
まず、空のリストが渡されると例外が投げられます。初期値として空文字を渡すと例外を回避できますが、今度は先頭にもセパレータがついてしまうため、これを削除（上の例では `TrimStart`）する必要があります。

[StackOverflow](https://stackoverflow.com/questions/217805/using-linq-to-concatenate-strings) では以下のような書き方も紹介されていました。パフォーマンスも `string.Join` 並みによいということです。

```csharp:C#/LINQのAggregateで文字列を結合
string strJoin = strList.Aggregate(new StringBuilder(),
    (x, y) => x.Append(x.Length==0 ? "" : "-").Append(y)).ToString();
```

ただ、いくつかの記事やスレッドを追った感じでは、あえて `Aggregate` にせず、素直に `string.Join` する方がよい、という結論が多いようです。

## print とフォーマット指定

```python:Python
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)  # 設定を戻す（辞書を展開して引数へ）
```

```csharp:C#
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]
```

# ソート

## 昇順・降順ソートと安定性

```python:Python
sorted_lst = sort(lst)  # 昇順ソートされたリストが返る
lst.sorted()  # lstそのものが昇順ソートされる (in-place)

sorted_data = np.sort(arr)  # 昇順ソート
sorted_data = np.sort(arr)[::-1]  # 降順ソート
```

```csharp:C#
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 より](https://stackoverflow.com/questions/148074/is-the-sorting-algorithm-used-by-nets-array-sort-method-a-stable-algorithm)）。安定なソートが欲しい場合は LINQ が手っ取り早いかも。

ちなみに Python では list のソートは安定で、numpy でも 'stable' をオプションで渡すと安定性が保証されます。


## ソートされたデータの添え字

```python:Python
idx = np.argsort(arr)
```

```csharp:C#(array1とインデックスのソート結果をarray2とidxへ)
// 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 の記事](https://stackoverflow.com/questions/1760185/c-sharp-sort-list-while-also-returning-the-original-index-positions)を参考にしました。

## 二つの系列をまとめてソート

```csharp:C#
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
```

# 基本的な統計量や数学関数

## 基本的な統計量

```python:Python
np.mean()
np.std()
np.max()
np.min()
np.argmax()
```

```csharp:C#
double mean = data.Average();
double varP = data.Select(x => (x - mean) * (x - mean)).Sum() / data.Length;
double stdevP = Math.Sqrt(var);
```

そろそろ Math.NET などパッケージを入れたほうがよいかもしれません。

## bool型の統計量

```python:Python
lst = [True, False, True]
sum(lst)  # 2
any(lst)  # True
all(lst)  # False
```

```csharp:C#
var list = new bool[] { true, false, true };
list.Count()  // Sum() ではない
list.Any(v => v)  // true
list.All(v => v)  // false
```

## 数学関数

```python:Python
np.round()
```

```csharp:C#
Math.Round()
```


# 多次元配列

以下書きかけです。リストのリストを矩形行列としてみた操作（つまり内側のリストはすべて同じ長さと仮定）。

## 初期化

```python:Python
mat = np.zeros((NROWS, NCOLS))
```

```csharp:C#
List<List<double>> mat = Enumerable.Range(0, NROWS).Select(_ => new double[NCOLS].ToList()).ToList();
```

`Repeat` ではなくそれぞれ実体化していく。


## 行や列をコピー
```python:Python
arr = mat[i,:].copy()
arr = mat[:,j].copy()
arr = mat.flatten()
```

```csharp:C#
var list = mat[i];
var list = mat.Select(r => r[j]).ToList();
var list = mat.SelectMany(r => r).ToList();  // flatten
```

## 列ごとの平均

```python:Python
mat.mean(0)
```

```csharp:C#
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

### 転置

- https://qiita.com/kskhsn_kskhsn15/items/0c42f560fe20259ff83f#_reference-76885976e8705d813206