C#のLINQの関数であるToLookup()
の使い方についてです。
配列やリストの要素を特定のデータをキーにしたシーケンスにまとめることが出来ます。
この記事には.NET Framework 4.6.1を使用しています。
ILookupにデータを纏めちゃうぞ
データが配列やリストでたくさんあるとき、特定のプロパティごとにデータをまとめたいとき、きっとありますよね。
そんなときはLINQのToLookup()
を使ってみませんか。
いい感じにまとめてくれますよ。
public static ILookup<TKey, TSource> ToLookup<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector );
Enumerable.ToLookup(TSource, TKey) メソッド (IEnumerable(TSource), Func(TSource, TKey)) (System.Linq)
ちなみに返ってくるのはILookup
という聞きなれない型です。
ILookup
ILookup(TKey, TElement) インターフェイス (System.Linq)
Dictionary
と似ていますが、キーに複数の要素が設定できるのと、後から要素が追加できない点という違いがありそうですね。
ToLookup()
を使う前に、データ用のクラスを作成しておきましょう。
Program.cs
private class Parameter { public string Team { get; set; } public string Name { get; set; } public int Age { get; set; } public override string ToString() { return string.Format( "Team:{0}, Name:{1}, Age:{2}", Team, Name, Age ); } }
このクラスの配列を用意して、ToLookup()
を使ってTeam
のプロパティ毎にまとめてみましょうか。
ToLookup()
の使い方は、第一引数にまとめるために使用するプロパティを指定する処理を記述するだけです。
今回の場合は、ラムダ式を用いています。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class Parameter { public string Team { get; set; } public string Name { get; set; } public int Age { get; set; } public override string ToString() { return string.Format( "Team:{0}, Name:{1}, Age:{2}", Team, Name, Age ); } } static void Main( string[] args ) { // 人物データ。 Parameter[] persons = new Parameter[] { new Parameter() { Team = "A", Age = 52, Name = "正一郎" }, new Parameter() { Team = "A", Age = 28, Name = "清次郎" }, new Parameter() { Team = "C", Age = 20, Name = "誠三郎" }, new Parameter() { Team = "B", Age = 18, Name = "征史郎" }, }; ILookup<string, Parameter> results = persons.ToLookup( param => param.Team ); // 結果発表 System.Console.WriteLine( "persons:{0}", persons.Text() ); foreach( var team in results ) { System.Console.WriteLine( "Team:{0}", team.Key ); foreach( var person in team ) { System.Console.WriteLine( "\t{0}", person ); } } // 入力待ち用 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
persons:[Team:A, Name:正一郎, Age:52], [Team:A, Name:清次郎, Age:28], [Team:C, Name:誠三郎, Age:20], [Team:B, Name:征史 郎, Age:18],
Team:A
Team:A, Name:正一郎, Age:52
Team:A, Name:清次郎, Age:28
Team:C
Team:C, Name:誠三郎, Age:20
Team:B
Team:B, Name:征史郎, Age:18
Team
のプロパティ毎にまとめてくれました。
いい感じです。
纏めるデータの型を変更しちゃうぞ
さきほどはデータの型であったParameter
クラスをそのままILookup
の要素として使いましたが、この要素の型も指定することが出来ます。
public static ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector );
Enumerable.ToLookup(TSource, TKey, TElement) メソッド (IEnumerable(TSource), Func(TSource, TKey), Func(TSource, TElement)) (System.Linq)
第二引数に、要素として使う情報を指定する処理を書くことで、ILookup
の要素となります。
以下の例では、Parameter
クラスのName
プロパティを要素として使うようにラムダ式を用いて指定しています。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class Parameter { public string Team { get; set; } public string Name { get; set; } public int Age { get; set; } public override string ToString() { return string.Format( "Team:{0}, Name:{1}, Age:{2}", Team, Name, Age ); } } static void Main( string[] args ) { // 人物データ。 Parameter[] persons = new Parameter[] { new Parameter() { Team = "A", Age = 52, Name = "正一郎" }, new Parameter() { Team = "A", Age = 28, Name = "清次郎" }, new Parameter() { Team = "C", Age = 20, Name = "誠三郎" }, new Parameter() { Team = "B", Age = 18, Name = "征史郎" }, }; ILookup<string, string> results = persons.ToLookup( param => param.Team, param => param.Name ); // 結果発表 System.Console.WriteLine( "persons:{0}", persons.Text() ); foreach( var team in results ) { System.Console.WriteLine( "Team:{0}", team.Key ); foreach( var person in team ) { System.Console.WriteLine( "\t{0}", person ); } } // 入力待ち用 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
persons:[Team:A, Name:正一郎, Age:52], [Team:A, Name:清次郎, Age:28], [Team:C, Name:誠三郎, Age:20], [Team:B, Name:征史 郎, Age:18],
Team:A
正一郎
清次郎
Team:C
誠三郎
Team:B
征史郎
グループ化する際の比較処理を自作するぞ
さて以下のコードを見てください。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class Parameter { public string Team { get; set; } public string Name { get; set; } public int Age { get; set; } public override string ToString() { return string.Format( "Team:{0}, Name:{1}, Age:{2}", Team, Name, Age ); } } static void Main( string[] args ) { // 人物データ。 Parameter[] persons = new Parameter[] { new Parameter() { Team = "A", Age = 52, Name = "正一郎" }, new Parameter() { Team = "a", Age = 28, Name = "清次郎" }, new Parameter() { Team = "C", Age = 20, Name = "誠三郎" }, new Parameter() { Team = "B", Age = 18, Name = "征史郎" }, }; ILookup<string, string> results = persons.ToLookup( param => param.Team, param => param.Name ); // 結果発表 System.Console.WriteLine( "persons:{0}", persons.Text() ); foreach( var team in results ) { System.Console.WriteLine( "Team:{0}", team.Key ); foreach( var person in team ) { System.Console.WriteLine( "\t{0}", person ); } } // 入力待ち用 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
persons:[Team:A, Name:正一郎, Age:52], [Team:a, Name:清次郎, Age:28], [Team:C, Name:誠三郎, Age:20], [Team:B, Name:征史 郎, Age:18],
Team:A
正一郎
Team:a
清次郎
Team:C
誠三郎
Team:B
征史郎
さきほどと同じように、Team
のプロパティでデータを分けているのですが、"A"と"a"が別々のグループとして分かれています。
これを、大文字でも小文字でも同じグループ扱いにすることはできないでしょうか。
そんな場合は、IEqualityComparer
を使った比較処理を自作することで可能となります。
IEqualityComparer
IEqualityComparer(T) インターフェイス (System.Collections.Generic)
Program.cs
private class GroupComparer : IEqualityComparer<string> { public bool Equals( string i_lhs, string i_rhs ) { return i_lhs.ToUpper() == i_rhs.ToUpper(); } public int GetHashCode( string i_value ) { return i_value.ToUpper().GetHashCode(); } }
このようにIEqualityComparer
を継承して、大文字でも小文字でも、一度大文字にして比較する処理を作成してみました。
これをToLookup()
に指定してあげればOKです。
public static ILookup<TKey, TSource> ToLookup<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer );
Enumerable.ToLookup(TSource, TKey) メソッド (IEnumerable(TSource), Func(TSource, TKey), IEqualityComparer(TKey)) (System.Linq)
public static ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer );
Enumerable.ToLookup(TSource, TKey, TElement) メソッド (IEnumerable(TSource), Func(TSource, TKey), Func(TSource, TElement), IEqualityComparer(TKey)) (System.Linq)
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class Parameter { public string Team { get; set; } public string Name { get; set; } public int Age { get; set; } public override string ToString() { return string.Format( "Team:{0}, Name:{1}, Age:{2}", Team, Name, Age ); } } private class GroupComparer : IEqualityComparer<string> { public bool Equals( string i_lhs, string i_rhs ) { return i_lhs.ToUpper() == i_rhs.ToUpper(); } public int GetHashCode( string i_value ) { return i_value.ToUpper().GetHashCode(); } } static void Main( string[] args ) { // 人物データ。 Parameter[] persons = new Parameter[] { new Parameter() { Team = "A", Age = 52, Name = "正一郎" }, new Parameter() { Team = "a", Age = 28, Name = "清次郎" }, new Parameter() { Team = "C", Age = 20, Name = "誠三郎" }, new Parameter() { Team = "B", Age = 18, Name = "征史郎" }, }; GroupComparer compare = new GroupComparer(); ILookup<string, string> results = persons.ToLookup( param => param.Team, param => param.Name, compare ); // 結果発表 System.Console.WriteLine( "persons:{0}", persons.Text() ); foreach( var team in results ) { System.Console.WriteLine( "Team:{0}", team.Key ); foreach( var person in team ) { System.Console.WriteLine( "\t{0}", person ); } } // 入力待ち用 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
persons:[Team:A, Name:正一郎, Age:52], [Team:a, Name:清次郎, Age:28], [Team:C, Name:誠三郎, Age:20], [Team:B, Name:征史 郎, Age:18],
Team:A
正一郎
清次郎
Team:C
誠三郎
Team:B
征史郎
このように、"A"も"a"も同じグループになっています。
こんな感じでToLookup()
を七兆回ほどつかって、データを整理してみてください。
LINQのリンク
LINQ一覧
www.urablog.xyzGroupBy
シーケンス(配列やリスト)内の要素をグループ化したい!
www.urablog.xyz