■ 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
L
I
I
N
N
Q
Q
概
概
論
論
// 結果を返す 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 で同じ処理を記述
Result = (From Ch In Src.ToCharArray() Where 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 = (from ch in src.ToCharArray() where Char.IsDigit(ch) select ch) .Aggregate(String.Empty, (r, ch) => r + ch); ' 結果を返す return result; } 上記では、括弧内に記述したクエリ式に対して Aggregate メソッドを適用して居り、完全なクエリ式と は謂えないコードに成って居る。此れは、Aggregate 句を使用して居る事に起因する。 クエリ式で Aggregate を使用する方法に付いては後述するが、基本的には、下記の構文の様に、既に用 意されて居る集計用の関数を用いる使い方に成る。而も、C#では、Aggregate 句はクエリキーワードの 中に存在せず、クエリ式に使えない様で有る。
Visual Basic Aggregate 反復変数 [ As データ型 ] In データソース Into 集計関数 C# 不明 亦、VB でも、予め用意されて居る集計用の関数(All、Any、Average、Count、First、FirstOrDefault、 Last、LastOrDefault、LongCount、Max、Min、Sum)以外の処理を行う場合、関数を自作する必要 が有る(後述)。従って、此処では、Aggregate に関しては標準クエリー演算子、其の他に関してはク エリ式を適用する折衷表記を採る事にする。 下記の様に、区切り部分で改行を入れると少し見易く成るかも知れない。 Visual Basic
Private Function RemoveNonNumericChars(Src As String) As String ' 空の文字列の宣言
Dim Result As String = String.Empty
' LINQ で同じ処理を記述
' 引数の文字列を 1 文字宛チェック
Result = (From Ch In Src.ToCharArray() Where 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 = (from ch in src.ToCharArray() where Char.IsDigit(ch) select 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'} 此の戻り値に対して、次の From 句を適用して居る。 Visual Basic From Ch In Src.ToCharArray() C# from ch in src.ToCharArray() コレクション(此処では Char 型の配列)の要素を 1 個宛取り出して反復変数(此処では Ch)に代入 して居る。 此の反復変数に対して、次の Where 句を適用して居る。 Visual Basic Where Char.IsDigit(Ch) C# where Char.IsDigit(ch) Where では、コレクションや配列を、指定した条件で絞り込む事が出来る。引数として与えるのは、条 件判定文で有る。配列の各要素(此処では 1 文字分のデータ)を引数として受け取り、其れが条件に合 う文字ならば True を、合わない文字ならば False を返すと謂う文が、今回の条件判定文に成る。 具体的には、下記の部分が条件判定文で有る。 Visual Basic Char.IsDigit(Ch) C# Char.IsDigit(ch)
Ch は Char 型の引数で有る。今回処理するのが Char 型の配列なので、条件判定文の引数も Char 型に 成る。其の Ch を、Char.IsDigit(Char) を使用して、数字か何うかを判定して居り、戻り値は True か False に成る。猶、論理演算子を用いて、複文にしても勿論構わない。 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
次は、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 に格納して行く。 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 に成る訳で有る。 此処では、敢えてクエリ式を用いて記述したが、此の例の場合は、メソッドベースで記述した方が簡潔 に成る。亦、Aggregate を使用しないで、下記の様に記述する事も出来る。 Visual Basic Dim Str = "tel:0120-444-444"
Dim Res = From Ch In Str.ToCharArray( ) Where Char.IsDigit(Ch) Select Ch Dim S As String = ""
For Each Ch As Char In Res S &= Ch
Next
MessageBox.Show("NumericStr = " + S) C#
■ 数値配列を Where で絞り込む
配列データを Where メソッドで絞り込む例を、Integer 型の配列を使用して、試して見る事にする。 偶数丈を抜き出す LINQ のコードを、下記に示す。
Visual Basic
' データソースの作成
Dim Arr() As Integer = {1, 2, 3, 4, 5, 6, 7, 8, 9} ' 偶数丈を抽出
Dim Even = From Item In Arr Where Item Mod 2 = 0 ' 結果を表示
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, 10 }; // 偶数丈を抽出
var even = from item in arr where item % 2 == 0 select item; // 結果を表示
foreach(int i in even) {
MessageBox.Show(i.ToString( )); // 2 4 6 8 が出力される }
Where 句に与えて居る条件判定文の I Mod 2 = 0 は、I が偶数の場合而巳 True が返る式なので、結果的 に偶数丈が抽出されると謂う訳で有る。 上記では、Aggregate で単一データにして居ないので、結果を観るにはループを廻す必要が有る。 ■ メソッドベース 因みに、此れをメソッド形式と呼ばれる、標準クエリー演算子を用いた記法で書き表すと、下記の様に 成る。 Visual Basic ' 奇数丈を抽出
Dim Odd = Arr.Where(Function(Item) Item Mod 2 = 1) For Each I In Odd
Messagebox.Show(I.ToString()) Next
C#
' 奇数丈を抽出
var odd = arr.Where(item => item % 2 == 1); foreach(int i in odd)
{
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
' 最大
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());
実際には、配列自体に Sum や Max、Average 等のメソッドが有るので、出番は無いかも知れない。 猶、上記の予め用意された関数ではなく、Aggregate で呼び出し、独自の処理を行う集計関数の例を下 記に示す。 関数は、拡張関数と成るので、モデュールを追加して、其処に記述する事に成る。 Visual Basic ' モデュール部分 Imports System.Runtime.CompilerServices Imports System.Linq Module Module1 <Extension()>
Public Function AddChar(Of T)(ByVal Values As IEnumerable(Of T)) As String Dim S = ""
For Each Ch In Values
Dim C As Char = System.Convert.ToChar(Ch) If Char.IsDigit(C) Then S &= C End If Next Return S End Function End Module ' フォーム部分
Private Sub btnExec2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnExec2.Click
Dim Str = "tel:0120-444-444"
Dim NumericStr = RemoveNonNumericChars(Str)
MessageBox.Show("NumericStr = " + NumericStr) End Sub
Private Function RemoveNonNumericChars(ByVal Src As String) As String ' 空の文字列の宣言
Dim Result As String = String.Empty
Result = Aggregate ch In Src.ToArray() Into AddChar()
' 結果を返す Return Result End Function
■ 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 = From Elem In Src Select 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 = from elem in src select elem * 2; // {6, 4, 18, 12} foreach (var i in mapped)
{ MessageBox.Show(i.ToString()); } Select が施す処理は要素の型を引数に取る処理で、其の処理後の値が新しいコレクションの要素と成る。 Select は施す処理の処理後の値の型を変えれば、値を変える丈でなく、型を変えた新しいコレクション も作る事が出来、用途の広い処理で有る。
■ Where:フィルター(filter)
フィルターは他言語では、filter や find_all や select 等の名称が使われる。此れはコレクションの中か ら条件に合う要素を取り出す処理で有る。
LINQ では Where と謂う名前に成って居る。 Visual Basic
Dim Src = {3, 2, 9, 6}
Dim Filtered = From Elem In Src Where Elem Mod 2 = 1 Select Elem ' {3, 9} C#
var src = new[] {3, 2, 9, 6};
var filtered = from elem in src where elem % 2 == 1 select elem; // {3, 9}
データ処理で重要な処理を更に絞るとすると、前節のマップと此のフィルターが、特に重要度が高い。 ■ OrderBy:並び替え(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 = From Elem In PosSrc Order By Elem.X, Elem.Y ' {{ x = 1, y = 1 }, { x = 1, y = 2 }, { x = 3, y = 4 }}
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 = from elem in possrc orderby elem.x, elem.y select elem; // {{ 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()); }
Order By や orderby は昇順で並び替えを行う。降順にしたい場合は、Descending や descending を項 目の後ろに付ける(因みに、昇順は Ascending や ascending だが、既定なので省略する事が多い)。 ■ Aggregate:畳み込み(fold)
畳み込みは他言語では、fold や reduce や inject 等の名前だが、LINQ では Aggregate(集める)と謂 う名前で有る。
此れは他の処理に比べると、利用頻度は低い上に難易度も少し高いが、割りと応用も効く。此の処理は 先に使用例を見た方が解り易いだろう。
Visual Basic
Dim Src = {3, 2, 9, 6}
Dim A As Integer = Aggregate Elem In Src Into Sum() ' 20 Dim B As Integer = Aggregate Elem In Src Into Max() ' 9 Dim C As Integer = Aggregate Elem In Src Into Count() ' 4 MessageBox.Show(A.ToString())
MessageBox.Show(B.ToString()) MessageBox.Show(C.ToString()) C#
var src = new[] { 3, 2, 9, 6 };
int a = (from elem in src select elem).Sum(); // 20 int b = (from elem in src select elem).Max(); // 9 int c = (from elem in src select elem).Count(); // 4 MessageBox.Show(a.ToString()); MessageBox.Show(b.ToString()); MessageBox.Show(c.ToString()); 渡された関数を各メンバーに適用して行き、其の結果を重ねた物が Aggregate の戻り値として得らる。 LINQ は、元々はシーケンス(IEnumerable 実装クラス)やデータベーステーブルに対するメソッ ド群として而巳提供される予定だったそうで、from x in list の様なクエリ式を導入する予定は無か ったと謂う事で有る。 併し、メソッド丈では、join や let 等の記述に難が有り、止む無く SQL 風のクエリ式を導入したそ うで有る。 斯うした背景から、標準クエリ演算子と呼ばれるメソッド群に対して、クエリ式の形で書ける物は 少数で有る(クエリ式で書けない物の方が多数)。 特に、C#では、クエリ式に制約が多く、Aggregate 句に至っては、クエリ式の構文も存在しない様 で有る。従って、此処ではクエリ式とメソッドベースを折衷した記述を採った。
渡す関数は 2 個の引数を取り、1 個目が其れ迄の計算の結果で、2 個目が各要素で有る。関数の戻り値 として返した物が、次の 1 個目の引数に入ると謂う繰り返しに成る。合計の例を順に記述すると下記の 様に成る。 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
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())
MessageBox.Show(B.ToString()) 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()); }
■ 遅延評価 LINQ の重要な要素に遅延評価と謂う物が有る。 メソッドの連結と遅延評価 LINQ のメソッドの使える対象は、型で言うと IEnumerable<T> インターフェースを継承したクラス で有る。インターフェースだが、此れに拡張メソッドと謂う機能を使って、IEnumerable<T> がメソ ッドを持てる様に成って居る。 ・Enumerable クラス(System.Linq) IEnumerable<T> を継承した物と謂うのも長いので、此処ではシーケンスと呼ぶ事にする。 先程の Select や Where は新しいコレクションを返す。此の戻り値がシーケンスで有る。正確な型はコ ンパイラーが生成する複雑な物なので、変数に入れたい場合等は、Visual C#の場合は var、Visual Basic では変数の宣言でデータ型を指定しないデータ型を使った型推論が必要と成る。
Visual Basic
Dim Src = {3, 2, 9, 6}
Dim Res = From Elem In Src Select Elem * 2 MessageBox.Show(Res.ToString())
' => System.Linq.Enumerable+WhereSelectArrayIterator`2[System.Int32,System.Int32] C#
var src = new[] { 3, 2, 9, 6 };
var res = from elem in src select elem * 2; MessageBox.Show(res.ToString());
// => System.Linq.Enumerable+WhereSelectArrayIterator`2[System.Int32,System.Int32] シーケンスを返すメソッドは続けて書く事が出来る。
Visual Basic
Dim Src = {3, 2, 9, 6}
Dim Res = From Elem In Src Where Elem Mod 2 = 1 Select Elem * 2 For Each Elem In Res
MessageBox.Show(Elem.ToString()) Next
' => 6 18 C#
var src = new[] { 3, 2, 9, 6 };
var res = from elem in src where elem % 2 == 1 select elem * 2; foreach (var elem in res)
{ MessageBox.Show(elem.ToString()); } // => 6 18 此のシーケンスには必要に成る迄実行されないと謂う遅延評価の機能が有る。上記の例では foreach で 1 個宛取り出す時が実行のタイミングで有る。
連結の処理は一見、下記の様に処理してる様に見える。 Where Select {3, 2, 9, 6} → {3, 9} → {6, 18} 併し、実際には遅延評価に依り、メソッド毎に結果を貯めるのではなく、1 要素宛流す様に次のメソッ ドに渡して行く。 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) MessageBox.Show(Line)
Next C#
foreach (string line in File.ReadLines(fpath)){ MessageBox.Show (line);
}
一行宛取りたいなら普通にループ回して一行宛読み取れば良いのではと思うかも知れないが、シーケン スを返して居るので、其れに対して LINQ の豊富なメソッドを使えると謂う利点が有る。
Visual Basic
Public Shared Function ReadValues(ByVal Fpath As String) As IEnumerable(Of Integer) Dim Ptn = New Regex("¥d+")
Dim Ret = From Line In File.ReadLines(Fpath) Let Str = Ptn.Match(Line).Value Select Str
Where String.IsNullOrEmpty(Str) = False Select Integer.Parse(Str)
Return Ret End Function
' 使用例
Dim Fpath = Application.StartupPath
If Not Fpath.EndsWith("¥") Then Fpath &= "¥" Fpath &= "data.txt"
For Each Val As Integer In ReadValues(Fpath) MessageBox.Show(Val.ToString())
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)); // 数値に変換 } // 使用例
foreach (int val in ReadValues(fpath)) { MessageBox.Show(val.ToString()); } テストに使用したテキストファイル 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)
Dim Ret = From Fi In FiList
Where 0 <= Fi.Name.IndexOf(Str, StringComparison.CurrentCultureIgnoreCase) Select Fi.FullName
Return Ret End Function ' 使用例
For Each Path As String In SearchDir(DirPath, Str)) MessageBox.Show(Path);
Next 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))) ' 先頭は要素数
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()); }