C#のLINQの関数であるIntersect()
の使い方についてです。
配列やリストの要素同士を比べて、積集合のシーケンスを作成することが出来ます。
この記事には.NET Framework 4.6.1を使用しています。
積集合が欲しいの
配列やリスト同士を比べて、同じ要素のものだけを残した集合を積集合と呼ぶそうですよ。
共通部分 (数学) - Wikipedia
これをコード上で実現する場合は、LINQのIntersect()
を使いましょう。
public static IEnumerable<TSource> Intersect<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second );
Enumerable.Intersect(TSource) メソッド (IEnumerable(TSource), IEnumerable(TSource)) (System.Linq)
使い方は簡単。
引数に比較用の配列やリストなどのシーケンスを指定するだけでOKです。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // 数字データ int[] numbersA = new int[] { 1, 2, 3, 4, 5 }; int[] numbersB = new int[] { 8, 6, 4, 2, 0 }; IEnumerable<int> results = numbersA.Intersect( numbersB ); // 結果発表 System.Console.WriteLine( "numbersA:{0}", numbersA.Text() ); System.Console.WriteLine( "numbersB:{0}", numbersB.Text() ); System.Console.WriteLine( "results :{0}", results.Text() ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なコレクションのテキスト取得処理 /// </summary> public static string Text( this IEnumerable i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
numbersA:[1], [2], [3], [4], [5],
numbersB:[8], [6], [4], [2], [0],
results :[2], [4],
numbersA
とnumbersB
の両方にある、2 と 4 だけのシーケンスが作られました。
比較処理を自作するの
このようなクラスを用意します。
Program.cs
private class Parameter { public int ID { get; set; } public string Name { get; set; } public override string ToString() { return string.Format( "ID:{0}, Name:{1}", ID, Name ); } }
このクラスの配列に対して、Intersect()
を使用してみましょう。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class Parameter { public int ID { get; set; } public string Name { get; set; } public override string ToString() { return string.Format( "ID:{0}, Name:{1}", ID, Name ); } } static void Main( string[] args ) { // 人物データ Parameter[] dataA = new Parameter[] { new Parameter() { ID = 0, Name = "正一郎" }, new Parameter() { ID = 5, Name = "清次郎" }, new Parameter() { ID = 3, Name = "誠三郎" }, new Parameter() { ID = 9, Name = "征史郎" }, }; Parameter[] dataB = new Parameter[] { new Parameter() { ID = 5, Name = "清次郎" }, new Parameter() { ID = 3, Name = "誠三郎" }, new Parameter() { ID = 2, Name = "征史郎" }, }; IEnumerable<Parameter> results = dataA.Intersect( dataB ); // 結果発表 System.Console.WriteLine( "dataA :{0}", dataA.Text() ); System.Console.WriteLine( "dataB :{0}", dataB.Text() ); System.Console.WriteLine( "results:{0}", results.Text() ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なコレクションのテキスト取得処理 /// </summary> public static string Text( this IEnumerable i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
dataA :[ID:0, Name:正一郎], [ID:5, Name:清次郎], [ID:3, Name:誠三郎], [ID:9, Name:征史郎],
dataB :[ID:5, Name:清次郎], [ID:3, Name:誠三郎], [ID:2, Name:征史郎],
results:
一つも同じものがないという判定が下りました。
おかしいですね。
これは、各自がParameter
クラスをnew
してインスタンス化していることが問題です。
この場合は、各自の参照はバラバラになってしまい、プロパティの値は同じでも、違うデータ扱いになっているため、正しく積集合が取れていないのです。
同じ参照を使うことで解決はできますが、今回は要素が同じかの判定を、参照からではなく、中身のプロパティの値で判断するようにしてみましょう。
比較用のクラスとして、IEqualityComparer
を継承したクラスを作成します。
IEqualityComparer
IEqualityComparer(T) インターフェイス (System.Collections.Generic)
Program.cs
private class ParameterComparer : IEqualityComparer<Parameter> { public bool Equals( Parameter i_lhs, Parameter i_rhs ) { if( i_lhs.ID == i_rhs.ID && i_lhs.Name == i_rhs.Name ) { return true; } return false; } public int GetHashCode( Parameter i_obj ) { // 本当はnullチェックをしなきゃ駄目だよ。 return i_obj.ID ^ i_obj.Name.GetHashCode(); } }
このクラスをIntersect()
に指定することで、独自の比較処理で判定してくれるようになります。
public static IEnumerable<TSource> Intersect<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer );
Enumerable.Intersect(TSource) メソッド (IEnumerable(TSource), IEnumerable(TSource), IEqualityComparer(TSource)) (System.Linq)
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class Parameter { public int ID { get; set; } public string Name { get; set; } public override string ToString() { return string.Format( "ID:{0}, Name:{1}", ID, Name ); } } private class ParameterComparer : IEqualityComparer<Parameter> { public bool Equals( Parameter i_lhs, Parameter i_rhs ) { if( i_lhs.ID == i_rhs.ID && i_lhs.Name == i_rhs.Name ) { return true; } return false; } public int GetHashCode( Parameter i_obj ) { // 本当はnullチェックをしなきゃ駄目だよ。 return i_obj.ID ^ i_obj.Name.GetHashCode(); } } static void Main( string[] args ) { // 人物データ Parameter[] dataA = new Parameter[] { new Parameter() { ID = 0, Name = "正一郎" }, new Parameter() { ID = 5, Name = "清次郎" }, new Parameter() { ID = 3, Name = "誠三郎" }, new Parameter() { ID = 9, Name = "征史郎" }, }; Parameter[] dataB = new Parameter[] { new Parameter() { ID = 5, Name = "清次郎" }, new Parameter() { ID = 3, Name = "誠三郎" }, new Parameter() { ID = 2, Name = "征史郎" }, }; ParameterComparer compare = new ParameterComparer(); IEnumerable<Parameter> results = dataA.Intersect( dataB, compare ); // 結果発表 System.Console.WriteLine( "dataA :{0}", dataA.Text() ); System.Console.WriteLine( "dataB :{0}", dataB.Text() ); System.Console.WriteLine( "results:{0}", results.Text() ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なコレクションのテキスト取得処理 /// </summary> public static string Text( this IEnumerable i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
dataA :[ID:0, Name:正一郎], [ID:5, Name:清次郎], [ID:3, Name:誠三郎], [ID:9, Name:征史郎],
dataB :[ID:5, Name:清次郎], [ID:3, Name:誠三郎], [ID:2, Name:征史郎],
results:[ID:5, Name:清次郎], [ID:3, Name:誠三郎],
今度は正しく積集合が取れているみたいです。
このようにIntersect()
を七兆回ほど使って、いろいろな集合をとってみてください。
LINQのリンク
LINQ一覧
www.urablog.xyzExcept
異なるシーケンス(配列やリスト)の差集合が欲しい!
www.urablog.xyzUnion
異なるシーケンス(配列やリスト)の和集合が欲しい!
www.urablog.xyz