プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

C#ぼっち勉強会 配列

 はい、随分期間が空きましたが、別に勉強サボってた訳じゃ無くて、単にブログ書くのが面倒くさかっただけです。

 チマチマとC#で作る様になってきてJavaと比較して使いにくい点もままあるけど(特にInterfaceがデフォルト実装持てないのと、String.Spritが使いにくい、インデント幅4は長すぎる)、まぁ、良い言語かなと思います。

 

 文法は、JavaとC++をやっていれば、微妙な違いはありますが、だいたいどちらかの言語にある概念を持ってくれば理解できます。
 というわけで、文法はすっ飛ばして、データ型とその処理について書いていこうと思う。言語を使いこなせるかどうかって、結局、基本データ型(コレクション、ファイル、時間、Thread)の操作を使いこなせるかどうかが結構大きいからね。

 最初は配列からだ。

配列


 配列の型宣言は型[]だけが許されるっぽい。参照型で全てArrayクラスの継承クラス。各要素の初期値は0かnullである。

int[] array = {1,2,3,4};
// int array[] = {1,2,3,4}はだめ


 配列はおおよそ以下の種類がある。

  • 1次元配列
  • 多次元配列
  • ジャグ配列
  • スタック上の配列(Span)

 多次元配列はJavaにはない配列で、中身は1次元配列だけど、添え字で多次元的にアクセス出来る配列。
 以下の例の様に、array2dの添え字 [y,x]は、arrayの [x + y*X] と等価だ。(Xは1次元の長さ) 次元はRankに格納されていて、各次元の長さはGetLength(n)から取得出来る。

int[] array = {1,2,3 ,  4,5,6};
int[,] array2d = {{1,2,3}, {4,5,6}};

Console.Write(
    "[1,2] => array[1*3+2]({0}),  array2d[1,2]=({1})",
   array[2 + 1*3 ], array2d[1,2]);

 なお、多次元配列はforeachの前には無意味だ。

//どちらも同じ。
foreach( var i : array) Console.WriteLine(i);
foreach( var i : array2d)   Console.WriteLine(i);

 
 次に、ジャグ配列はJavaの配列と同じで、配列の要素に別の配列を入れるタイプの配列だ。各要素ごとに長さが違う配列を入れることが出来る。

int[][] array2dJ = new int[2][];
array2dJ[0] = new int[5];
array2dJ[1] = new int[3];

 
 最後に、なんとC#はnewせずに直接スタック上に配列が確保出来る。スゲぇ。Javaにもくれ。

// newではなく、スタック上に確保(解放コスト無し)
Span<int> array = stackalloc int[10];
array[0] = 100;

 なお、C#7.2以上じゃないと駄目なので、program.csprojのPropertyGroup.LangVersionをlatestとかにしておくこと。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <DebugType>Full</DebugType>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>
</Project>

配列の各要素を結合した文字列を作成

 String.Joinで各要素を結合した文字列を作ることが出来る。

int[] array = { 1, 2, 3, 4, 5, 6 };
Console.WriteLine("array={0}",String.Join(",",array));

 ただし、多次元配列だと駄目。面倒くさい。

部分配列(Span)

 Spanを使うことで部分配列を取り出すことが出来る。
 ただし、やっぱり多次元配列だと駄目。(DangerousCreateで出来る?)
 多次元配列いらなくない?[x + y * width]って書くのJavaで慣れてしまったし、正直、デメリットの方が大きいわ。
 Cの多次元配列と同じ構造なら、多次元から1次元にポインタ変換出来そうな気がするんだけど、手段無いのかな?

int[] array = { 1, 2, 3, 4, 5, 6 };
var span = array.AsSpan(2,3);// new Span<int>(array,2,3);
span[0] = 100;
span[1] = 101;
span[2] = 102;
//span[3] = 103; これはエラー
// {1,2,100,101,102,6}
Console.WriteLine("array={0}",String.Join(",",array));


特定のデータで埋める(JavaのArrays.fill)

 JavaのArrays.fillはArray.Clear,Array.Fillそれに当たる。0やnull初期化したいならArray.Clearが引数が無くて楽かも知れない。

int[] array = { 1, 2, 3, 4, 5, 6 };

// {1,0,0,0,5,6}
Array.Clear(array,1,3);
// {1,0,0,100,100,6}
Array.Fill(array,100,3,2);

検索や反転、コピー

 バイナリサーチ、反転、コピーなどのJavaではArraysクラスにstaticメソッドで提供されている関数は、C#ではArrayに一通り揃っている。


 ちなみに、int[]の配列では無く、Spanにしておくと、メソッドとして使えるっぽい。Spanで扱えるところは基本的にSpanで扱った方が楽そうだ。

int[] array={1,2,3,4,5,6};
Span<int> span = array;
Array.Reverse(array);
span.Reverse();

map,filter

 JavaでのmapやfilterはC#ではLINQで実現される。Javaと違って、Streamにしなくてもそのまま使える。

 だが、これがぶっちゃけ言って全然慣れない!!
 なんで、mapがSelectなん?SQLぽさなんていらんわ。Selectって響き的にfilterっぽいじゃん。で、filterがなんでWhereなん?英語的には条件式ってIf,Whenもありうるじゃん。Aggregate?英語音痴の私、この単語知らない。(集計)
 正直この名前付けには不満しかないわ。flattenないし。何でmap→flattenを一つにしてSelectManyにしたわけ?

 逆に、JavaのStreamでは削除されたzipがあるのは素晴らしい。

Java C#
map Select
filter Where
reduce Aggregate
sort OrderBy
sum,max,min,distinct Sum,Max,Min,Distinct
first FirstOrDefault
なし Zip,Concat,Join
flatten SelectMany(_=>_)
toArray ToArray
collect(toList()) ToList
groupingBy GroupBy
parallel AsParallel
IntStream.range Enumerable.Range


 Javaに無いJoinは、最初よく分からんかったけど、こういうことらしい。SQLの内部結合と同じ物を表現する為にあるらしい。SQLとか触ったことしかないからなぁ。

array.Join(array2, func1 , func2 , func3);
// ↓
array.Zip(array2,(x,y) => (x,y))
     .When(x => func1(x.x) == func2(x.y))
     .Select(x => func3(x.x,x.y))


 以上、次はコレクションかな。