List<T>クラスのメソッドあるいはLINQ拡張メソッドを利用して、条件に合致する要素をリストから検索する方法を紹介する。
ジェネリックコレクションで最も頻繁に使われるのはList<T>(C#)/List(Of T)(VB)クラス(System.Collections.Generic名前空間)であろう(以降、型引数はC#での表記だけとさせていただく)。本稿では、そのコレクションに含まれる要素を検索する方法を解説する。
なお、List<T>クラスは.NET Framework 2.0で導入されたものだが、本稿はそれ以降の内容も含んでいる。サンプルコードをそのまま試すには、Visual Studio 2015(またはそれ以降)が必要である。
通常はLINQ拡張を使えばよい。ただし、要素数が多く、かつ、要素が昇順に並んでいるときに特定の要素を検索する場合は、List<T>クラスのBinarySearchメソッドが高速だ。
List<T>クラスは.NET Framework 2.0で導入された。LINQは.NET Framework 3.5からである。そのため、List<T>クラスは検索のためのメソッドを独自に持っているのだが、ほとんどは後のLINQ拡張でも同じことが可能なのである。従って、統一的にコードを書くという観点からは、LINQ拡張を使って検索するのがよいだろう。ただし、LINQ拡張ではできないこともある。代表的なものはBinarySearchメソッドである(最後に解説する)。
実際にList<T>コレクションで検索する例を確認してみよう。
まずは、条件を満たす要素の全てを新しいコレクションに抽出する例だ。これには、LINQのWhere拡張メソッドを使う。List<T>クラスのFindAllメソッドを使ってもよい。次のコードに、コンソールアプリの例を示す。
using System.Collections.Generic;
using System.Linq;
using static System.Console;
class Program
{
// コレクションの全要素を出力するメソッド
static void DisplayItems<T>(IEnumerable<T> collection)
=> WriteLine($"{string.Join(", ", collection)}");
static void Main(string[] args)
{
List<int> list = new List<int> {1,2,3,4,5,6,7,8,9,10,};
// 偶数を検索して、見つかったものだけのコレクションを作る
// 【1】LINQのWhere拡張メソッド
IEnumerable<int> result1 = list.Where(n => n % 2 == 0);
DisplayItems(result1);
// 出力:2, 4, 6, 8, 10
// 【2】List<T>クラスのFindAllメソッド
List<int> result2 = list.FindAll(n => n % 2 == 0);
DisplayItems(result2);
// 出力:2, 4, 6, 8, 10
#if DEBUG
ReadKey();
#endif
}
}
Imports System.Console
Module Module1
' コレクションの全要素を出力するメソッド
Sub DisplayItems(Of T)(collection As IEnumerable(Of T))
WriteLine($"{String.Join(", ", collection)}")
End Sub
Sub Main()
Dim list As List(Of Integer) _
= New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
' 偶数を検索して、見つかったものだけのコレクションを作る
' 【1】LINQのWhere拡張メソッド
Dim result1 As IEnumerable(Of Integer) _
= list.Where(Function(n) n Mod 2 = 0)
DisplayItems(result1)
' 出力:2, 4, 6, 8, 10
' 【2】List<T>クラスのFindAllメソッド
Dim result2 As List(Of Integer) = list.FindAll(Function(n) n Mod 2 = 0)
DisplayItems(result2)
' 出力:2, 4, 6, 8, 10
#If DEBUG Then
ReadKey()
#End If
End Sub
End Module
上のコードで注意したいのは、検索結果の型が違うことだ。LINQのWhere拡張メソッドは遅延実行され、IEnumerable<T>型を返す。List<T>クラスのFindAllメソッドは即時実行され、List<T>型を返す。即時実行(List<T>クラス)の方が多くのメモリを消費する。また、検索結果の全部を使わなかった場合には(例えば、後続のforeach(C#)/For Each(VB)ループ内で処理を途中で打ち切る場合など)、遅延実行(LINQ)は打ち切り後の無駄な検索は実行しない(即時実行では、必ず最後まで検索を実行してしまう)。
つまり、メモリ効率/処理効率の点から、検索結果のコレクションを得るにはLINQの方が優れているのである(後続の処理がforeach(C#)/For Each(VB)ループの場合)。
条件を満たす要素のうちで最初に見つかったものだけがほしい場合は、LINQのFirstOrDefault拡張メソッドを使う。List<T>クラスのFindメソッドを使ってもよい(次のコード)。なお、見つからなかった場合は、どちらも型の既定値が返る(整数では0)。
List<int> list = new List<int> {1,2,3,4,5,6,7,8,9,10,};
// 偶数を検索して、最初に見つかったものだけを得る
// 【1】LINQのFirstOrDefault拡張メソッド
int result1 = list.FirstOrDefault(n => n % 2 == 0);
WriteLine(result1);
// 出力:2
// 【2】List<T>クラスのFindメソッド
int result2 = list.Find(n => n % 2 == 0);
WriteLine(result2);
// 出力:2
Dim list As List(Of Integer) _
= New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
' 偶数を検索して、最初に見つかったものだけを得る
' 【1】LINQのFirstOrDefault拡張メソッド
Dim result1 As Integer = list.FirstOrDefault(Function(n) n Mod 2 = 0)
WriteLine(result1)
' 出力:2
' 【2】List<T>クラスのFindメソッド
Dim result2 As Integer = list.Find(Function(n) n Mod 2 = 0)
WriteLine(result2)
' 出力:2
条件を満たす要素の有無だけを知りたい場合は、LINQのAny拡張メソッドを使う。List<T>クラスのExistsメソッドを使ってもよい(次のコード)。
List<int> list = new List<int> {1,2,3,4,5,6,7,8,9,10,};
// 偶数が含まれているかどうかを調べる
// 【1】LINQのAny拡張メソッド
bool result1 = list.Any(n => n % 2 == 0);
WriteLine(result1);
// 出力:True
// 【2】List<T>クラスのExistsメソッド
bool result2 = list.Exists(n => n % 2 == 0);
WriteLine(result2);
// 出力:True
Dim list As List(Of Integer) _
= New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
' 偶数が含まれているかどうかを調べる
' 【1】LINQのAny拡張メソッド
Dim result1 As Boolean = list.Any(Function(n) n Mod 2 = 0)
WriteLine(result1)
' 出力:True
' 【2】List<T>クラスのExistsメソッド
Dim result2 As Boolean = list.Exists(Function(n) n Mod 2 = 0)
WriteLine(result2)
' 出力:True
特定の要素の有無だけを知りたい場合は、Containsメソッドを使う(次のコード)。
ただし、Containsという名前のメソッドはLINQ拡張にもList<T>クラスにもある。引数が1つだけのものは、C#では型引数の有無で区別される(他に、LINQ拡張のContainsメソッドには引数が2つのものもある)。VBでは区別できないので、LINQ拡張のContainsメソッドを明示的に呼び出すには次のコードのようにEnumerableクラスの静的メソッドとして呼び出さねばならない。だが、実用上は、LINQ拡張のContainsメソッドを使っているつもりで「list.Contains(……)」と書いてよい(次のコードの解説を参照)。
List<int> list = new List<int> {1,2,3,4,5,6,7,8,9,10,};
// 特定の要素「5」が含まれているかどうか調べる
// 【1】LINQのContains<T>拡張メソッド
bool result1 = list.Contains<int>(5);
WriteLine(result1);
// 出力:True
// 【2】List<T>クラスのContainsメソッド
bool result2 = list.Contains(5);
WriteLine(result2);
// 出力:True
Dim list As List(Of Integer) _
= New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
' 特定の要素「5」が含まれているかどうか調べる
' 【1】LINQのContains<T>拡張メソッド
Dim result1 As Boolean = Enumerable.Contains(list, 5)
WriteLine(result1)
' 出力:True
' 【2】List<T>クラスのContainsメソッド
Dim result2 As Boolean = list.Contains(5)
WriteLine(result2)
' 出力:True
List<T>クラスにはBinarySearchメソッドがある(LINQ拡張にはない)。これは要素数が多いときに高速に検索できるメソッドだ。List<T>クラスのFindメソッドやLINQのFirstOrDefault拡張メソッドの計算量はO(n)なのに対して、BinarySearchメソッドではO(log n)である。ただし、あらかじめ要素が昇順に並んでいなければならないので、使いどころが難しい(1回の検索のためにソートしていたのではかえって時間がかかってしまう可能性が高い)。
次のコードに、要素が昇順に並んでいてうまく検索できる例と、昇順に並んでいないために検索に失敗する例を示す。
// 特定の要素「5」を検索する
List<int> list = new List<int> {1,2,3,4,5,6,7,8,9,10,};
// 「list」は昇順に並んでいるので、BinarySearchメソッドが使える
int index = list.BinarySearch(5);
WriteLine($"index={index}, value={list[index]}");
// 出力:index=4, value=5
List<int> list2 = new List<int> { 1, 2, 3, 4, 9, 6, 7, 8, 5, 10, };
// 「list2」は昇順に並んでいないため、正しい結果が得られない
int index2 = list2.BinarySearch(5);
WriteLine($"index2={index2}");
// 出力:index2=-5
' 特定の要素「5」を検索する
Dim list As List(Of Integer) _
= New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
' 「list」は昇順に並んでいるので、BinarySearchメソッドが使える
Dim index As Integer = list.BinarySearch(5)
WriteLine($"index={index}, value={list(index)}")
' 出力:Index=4, value=5
Dim list2 As List(Of Integer) _
= New List(Of Integer) From {1, 2, 3, 4, 9, 6, 7, 8, 5, 10}
' 「list2」は昇順に並んでいないため、正しい結果が得られない
Dim index2 As Integer = list2.BinarySearch(5)
WriteLine($"index2={index2}")
' 出力:index2=-5
List<T>コレクションの要素を検索するときも、他のコレクションと同様にLINQ拡張を使えばよい。ただし、要素数が非常に多い場合はList<T>クラスのBinarySearchメソッドの利用を検討しよう。
利用可能バージョン:.NET Framework 2.0以降(サンプルコードにはそれ以降の機能/構文も含む)
カテゴリ:クラス・ライブラリ 処理対象:コレクション
使用ライブラリ:Listクラス(System.Collections.Generic名前空間)
関連TIPS:Listの各要素を処理するには?[C#/VB]
関連TIPS:Listに要素を追加/挿入するには?[C#/VB]
関連TIPS:リスト(List)の列挙中にリスト要素を削除するには?[C#、VB]
関連TIPS:構文:メソッドやプロパティをラムダ式で簡潔に実装するには?[C# 6.0]
関連TIPS:構文:コレクションのインスタンス化と同時に要素を追加するには?[C#/VB]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
関連TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?
Copyright© Digital Advantage Corp. All Rights Reserved.