ここでは、配列やコレクションの要素のうち、条件に合った要素だけを取り出して新たな配列やコレクションを作成する(フィルタ処理を行う)方法を紹介します。
For文ですべての要素を一つずつ調べて、条件に合えばコレクションに追加するというのが最も基本的な方法です。
以下の例では、整数の配列から5未満の数だけ抽出して新しい配列を作っています。
'基の配列 Dim nums As Integer() = New Integer() {8, 2, 9, 1, 7, 4, 5, 3} '5未満の要素を覚えておくためのコレクション '.NET Framework 2.0以降ならば、List(Of Integer)を使った方が良い Dim al As New System.Collections.ArrayList() For Each i As Integer In nums '5未満の要素をコレクションに追加する If i < 5 Then al.Add(i) End If Next '配列に変換する Dim ret As Integer() = DirectCast(al.ToArray(GetType(Integer)), Integer()) '結果は、{ 2, 1, 4, 3 }となる
//基の配列 int[] nums = new int[] { 8, 2, 9, 1, 7, 4, 5, 3 }; //5未満の要素を覚えておくためのコレクション //.NET Framework 2.0以降ならば、List<int>を使った方が良い System.Collections.ArrayList al = new System.Collections.ArrayList(); foreach (int i in nums) { //5未満の要素をコレクションに追加する if (i < 5) { al.Add(i); } } //配列に変換する int[] ret = (int[])al.ToArray(typeof(int)); //結果は、{ 2, 1, 4, 3 }となる
.NET Framework 2.0以降では、FindAllメソッドが使えます。このメソッドを使うには、要素を抽出する条件に合った時にTrueを返すメソッド(このメソッドはPredicateジェネリックデリゲートと同じシグネチャである必要があります)を作成する必要があります。
FindAllメソッドは、配列の場合は、スタティックメソッド(Array.FindAll<T>メソッド)です。コレクションでは、List(List<T>.FindAllメソッド)などで使用でき、こちらはインスタンスメソッドです。
早速ですが、先ほどと同じように整数の配列から5未満の数だけ抽出する例を示します。
'指定された数値が5未満のときにTrueを返すメソッド Private Shared Function IsSmaller(num As Integer) As Boolean Return num < 5 End Function 'エントリポイント Public Shared Sub Main() '基の配列 Dim nums As Integer() = New Integer() {8, 2, 9, 1, 7, 4, 5, 3} 'IsSmallerメソッドがTrueを返す要素だけの配列を取得する Dim ret As Integer() = Array.FindAll(nums, AddressOf IsSmaller) 'コレクションの場合は、次のようになる 'Dim lst As List(Of Integer) = _ ' (New List(Of Integer)(nums)).FindAll(IsSmaller) '結果は、{ 2, 1, 4, 3 }となる Console.ReadLine() End Sub
//指定された数値が5未満のときにTrueを返すメソッド private static bool IsSmaller(int num) { return num < 5; } //エントリポイント public static void Main() { //基の配列 int[] nums = new int[] { 8, 2, 9, 1, 7, 4, 5, 3 }; //IsSmallerメソッドがTrueを返す要素だけの配列を取得する int[] ret = Array.FindAll(nums, IsSmaller); //コレクションの場合は、次のようになる //List<int> lst = (new List<int>(nums)).FindAll(IsSmaller); //結果は、{ 2, 1, 4, 3 }となる Console.ReadLine(); }
C#では、匿名メソッドを使ってもっと簡単に記述できます。残念ながら、匿名メソッドはVB.NETでは使えません。
//基の配列 int[] nums = new int[] { 8, 2, 9, 1, 7, 4, 5, 3 }; //フィルタ処理を行う int[] ret = Array.FindAll(nums, delegate(int num) { return num < 5; });
ラムダ式が使えるのであれば(VB9、C#3.0、.NET Framework 3.5、Visual Studio 2008以降)、さらに簡単に記述できます。
'基の配列 Dim nums As Integer() = New Integer() {8, 2, 9, 1, 7, 4, 5, 3} 'フィルタ処理を行う Dim ret As Integer() = Array.FindAll(nums, Function(num As Integer) num < 5)
//基の配列 int[] nums = new int[] { 8, 2, 9, 1, 7, 4, 5, 3 }; //フィルタ処理を行う int[] ret = Array.FindAll(nums, num => num < 5);
.NET Framework 3.5以降では、LINQを使用してフィルタ処理を行うことができます。LINQを使用するには、参照設定に「System.Core.dll」を追加する必要があります。
LINQによるフィルタ処理の詳細は、MSDNの「Language-Integrated Query (LINQ) データのフィルタ処理」をご覧ください。
Whereメソッドを呼び出してフィルタ処理する例を以下に示します。
'Imports System.Linq '基の配列(コレクションでも可) Dim nums As Integer() = New Integer() {8, 2, 9, 1, 7, 4, 5, 3} 'フィルタ処理を行い、5未満の要素だけの配列を作成する Dim ret As Integer() = nums.Where(Function(num) num < 5).ToArray() 'クエリ式を使うと、次のようにもできる 'Dim query = From num In nums _ ' Where num < 5 'Dim ret As Integer() = query.ToArray() '結果は、{ 2, 1, 4, 3 }となる
//using System.Linq; //基の配列(コレクションでも可) int[] nums = new int[] { 8, 2, 9, 1, 7, 4, 5, 3 }; //フィルタ処理を行い、5未満の要素だけの配列を作成する int[] ret = nums.Where(num => num < 5).ToArray(); //クエリ式を使うと、次のようにもできる //var query = from num in nums // where num < 5 // select num; //int[] ret = query.ToArray(); //結果は、{ 2, 1, 4, 3 }となる
次のようなコードでどの方法が速いかを調べてみました。
[VB.NET] VB.NETのコードは現在準備中です。 Convert C# to VB.NET により、 下記のC#のコードをVB.NETに変換したものを参考にしてください。
//using System.Linq; //がソースファイルの一番上に書かれているものとする System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch sw3 = new System.Diagnostics.Stopwatch(); Random r = new Random(); byte[] nums = new byte[100000]; r.NextBytes(nums); //For文で地道に sw1.Start(); List<byte> buf = new List<byte>(); foreach (byte num in nums) { if (num < 10) { buf.Add(num); } } byte[] ret1 = buf.ToArray(); sw1.Stop(); //FindAllを使う sw2.Start(); byte[] ret2 = Array.FindAll(nums, num => num < 10); sw2.Stop(); //Whereを使う sw3.Start(); byte[] ret3 = nums.Where(num => num < 10).ToArray(); sw3.Stop(); //結果を表示する Console.WriteLine("For : {0}", sw1.ElapsedTicks); Console.WriteLine("FindAll : {0}", sw2.ElapsedTicks); Console.WriteLine("Where : {0}", sw3.ElapsedTicks); //結果例 //For : 1857506 //FindAll : 6582366 //Where : 12715969
その結果、私の環境(.NET Framework 3.5 SP1)では、for文で1つずつ調べる方法が圧倒的に早く、その次にFindAllメソッドで、Whereを使った方法は一番遅かったです。