for WPF developers
Home Profile Tips 全記事一覧

SelectMany 拡張メソッドでシーケンスに射影した各要素を 1 次元に平坦化する

(2017/03/14 10:44:47 created.)

SelectMany 拡張メソッドは Select 拡張メソッドと同じく新たなシーケンスに射影しますが、各要素がシーケンスとなっている場合はこれを平坦化し、1 次元のシーケンスとして出力します。

Program.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.     using System.Linq;
  5.  
  6.     class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             var numbers = Enumerable.Range(1, 3);
  11.             Console.WriteLine(numbers.Count());
  12.  
  13.             Console.WriteLine("Select 拡張メソッドでは IEnumerable<int[]> になる");
  14.             var jag = numbers.Select(x => new int[x]);
  15.             foreach (var value in jag)
  16.             {
  17.                 Console.WriteLine(value);
  18.             }
  19.  
  20.             Console.WriteLine("SelectMany 拡張メソッドでは平坦化されて IEnumerable<int> になる");
  21.             var array = numbers.SelectMany(x => new int[x]);
  22.             foreach (var value in array)
  23.             {
  24.                 Console.WriteLine(value);
  25.             }
  26.  
  27.             Console.ReadKey();
  28.         }
  29.     }
  30. }


上記のコード例では、3 個のシーケンスを元に Select 拡張メソッドと SelectMany 拡張メソッドでどちらも int 型の配列を返すデリゲートを指定しています。このとき、Select 拡張メソッドは与えられたデリゲートが返すオブジェクトをそのまま新たなシーケンスの要素とするため、各要素が int 型の配列であるシーケンスを返します。したがってその要素数は元のシーケンスと同じく 3 個となります。

これに対して SelectMany 拡張メソッドは与えられたデリゲートが返すオブジェクトを平坦化してしまいます。したがって、得られた int 型の配列の各要素が新たなシーケンスの各要素になり、結果として int 型のシーケンスが生成されることになります。上記の例では 1+2+3=6 個の要素を持つシーケンスとなっています。階層構造を持つクラスに属しているプロパティを同じレベルとしてひとつのシーケンスに展開するときなどに便利です。

上記の例において、Select 拡張メソッドと SelectMany 拡張メソッドの違いをイメージで表すと次の図のようになります。

Select 拡張メソッドによる射影のイメージ

SelectMany 拡張メソッドによる射影のイメージ

SelectMany 拡張メソッドも Select 拡張メソッドと同じく、要素のインデックスを入力引数のデリゲートで使うことができます。

Program.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.     using System.Linq;
  5.  
  6.     class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             var numbers = Enumerable.Range(1, 3);
  11.             Console.WriteLine(numbers.Count());
  12.  
  13.             Console.WriteLine("Select 拡張メソッドでは IEnumerable<IEnumerable<int>> になる");
  14.             var jag = numbers.Select((x, i) => Enumerable.Range(1, i + 1));
  15.             foreach (var value in jag)
  16.             {
  17.                 Console.WriteLine(value);
  18.             }
  19.  
  20.             Console.WriteLine("SelectMany 拡張メソッドでは平坦化されて IEnumerable<int> になる");
  21.             var array = numbers.SelectMany((x, i) => Enumerable.Range(1, i + 1));
  22.             foreach (var value in array)
  23.             {
  24.                 Console.WriteLine(value);
  25.             }
  26.  
  27.             Console.ReadKey();
  28.         }
  29.     }
  30. }


また、平坦化されたシーケンスに対してさらに射影をおこなうことができるオーバーロードもあります。

Program.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.     using System.Linq;
  5.  
  6.     class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             var numbers = Enumerable.Range(1, 3);
  11.             Console.WriteLine(numbers.Count());
  12.  
  13.             Console.WriteLine("シーケンスが平坦化される");
  14.             var array1 = numbers.SelectMany(x => Enumerable.Range(10, x));
  15.             foreach (var value in array1)
  16.             {
  17.                 Console.WriteLine(value);
  18.             }
  19.  
  20.             Console.WriteLine("平坦化したシーケンスをさらに射影することができる");
  21.             var array2 = numbers.SelectMany(x => Enumerable.Range(10, x), (x, y) => "x = " + x + ", y = " + y);
  22.             foreach (var value in array2)
  23.             {
  24.                 Console.WriteLine(value);
  25.             }
  26.  
  27.             Console.WriteLine("平坦化したシーケンスをさらに射影することができる");
  28.             var array3 = numbers.SelectMany((x, i) => Enumerable.Range(i, x), (x, y) => "x = " + x + ", y = " + y);
  29.             foreach (var value in array3)
  30.             {
  31.                 Console.WriteLine(value);
  32.             }
  33.  
  34.             Console.ReadKey();
  35.         }
  36.     }
  37. }


上記の例のように、第 2 入力引数として平坦化されたシーケンスに対する射影を示すデリゲートを与えます。このとき、このデリゲートは元のシーケンスの要素 x と、平坦化された中間シーケンスの要素 y の 2 つの入力引数を持っています。平坦化されたシーケンスに対して Select 拡張メソッドを使用することでも射影できますが、こちらのオーバーロードを使用することで元のシーケンスの要素も反映させることができるため、射影する方法に幅があります。