DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

配列やコレクションのフィルタ処理を行う(条件に合う要素を抜き出す)

ここでは、配列やコレクションの要素のうち、条件に合った要素だけを取り出して新たな配列やコレクションを作成する(フィルタ処理を行う)方法を紹介します。

For文を使用する方法

For文ですべての要素を一つずつ調べて、条件に合えばコレクションに追加するというのが最も基本的な方法です。

以下の例では、整数の配列から5未満の数だけ抽出して新しい配列を作っています。

VB.NET
コードを隠すコードを選択
'基の配列
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 }となる
C#
コードを隠すコードを選択
//基の配列
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 }となる

FindAllメソッドを使用する方法

.NET Framework 2.0以降では、FindAllメソッドが使えます。このメソッドを使うには、要素を抽出する条件に合った時にTrueを返すメソッド(このメソッドはPredicateジェネリックデリゲートと同じシグネチャである必要があります)を作成する必要があります。

FindAllメソッドは、配列の場合は、スタティックメソッド(Array.FindAll<T>メソッド)です。コレクションでは、List(List<T>.FindAllメソッド)などで使用でき、こちらはインスタンスメソッドです。

早速ですが、先ほどと同じように整数の配列から5未満の数だけ抽出する例を示します。

VB.NET
コードを隠すコードを選択
'指定された数値が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
C#
コードを隠すコードを選択
//指定された数値が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では使えません。

C#
コードを隠すコードを選択
//基の配列
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以降)、さらに簡単に記述できます。

VB.NET
コードを隠すコードを選択
'基の配列
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)
C#
コードを隠すコードを選択
//基の配列
int[] nums = new int[] { 8, 2, 9, 1, 7, 4, 5, 3 };
//フィルタ処理を行う
int[] ret = Array.FindAll(nums, num => num < 5);

LINQを使用する方法

.NET Framework 3.5以降では、LINQを使用してフィルタ処理を行うことができます。LINQを使用するには、参照設定に「System.Core.dll」を追加する必要があります。

LINQによるフィルタ処理の詳細は、MSDNの「Language-Integrated Query (LINQ) データのフィルタ処理」をご覧ください。

Whereメソッドを呼び出してフィルタ処理する例を以下に示します。

VB.NET
コードを隠すコードを選択
'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 }となる
C#
コードを隠すコードを選択
//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]
VB.NETのコードは現在準備中です。
Convert C# to VB.NET により、
下記のC#のコードをVB.NETに変換したものを参考にしてください。
C#
コードを隠すコードを選択
//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を使った方法は一番遅かったです。

  • 履歴:
  • 2013/8/25 「For文を使用する方法」を追加など。
  • 2016/6/25 FindAllメソッドのサンプルのコメントにコレクションの例を追加。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • 「???を参照に追加します」の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。