• 検索結果がありません。

LINQ 資料 電脳梁山泊烏賊塾 LINQ 概論 LINQ 基礎知識 初めに 此処では 文字列を ToCharArray メソッドで文字型配列に分解して LINQ で処理する事で LINQ に付いて解説する 亦 数値配列を操作したり Aggregate で集計したりする 通常の関数として記述 先ずは

N/A
N/A
Protected

Academic year: 2021

シェア "LINQ 資料 電脳梁山泊烏賊塾 LINQ 概論 LINQ 基礎知識 初めに 此処では 文字列を ToCharArray メソッドで文字型配列に分解して LINQ で処理する事で LINQ に付いて解説する 亦 数値配列を操作したり Aggregate で集計したりする 通常の関数として記述 先ずは"

Copied!
19
0
0

読み込み中.... (全文を見る)

全文

(1)

■ LINQ 基礎知識 ■ ■ 初めに

此処では、文字列を ToCharArray メソッドで文字型配列に分解して LINQ で処理する事で、LINQ に 付いて解説する。亦、数値配列を操作したり、Aggregate で集計したりする。

■ 通常の関数として記述

先ずは、LINQ を使わずに、普通の関数としての記述を、下記に示す。 Visual Basic

' 文字列から数字丈を抜き出す関数

Private Function RemoveNonNumericChars(Src As String) As String ' 空の文字列の宣言

Dim Result As String = String.Empty

' 引数の文字列を 1 文字宛チェック

For Each Ch As Char In Src.ToCharArray() If Char.IsDigit(Ch) Then ' 数字なら結合 Result = Result + Ch End If Next ' 結果を返す Return Result End Function

Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) _ Handles Button1.Click

Dim Str = "tel:0120-444-444"

Dim NumericStr = RemoveNonNumericChars(Str)

Messagebox.show("NumericStr = " + NumericStr) End Sub

C#

private string RemoveNonNumericChars(string src) {

// 空の文字列の宣言

string result = string.Empty;

// 引数の文字列を 1 文字宛チェック foreach (char ch in src.ToCharArray()) { // 数字なら結合 if (Char.IsDigit(ch)) result += ch; }

L

LI

IN

NQ

Q

概論

(2)

// 結果を返す return result; }

private void Button1_Click(object sender, EventArgs e) {

string str = "tel:0120-444-444";

string numericstr = = RemoveNonNumericChars(str);

MessageBox.Show("NumericStr = " + numericstr); }

上記の処理は、文字列を与えると、其の文字列を Char 型の配列に変換(ToCharArray メソッドで変換 可能)して、1 文字宛 For Each でループを回し乍、Char.IsDigit(Ch)を使用して、数字かを判定し、数 字ならば結果文字列に結合して行くと謂う物で有る。"a1b2c3"ならば、a と b と c が除かれて、"123" が返って来る事に成る。

■ LINQ で記述

前記を LINQ で置き換えると、下記の様に成る(Linq 名前空間を Imports して置く事)。 Visual Basic

Private Function RemoveNonNumericChars(Src As String) As String ' 空の文字列の宣言

Dim Result As String = String.Empty

' LINQ で同じ処理を記述

' 引数の文字列を 1 文字宛チェック

Result = Src.ToCharArray().Where(Function(Ch) Char.IsDigit(Ch)). _ Aggregate(String.Empty, Function(R As String, Ch As Char) R + Ch)

' 結果を返す Return Result End Function C#

private string RemoveNonNumericChars(string src) {

' 空の文字列の宣言

string result = string.Empty;

' LINQ で同じ処理を記述

result = src.ToCharArray().Where(ch => Char.IsDigit(ch)). Aggregate(String.Empty, (r, ch) => r + ch); ' 結果を返す return result; } 上記で Where メソッドの結果に対して Aggregate メソッドを適用すると謂う様に、メソッドを鎖の様 に繋げて居るが、斯う謂う物をメソッドチェーンと呼ぶ。 下記の様に、ドット部分でメソッドチェーンに改行を入れると少し見易く成るかも知れない。

(3)

Visual Basic

Private Function RemoveNonNumericChars(Src As String) As String ' 空の文字列の宣言

Dim Result As String = String.Empty

' LINQ で同じ処理を記述

' 引数の文字列を 1 文字宛チェック Result = Src.ToCharArray(). _

Where(Function(Ch) Char.IsDigit(Ch)). _

Aggregate(String.Empty, Function(R As String, Ch As Char) R + Ch)

' 結果を返す Return Result End Function C#

private string RemoveNonNumericChars(string src) {

' 空の文字列の宣言

string result = string.Empty; ' LINQ で同じ処理を記述 result = src.ToCharArray(). Where(ch => Char.IsDigit(ch)). Aggregate(String.Empty, (r, ch) => r + ch); ' 結果を返す return result; } 以下に、LINQ の部分を詳しく見て行く事にする。 Visual Basic Src.ToCharArray(). C# src.ToCharArray(). 上記の部分は LINQ ではないので問題は無いと思う。文字列を、文字型(Char 型)の配列に変換して 居る。LINQ は配列や List、HashTable、Dictionary 等のコレクション型を処理する為のライブラリな ので、文字列型を一旦、1 文字単位で処理出来る文字型の配列に変換して居る訳で有る。 上記の戻り値は下記の様に成る。 Visual Basic {"t"c, "e"c, "l"c, ":"c, "0"c, "1"c, "2"c, "0"c, "-"c, "4"c, "4"c, "4"c, "-"c, "4"c, "4"c, "4c"} C# {'t', 'e', 'l', ':', '0', '1', '2', '0', '-', '4', '4', '4', '-', '4', '4', '4'} 此の戻り値に対して、次の Where メソッドを呼び出して居る。 Visual Basic Where(Function(Ch) Char.IsDigit(Ch)). C# Where(ch => Char.IsDigit(ch)).

(4)

Char 型の配列に対して Where メソッドを呼んで居る。Where では、コレクションや配列を、指定し た条件で絞り込む事が出来る。引数として与えるのは、条件判定関数で有る。配列の各要素(此処では 1 文字分のデータ)を引数として受け取り、其れが条件に合う文字ならば True を、合わない文字なら ば False を返すと謂う関数が、今回の条件判定関数に成る。 具体的には、下記の部分が条件判定関数で有る。 Visual Basic ' Where メソッドに与えて居る単一行の無名関数(ラムダ式) Function(Ch) Char.IsDigit(Ch) C# // Where メソッドに与えて居る単一行の無名関数(ラムダ式) ch => Char.IsDigit(ch)

Ch は Char 型の引数で有る。今回処理するのが Char 型の配列なので、条件判定関数の引数も Char 型 に成る。其の Ch を、Char.IsDigit(Char) を使用して、数字か何うかを判定して居り、戻り値は True か False に成る。猶、単一行なので End Function が無いが、複数行にしても勿論構わない。

Visual Basic

' Where メソッドに与えている無名関数(複数行の場合) Function(ch)

Dim Result As Boolean = Char.IsDigit(Ch) Return Result

End Function C#

// Where メソッドに与えて居る単一行の無名関数(ラムダ式) {ch =>

bool result = Char.IsDigit(ch); return result; } 複数行の場合は Return が必要に成るので注意を要する。 Where メソッドは、配列の各要素(1 文字宛のデータ)に対して此の無名関数を呼び出し、結果が True の物丈を残した配列を新たに作る。Where メソッドの戻り値は、下記の通りで有る。 "t"c False "e"c False "l"c False ":"c False "0"c True "1"c True "2"c True "0"c True "-"c False "4"c True "4"c True "4"c True "-"c False "4"c True "4"c True "4c" True

(5)

次は、Where メソッドの戻り値が True の場合に、引数の Ch を結果文字列 Result に連結していく部分 で有る。此れには Aggregate メソッドを使用する。

Visual Basic

Aggregate(String.Empty, Function(R As String, Ch As Char) R + Ch) C# Aggregate(String.Empty, (r, ch) => r + ch); Aggregate メソッドは、配列やコレクションのデータを、単一のデータに集計する。其の為に、先ず R に初期値(此処では、String.Empty)を設定し、配列の最初の要素から順番に 1 個宛、R に対して適用 して行く。此処では、其の適用処理が、Aggregate メソッドの第 2 引数として与えて居る無名関数に成 る。猶、Aggregate メソッドの第一引数 String.Empty は、R の初期値で有る。 無名関数丈を抜き出すと、内容は、文字列型の R に、引数の ch を連結して新しい文字列として返すと 謂う物で有る。 Visual Basic ' Aggregate メソッドに与えて居る単一行の無名関数(ラムダ式) Function(R As String, Ch As Char) R + Ch

C# // Aggregate メソッドに与えて居る単一行の無名関数(ラムダ式) (r, ch) => r + ch Aggregate メソッドは、先ず R に初期値 String.Empty を設定してから、此の無名関数に R と配列の第 一要素を入れて呼び出す。無名関数を仮に Agg、Aggregate メソッドに渡される配列データを Arr とす ると、処理のイメージは下記の通りで有る。 Visual Basic ' Aggreagete の処理イメージ

Dim Result As String = String.Empty dim Ch = Arr(0)

Result = Agg(Result, Ch) C#

// Aggreagete の処理イメージ string result = String.Empty; char ch = Arr(0); result = Agg(result, ch) 此の結果、R には配列の第一要素(0 番目の要素)が文字列として格納される。Aggregate メソッドは、 其の儘、次の要素(1 番目の要素)を取得し、続けて Agg を呼び出して Result に格納する。 Visual Basic ' Aggreagete の処理イメージ(続き) dim Ch = Arr(1) Result = Agg(Result, Ch) C# // Aggreagete の処理イメージ(続き) char ch = Arr(1); result = Agg(result, ch) 然して、2 番目の要素、3 番目の要素…と、次々と呼び出しては Result に格納して行く。

(6)

Visual Basic ' Aggreagete の処理イメージ(続き 2) dim Ch = Arr(2) Result = Agg(Result, Ch) dim Ch = Arr(3) Result = Agg(Result, Ch) … C# // Aggreagete の処理イメージ(続き 2) char ch = Arr(2); result = Agg(result, ch) char ch = Arr(3); result = Agg(result, ch) … 結果的に R には、総ての文字型配列の要素が、1 つの文字列として連結された物が格納される。 此れが、Aggregate メソッドが内部で行って居る事で有る。プログラマが為す可き事は、R の初期値と、 其の R に対して各要素を何の様に適用して行くのかと謂う処理を定義した無名関数而巳で有る。 Aggregate の戻り値は、指定した無名関数の戻り値の型と同じに成る。今回は文字列を返して居るので 文字列型と成るが、別に文字列型でなくても、各 Char の ASCII 値を合計した Integer 型でも構わない。 此の様に、配列やコレクションの各要素を順番に同じ方法で処理して行き、単一の値を作り出す事を「畳 み込み」と呼ぶ。他の関数型言語では fold と謂う名前の関数名に成って居る事が多い様なので、憶えて 置くと良いかと思う。 此等を総て繋げると、冒頭の LINQ に成る訳で有る。 ■ 数値配列を Where で絞り込む 配列データを Where メソッドで絞り込む例を、Integer 型の配列を使用して、試して見る事にする。 偶数丈を抜き出す LINQ のコードを、下記に示す。 Visual Basic

Dim Arr() As Integer = {1, 2, 3, 4, 5, 6, 7, 8, 9} ' 偶数丈を抽出

Dim Even() As Integer = Arr.Where(Function(Item) Item Mod 2 = 0).ToArray() For Each I In Even

Messagebox.Show(I.ToString( )) ' 2 4 6 8 が出力される Next

C#

int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9}; // 偶数丈を抽出

int[] even() = arr.Where(item => item % 2 == 0).ToArray(); foreach(int i in even)

(7)

Messagebox.Show(i.ToString( )) // 2 4 6 8 が出力される }

Where メソッドに与えて居る無名関数の I Mod 2 = 0 は、I が偶数の場合而巳 True が返る式なので、結 果的に偶数丈が抽出されると謂う訳で有る。 上記では、Aggregate で単一データにして居ないので、最後は ToArray メソッドで配列として取得する 必要が有る。 ■ クエリ式 因みに、此れをクエリ式と呼ばれる、SQL の様な LINQ の為の記法で書き表すと、下記の様に成る。 Visual Basic ' 奇数丈を抽出

Dim Odd = From Item In Arr Where Item Mod 2 = 1 For Each I In Odd

Messagebox.Show(I.ToString()) Next

C#

' 奇数丈を抽出

var odd = From item In arr Where item % 2 == 1 foreach(int i in even) { Messagebox.Show(i.ToString( )) } 今度は奇数を抜き出す処理にした。Function 等の装飾を全部省略する事が出来るので、記述が簡潔に 成る。 猶、クエリ式の戻り値は IEnumerable 型で、上記の例で居うと Odd は配列ではない。未だクエリ式自 体、実行されて居らず、関数の形の儘保持されて居る状態で有る。For Each 等で 1 個宛値を取り出す と、実際に実行されて結果を取得する事が出来る。猶、纏めて配列で取得したい場合は、Odd.ToArray() で取得可能で有る。 ■ クエリ式で Aggregate 序でなので、Aggregate をクエリ式で使用する場合も少し紹介する。 クエリ式で Aggregate を使用する場合は、基本的には、既に用意されて居る集計用の関数を用いる使い 方に成る。用意されて居るのは、数値型を合計する Sum や、数値型の最大数・最小数を求める Max、 Min、平均を求める Average 等で有る。 下記の様に使用する事が出来る。 Visual Basic

Dim Arr() As Integer = {1, 2, 3, 4, 5, 6, 7, 8, 9} ' 合計

Dim iSum As Integer = Aggregate Item In Arr Where Item > 7 Into Sum() Messagebox.Show("Sum = " + iSum.ToString()) ' Sum = 17

(8)

Dim iMax As Integer = Aggregate Item In Arr Where Item < 4 Into Max() Messagebox.Show("Max = " + iMax.ToString()) ' Max = 3

' 最少

Dim iMin As Integer = Aggregate Item In Arr Where Item < 4 Into Min() Messagebox.Show("Min = " + iMin.ToString()) ' Min = 1

' 平均

Dim dblAverage As Double = Aggregate Item In Arr Where Item > 5 Into Average() Messagebox.Show("Average = " + dblAverage.ToString()) ' Average = 3

C#

int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 合計

int iSum = (from item in arr where item > 7 select item).Sum(); MessageBox.Show("Sum = " + iSum.ToString());

// 最大

int iMax = (from item in arr where item < 4 select item).Max(); MessageBox.Show("Max = " + iMax.ToString());

// 最少

int iMin = (from item in arr where item < 4 select item).Min(); MessageBox.Show("Min = " + iMin.ToString());

// 平均

double dAve = (from item in arr where item > 5 select item).Average(); MessageBox.Show("Ave = " + dAve.ToString());

(9)

■ LINQ 詳細説明 ■ ■ 標準クエリー演算子とクエリー式 LINQ には SQL の様なクエリー式と通常のメソッド形式の標準クエリー演算子の 2 種の書き方が出来 るが、此処では、標準クエリー演算子の方で説明する。 ■ LINQ の主要メソッド データ処理に於ける主要な機能に下記の物が有る。 1.逐次処理(each) 2.写像(map) 3.フィルター(filter) 4.並び替え(sort) 5.畳み込み(fold)

此の内、一番基本的な処理は逐次処理だが、Visual Basic では For Each、C#では foreach で有り、LINQ 以前に言語機能として用意されて居るので、此処では他の 4 種の処理を紹介する。

■ Select:写像(map)

写像と謂うのは map や mapping と呼ばれる処理で、配列等のコレクションのメンバーに 1 個宛関数を 適用して戻り値で新しいコレクションを作る。

他言語では map と謂う名前に成って居る事が多いが、LINQ では Select で有る。 Visual Basic

Dim Src = {3, 2, 9, 6}

Dim Mapped = Src.Select(Function(Elem) Elem * 2) ' {6, 4, 18, 12} For Each I In Mapped

MessageBox.Show(I.ToString()) Next

C#

var src = new[] {3, 2, 9, 6};

var mapped = src.Select(elem => elem * 2); // {6, 4, 18, 12} foreach (var i in mapped)

{ MessageBox.Show(i.ToString()); } Select に渡す関数は要素の型を引数に取る関数で、其の戻り値が新しいコレクションの要素と成る。 Select は渡す関数の戻り値の型を変えれば、値を変える丈でなく、型を変えた新しいコレクションも作 る事が出来、用途の広いメソッドで有る。

(10)

渡す関数は、通常の関数でも良いし、匿名メソッドやラムダ式の様な無名関数でも構わない。

通常の関数等の場合には、渡す関数の型に合った物しか渡せないが、然う謂う場合にはカリー化と呼ば れる手法を使う事も出来る。

以降のサンプルでは、一番短く書けるラムダ式を使用する事にする。 ■ Where:フィルター(filter)

フィルターは他言語では、filter や find_all や select 等メソッド名が使われる。此れはコレクションの 中から条件に合う要素を取り出す処理で有る。

LINQ では Where と謂う名前に成って居る。 Visual Basic

Dim Src = {3, 2, 9, 6}

Dim Filtered = Src.Where(Function(Elem) Elem Mod 2 = 1) ' {3, 9} C#

var src = new[] {3, 2, 9, 6};

var filtered = src.Where(elem => elem % 2 == 1); // {3, 9}

データ処理で重要なメソッドを更に絞るとすると、前節のマップと此のフィルターが、特に重要度が高 い。

■ OrderBy, ThenBy:並び替え(sort)

並び替えは通常 sort と謂う名前のメソッドで、コレクションの要素の並び替えを行う。

他言語の sort では比較用のメソッドを渡す物なのだが、LINQ の OrderBy では比較に使う値を指定す る関数を渡す。OrderBy で指定した値で同じ値に成る場合には ThenBy で順列を付ける事が出来る。

Visual Basic

Dim PosSrc = {New With {.X = 1, .Y = 2}, New With {.X = 3, .Y = 4}, New With {.X = 1, .Y = 1}} Dim Sorted = PosSrc.OrderBy(Function(Elem) Elem.X).ThenBy(Function(Elem) Elem.Y) For Each i In Sorted

MessageBox.Show("X=" + i.X.ToString() + ", Y=" + i.Y.ToString()) Next Dim PosSrc = { New With {.X = 1, .Y = 2}, New With {.X = 3, .Y = 4}, New With {.X = 1, .Y = 1} }

(11)

For Each I In Sorted

MessageBox.Show("X=" + I.X.ToString() + ", Y=" + I.Y.ToString()) Next

C#

var possrc = new [] { new { x = 1, y = 2 }, new { x = 3, y = 4 }, new { x = 1, y = 1 } };

var sorted = possrc.OrderBy(elem => elem.x).ThenBy(elem => elem.y); // {{ x = 1, y = 1 }, { x = 1, y = 2 }, { x = 3, y = 4 }}

foreach (var i in sorted) {

MessageBox.Show("x=" + i.x.ToString() + ", y=" + i.y.ToString()); }

OrderBy や ThenBy は昇順で並び替えを行う。降順にしたい場合は夫々OrderByDescending と ThenByDescending を使う。

■ Aggregate:畳み込み(fold)

畳み込みは他言語では、fold や reduce や inject 等の名前だが、LINQ では Aggregate(集める)と謂 う名前で有る。

此れは他の処理に比べると、利用頻度は低い上に難易度も少し高いが、割りと応用も効く。此の処理は 先に使用例を見た方が解り易いだろう。

Visual Basic

Dim A As Integer = Src.Aggregate(Function(Sum, Elem) Sum + Elem) ' 20 Dim B As Integer = Src.Aggregate(Function(Max, Elem) IIf(Max < Elem, Elem, Max)) ' 9 Dim C As Integer = Src.Aggregate(0, Function(Count, Elem) Count + 1) ' 4 MessageBox.Show(A.ToString())

MessageBox.Show(B.ToString()) MessageBox.Show(C.ToString()) C#

var src = new[] {3, 2, 9, 6};

int a = src.Aggregate((sum, elem) => sum + elem); // 20 int b = src.Aggregate((max, elem) => (max < elem) ? elem : max); // 9 int c = src.Aggregate(0, (count, elem) => count + 1); // 4 MessageBox.Show(a.ToString()); MessageBox.Show(b.ToString()); MessageBox.Show(c.ToString()); 渡された関数を各メンバーに適用して行き、其の結果を重ねた物が Aggregate の戻り値として得らる。 渡す関数は 2 個の引数を取り、1 個目が其れ迄の計算の結果で、2 個目が各要素で有る。関数の戻り値 として返した物が、次の 1 個目の引数に入ると謂う繰り返しに成る。合計の例を順に記述すると下記の 様に成る。

(12)

1 番最初は 1 個目の引数が最初の要素で、2 番目の要素から始める事に成る。亦、count の例の様に初 期値を与え、最初の要素から始める事も出来る。

此の様に畳み込みは要素を順に取得し、新しい値を返す処理で有る。

此処で挙げた例は実は孰れ Sum や Max や Count として既に LINQ のメソッドは用意されて居る。頻 繁に使われるからこそ用意されて居るので有り、広く応用出来るのが解ると思う。 ■ 其他のメソット データ処理の定番のメソッド以外で、LINQ のメソッドとして知って置いた方が良いと思われる物も挙 げて置く。 Count 要素数(サイズ)の取得。 Visual Basic Dim Src = {3, 2, 9, 6}

Dim N As Integer = Src.Count() MessageBox.Show(N.ToString()) C# var src = new[] {3, 2, 9, 6}; int n = src.Count(); // 4 MessageBox.Show(n.ToString()); Take 指定した要素数の抽出。 Visual Basic Dim Src = {3, 2, 9, 6} Dim Taken = Src.Take(2) For Each I In Taken

MessageBox.Show(I.ToString()) Next

(13)

C#

var src = new[] {3, 2, 9, 6}; var taken = src.Take(2); foreach (var i in taken) { MessageBox.Show(i.ToString()); } First、Last 指定した条件に最初(最後)にマッチする要素の検索。 検索もデータ処理では良く行う処理だが、条件に合う総ての要素を取得するのがフィルター(Where) で、最初の要素を取得するのが First で有る。 Visual Basic Dim Src = {3, 2, 9, 6}

Dim A As Integer = Src.First(Function(Elem) Elem Mod 2 = 1) ' 3 Dim B As Integer = Src.Last(Function(Elem) Elem Mod 2 = 1) ' 9 MessageBox.Show(A.ToString())

MessageBox.Show(B.ToString()) C#

var src = new[] {3, 2, 9, 6};

int a = src.First(elem => elem % 2 == 1); // 3 int b = src.Last(elem => elem % 2 == 1); // 9 MessageBox.Show(a.ToString()); MessageBox.Show(b.ToString()); Max、Min 要素の最大(最小)値の取得。 Visual Basic Dim Src = {3, 2, 9, 6}

Dim A As Integer = Src.Max() ' 9 Dim B As Integer = Src.Min() ' 2 MessageBox.Show(A.ToString()) MessageBox.Show(B.ToString()) C# var src = new[] {3, 2, 9, 6}; int a = src.Max(); // 9 int b = src.Min(); // 2 MessageBox.Show(a.ToString()); MessageBox.Show(b.ToString()); Contains 要素を含んで居るかの判定。 Visual Basic Dim Src = {3, 2, 9, 6}

Dim A As Boolean = Src.Contains(9) ' True Dim B As Boolean = Src.Contains(1) ' False MessageBox.Show(A.ToString())

(14)

C#

var src = new[] {3, 2, 9, 6}; bool a = src.Contains(9); // true bool b = src.Contains(1); // false MessageBox.Show(a.ToString()); MessageBox.Show(b.ToString()); All、Any 要素総て(孰れか 1 個)が条件を満たすか何うかの判定。 Visual Basic Dim Src = {3, 2, 9, 6}

Dim A As Boolean = Src.All(Function(Elem) Elem Mod 3 = 0) ' False Dim B As Boolean = Src.Any(Function(Elem) Elem Mod 3 = 0) ' True MessageBox.Show(A.ToString())

MessageBox.Show(B.ToString()) C#

var src = new[] {3, 2, 9, 6};

bool a = src.All(elem => elem % 3 == 0); // False bool b = src.Any(elem => elem % 3 == 0); // True MessageBox.Show(a.ToString()); MessageBox.Show(b.ToString()); Distinct 重複する要素の除去(2 個目以降が無く成る)。 Visual Basic Dim Src = {3, 3, 2, 9, 2, 6, 2};

Dim Distincted = Src.Distinct() ' {3, 2, 9, 6} For Each I In Distincted

MessageBox.Show(I.ToString()) Next

C#

var src = new[] {3, 3, 2, 9, 2, 6, 2};

var distincted = src.Distinct(); // {3, 2, 9, 6} foreach (var i in distincted)

{

MessageBox.Show(i.ToString()); }

(15)

■ 遅延評価 LINQ の重要な要素に遅延評価と謂う物が有る。 メソッドの連結と遅延評価 LINQ のメソッドの使える対象は、型で言うと IEnumerable<T> インターフェースを継承したクラス で有る。インターフェースだが、此れに拡張メソッドと謂う機能を使って、IEnumerable<T> がメソ ッドを持てる様に成って居る。 ・Enumerable クラス(System.Linq) IEnumerable<T> を継承した物と謂うのも長いので、此処ではシーケンスと呼ぶ事にする。 先程の Select や Where は新しいコレクションを返す。此の戻り値がシーケンスで有る。正確な型はコ ンパイラーが生成する複雑な物なので、変数に入れたい場合等は、Visual C#の場合は var、Visual Basic では変数の宣言でデータ型を指定しないデータ型を使った型推論が必要と成る。

Visual Basic

Console.WriteLine("{0}", src.Select(Function(Elem) Elem * 2))

' => System.Linq.Enumerable+WhereSelectArrayIterator`2[System.Int32,System.Int32] C#

Console.WriteLine("{0}", src.Select(elem => elem * 2));

// => System.Linq.Enumerable+WhereSelectArrayIterator`2[System.Int32,System.Int32] シーケンスを返すメソッドは続けて書く事が出来る。

Visual Basic

Dim Src = {3, 2, 9, 6}

Dim Seq = Src.Where(Function(Elem) Elem Mod 2 = 1).Select(Function(Elem) Elem * 2) For Each Elem in Seq

Console.Write("{0} ", Elem); Next

Console.WriteLine(); ' => 6 18 C#

var src = new[] {3, 2, 9, 6};

var seq = src.Where(elem => elem % 2 == 1).Select(elem => elem * 2); foreach (var elem in seq)

{ Console.Write("{0} ", elem); } Console.WriteLine(); // => 6 18 此のシーケンスには必要に成る迄実行されないと謂う遅延評価の機能が有る。上記の例では foreach で 1 個宛取り出す時が実行のタイミングで有る。 連結の処理は一見、下記の様に処理してる様に見える。 Where Select {3, 2, 9, 6} → {3, 9} → {6, 18} 併し、実際には遅延評価に依り、メソッド毎に結果を貯めるのではなく、1 要素宛流す様に次のメソッ ドに渡して行く。

(16)

Where Select 3 → 3 → 6 2 → ? 9 → 9 → 18 6 → ? 実行するのは必要な時で有る。其の為、ソート(OrderBy)の様な全要素が揃わないと完了しない様な メソッドの場合は、其の時点で要素は溜まる事に成る。 遅延評価のメリット 遅延評価は関数型プログラミングでは並列性の為に欠かせない機能で有る。関数型の話を抜きにしても、 遅延評価には大きなメリットが有る。其れは処理結果を溜めない為、使用メモリーを減らせる点で有る。 例えば、File クラスには、指定したファイルの全行を文字列の配列として取得する ReadAllLines と謂 うメソッドが有る。 此れはループを回して1行宛取り出す必要がなく便利なのだが、ログファイルの様に大きなファイルに 対して使用すると大量のメモリーを使用して了う事に成る。 其処で ReadAllLines のシーケンス版で有る ReadLines を使えば、一行宛渡してくれる様に成る。 Visual Basic

For Each Line As String In File.ReadLines(Fpath) Console.WriteLine(Line)

Next C#

foreach (string line in File.ReadLines(fpath)) { Console.WriteLine(line); }

一行宛取りたいなら普通にループ回して一行宛読み取れば良いのではと思うかも知れないが、シーケン スを返して居るので、其れに対して LINQ の豊富なメソッドを使えると謂う利点が有る。

Visual Basic

Public Shared Function ReadValues(Fpath As String) As IEnumerable(Of Integer) Dim Ptn = New Regex(@"¥d+")

Return File.ReadLines(Fpath).

Select(Function(Line) Ptn.Match(Line).Value).

Where(Function(Str) Not String.IsNullOrEmpty(Str)). Select(Function(Str) Integer.Parse(Str))

End Function ' 使用

For Each Val As Integer In ReadValues(Fpath) Console.WriteLine("{0}", Val)

Next C#

public static IEnumerable<int> ReadValues(string fpath) {

var ptn = new Regex(@"¥d+"); return File.ReadLines(fpath)

.Select(line => ptn.Match(line).Value) // マッチした数字か空文字 .Where(str => !String.IsNullOrEmpty(str)) // 空文字を抜く

.Select(str => int.Parse(str)); // 数値に変換 }

(17)

// 使用

foreach (int val in ReadValues(fpath)) { Console.WriteLine("{0}", val); } テストに使用したテキストファイル 67 28 # 鉄人 999 # 銀河鉄道 009 # サイボーグ

~/cs/LINQ $ ./squid.exe data.txt 67 28 999 9 猶、foreach の処で実行すると謂う事は、ptn の正規表現オブジェクトはローカル変数なので、最早存 在して居ないのではないかと思うかも知れないが、其処はクロージャーと謂う技術に依り、無名関数に 使うオブジェクトは残す様に成って居る。 ■ コンテナー以外への LINQ の適用 LINQ はコンテナー以外でも、先程のファイルの読み取りやデータベース、XML 等色々な物に使える。 寧ろ、LINQ は元々データベースや XML の処理を簡便化する事をメインに導入された物であろう。 此処からは、コンテナー以外への LINQ の適用例を紹介する。只、データベースや XML だとサンプル として大きく成るので、簡単な例にする。 フォルダーを走査して、特定の条件に有るファイルのリストを取得したいと謂う処理にも LINQ を使用 して処理する事が出来る。 Visual Basic ' DirPath 以下のファイルの中で Str の文字列を含むファイル名のファイル群を返す Public Shared Function SearchDir(ByVal DirPath As String, ByVal Str As String) _ As IEnumerable(Of String)

' 指定フォルダーのファイルをサブフォルダー迄列挙 Dim Di As DirectoryInfo = New DirectoryInfo(DirPath) Dim FiList As IEnumerable(Of System.IO.FileInfo) = _ Di.GetFiles("*.*", SearchOption.AllDirectories) Return FiList. Where(Function(Fi) (0 <= _ Fi.Name.IndexOf(Str, StringComparison.CurrentCultureIgnoreCase))). Select(Function(Fi) Fi.FullName) End Function ' 使用

For Each Path As String In SearchDir(DirPath, Str)) MessageBox.Show(Path);

(18)

C#

// dirPath 以下のファイルの中で str の文字列を含むファイル名のファイル群を返す public static IEnumerable<string> SearchDir(string dirPath, string str)

{

// 指定フォルダーのファイルをサブフォルダー迄列挙 DirectoryInfo di = new DirectoryInfo(dirPath);

IEnumerable<System.IO.FileInfo> fiList = di.GetFiles("*.*", SearchOption.AllDirectories); return fiList .Where(fi => // str を含むかでフィルター (0 <= fi.Name.IndexOf(str, StringComparison.CurrentCultureIgnoreCase))) .Select(fi => fi.FullName); // フルパスに変換 } // 使用

foreach (string path in SearchDir(dirPath, str)) { MessageBox.Show(path); } d:¥home¥cs¥LINQ¥BinFileIo.cs d:¥home¥cs¥LINQ¥CatFile.cs d:¥home¥cs¥LINQ¥FindFile.cs d:¥home¥cs¥LINQ¥LazySample.cs d:¥home¥cs¥LINQ¥StdQueryOperators.cs d:¥home¥cs¥LINQ¥test.cs ■ シーケンスの自作 前項で LINQ には色々なデータの物が用意されて居ると書いたが、当然、総てのケースが用意されて居 る訳ではない。併し、其の様な場合でも、自分でシーケンスを返す関数を作る事が出来る。 File クラスの ReadLines はテキストファイル用の物だが、サンプルとして自身で構成を定義したバイ ナリーファイル版 ReadLines を作る事にする。 バイナリーファイルの構成は、ヘッダーとして要素数が記述され、其の後に整数値が連続して居ると謂 うシンプルな物にする。 要素 バイト数 格納値(例) ヘッダー 4 bytes 4 1 st 4 bytes 3 2 nd 4 bytes 2 3 rd 4 bytes 9 4 th 4 bytes 6

シーケンスを返す関数は IEnumerable<T> を戻り値として、yield return で値を返す様にする。返す 値は IEnumerable<T>の T の型で有る(例では int)。

Visual Basic

' バイナリー版 ReadLines

Public Shared Function ReadData(Fpath As String) As IEnumerable(Of Integer) Using Br As BinaryReader = New BinaryReader(File.OpenRead(Fpath)))

(19)

' 先頭は要素数

Dim Siz As Integer = Br.ReadInt32(); ' 要素数分、読み取った値を返す For Cnt As Integer = 0 To (Siz - 1) Yield Return Br.ReadInt32() Next

End Using End Function C#

// バイナリー版 ReadLines

public static IEnumerable<int> ReadData(string fpath) {

using(BinaryReader br = new BinaryReader(File.OpenRead(fpath))) {

// 先頭は要素数

int siz = br.ReadInt32();

// 要素数分、読み取った値を返す for (int cnt = 0 ; cnt < siz ; cnt++) {

yield return br.ReadInt32(); }

} }

関数の戻り値がシーケンスなので、ReadLines と同じ様に使う事が出来る。 Visual Basic

For Each It As Integer In ReadData(TestFile)) MessageBox.Show(It.ToString())

Next C#

foreach (int it in ReadData(testfile)) { MessageBox.Show(it.ToString()); } テストに使用したテキストファイル ~/cs/LINQ $ ./BinFileIo.exe 3 2 9 6

参照

関連したドキュメント

奥付の記載が西暦の場合にも、一貫性を考えて、 []付きで元号を付した。また、奥付等の数

奥付の記載が西暦の場合にも、一貫性を考えて、 []付きで元号を付した。また、奥付等の数

しかし , 特性関数 を使った証明には複素解析や Fourier 解析の知識が多少必要となってくるため , ここではより初等的な道 具のみで証明を実行できる Stein の方法

本論文での分析は、叙述関係の Subject であれば、 Predicate に対して分配される ことが可能というものである。そして o

の繰返しになるのでここでは省略する︒ 列記されている

いてもらう権利﹂に関するものである︒また︑多数意見は本件の争点を歪曲した︒というのは︑第一に︑多数意見は

 本資料は、宮城県に所在する税関官署で輸出通関又は輸入通関された貨物を、品目別・地域(国)別に、数量・金額等を集計して作成したもので

本資料は、宮城県に所在する税関官署で輸出通関又は輸入通関された貨物を、品目別・地域(国)別に、数量・金額等を集計して作成したもので