for WPF developers
Home Profile Tips 全記事一覧

Max 拡張メソッドで最大値を探索する

(2017/03/08 18:25:49 created.)

Max 拡張メソッドはシーケンスの最大値を線形探索法で取得します。

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 = new int[] { 1, 2, 5, 3, 4 };
  11.             Console.WriteLine("コレクションの要素は {{ {0} }} です。", string.Join(", ", numbers));
  12.  
  13.             var max = numbers.Max();
  14.             Console.WriteLine("最大値は {{ {0} }} です。", max);
  15.  
  16.             Console.ReadKey();
  17.         }
  18.     }
  19. }


内部では要素の先頭から末尾までただひたすら大小比較をおこなっているため、大規模データの探索には向かないかもしれません。数値型として int、long、float、double、decimal に対応しています。また、それぞれの null 許容型にも対応しています。

次に以下のような Person クラスを考えます。

Person.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.  
  5.     /// <summary>
  6.     /// 人物データを表します。
  7.     /// </summary>
  8.     public class Person
  9.     {
  10.         /// <summary>
  11.         /// 氏名を取得または設定します。
  12.         /// </summary>
  13.         public string Name { get; set; }
  14.  
  15.         /// <summary>
  16.         /// 年齢を取得または設定します。
  17.         /// </summary>
  18.         public int Age { get; set; }
  19.     }
  20. }

この Person クラスを使って次のようなコードを実行してみましょう。

Program.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.     using System.Collections.Generic;
  5.     using System.Linq;
  6.  
  7.     class Program
  8.     {
  9.         static void Main(string[] args)
  10.         {
  11.             var People = GetPeople();
  12.  
  13.             var max = People.Max();
  14.             Console.WriteLine("最大値は {{ {0} }} です。", max.Name);
  15.  
  16.             Console.ReadKey();
  17.         }
  18.  
  19.         /// <summary>
  20.         /// 人物コレクションの列挙子を取得します。
  21.         /// </summary>
  22.         /// <returns></returns>
  23.         static IEnumerable<Person> GetPeople()
  24.         {
  25.             yield return new Person() { Name = "田中 淳平", Age = 37 };
  26.             yield return new Person() { Name = "鈴木 ほのか", Age = 26 };
  27.             yield return new Person() { Name = "小池 哲司", Age = 22 };
  28.             yield return new Person() { Name = "恩田 進", Age = 42 };
  29.             yield return new Person() { Name = "中津山 亜希子", Age = 20 };
  30.         }
  31.     }
  32. }


当然ですが、カスタムクラスである Person クラスをどうやって比較すればいいのかを教えていないので例外が発生してしまいます。エラーメッセージにある通りで、Max 拡張メソッドは IComparable<T> インターフェースを実装したものしか扱えません。

それでは Person クラスに IComparable<T> インターフェースを実装しましょう。

Person.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.  
  5.     /// <summary>
  6.     /// 人物データを表します。
  7.     /// </summary>
  8.     public class Person : IComparable<Person>
  9.     {
  10.         /// <summary>
  11.         /// 氏名を取得または設定します。
  12.         /// </summary>
  13.         public string Name { get; set; }
  14.  
  15.         /// <summary>
  16.         /// 年齢を取得または設定します。
  17.         /// </summary>
  18.         public int Age { get; set; }
  19.  
  20.         /// <summary>
  21.         /// 年齢を比較します。
  22.         /// </summary>
  23.         /// <param name="other">比較する他のオブジェクトを指定します。</param>
  24.         /// <returns>比較結果を返します。</returns>
  25.         public int CompareTo(Person other)
  26.         {
  27.             return this.Age - other.Age;
  28.         }
  29.     }
  30. }

これでカスタムクラスである Person クラス同士の比較ができるようになったので、次のように Max 拡張メソッドが使えます。

Program.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.     using System.Collections.Generic;
  5.     using System.Linq;
  6.  
  7.     class Program
  8.     {
  9.         static void Main(string[] args)
  10.         {
  11.             var People = GetPeople();
  12.  
  13.             var max = People.Max();
  14.             Console.WriteLine("最年長は {0} さん ({1}) です。", max.Name, max.Age);
  15.  
  16.             Console.ReadKey();
  17.         }
  18.  
  19.         /// <summary>
  20.         /// 人物コレクションの列挙子を取得します。
  21.         /// </summary>
  22.         /// <returns></returns>
  23.         static IEnumerable<Person> GetPeople()
  24.         {
  25.             yield return new Person() { Name = "田中 淳平", Age = 37 };
  26.             yield return new Person() { Name = "鈴木 ほのか", Age = 26 };
  27.             yield return new Person() { Name = "小池 哲司", Age = 22 };
  28.             yield return new Person() { Name = "恩田 進", Age = 42 };
  29.             yield return new Person() { Name = "中津山 亜希子", Age = 20 };
  30.         }
  31.     }
  32. }


上記では IComparable<T> インターフェースを実装することで年齢比較をおこなう Person クラスを作成しましたが、Max 拡張メソッドには比較方法を指定するオーバーロードが用意されているため、今回のように単純な比較であれば、Person クラスにわざわざ IComparable<T> インターフェースを実装する必要がありません。

IComparable<Person> インターフェースを実装していない Person クラスを用いて次のように書くことができます。

Program.cs
  1. namespace Tips_Linq
  2. {
  3.     using System;
  4.     using System.Collections.Generic;
  5.     using System.Linq;
  6.  
  7.     class Program
  8.     {
  9.         static void Main(string[] args)
  10.         {
  11.             var People = GetPeople();
  12.  
  13.             var max = People.Max(x => x.Age);
  14.             Console.WriteLine("最年長は {0} 才です。", max);
  15.  
  16.             Console.ReadKey();
  17.         }
  18.  
  19.         /// <summary>
  20.         /// 人物コレクションの列挙子を取得します。
  21.         /// </summary>
  22.         /// <returns></returns>
  23.         static IEnumerable<Person> GetPeople()
  24.         {
  25.             yield return new Person() { Name = "田中 淳平", Age = 37 };
  26.             yield return new Person() { Name = "鈴木 ほのか", Age = 26 };
  27.             yield return new Person() { Name = "小池 哲司", Age = 22 };
  28.             yield return new Person() { Name = "恩田 進", Age = 42 };
  29.             yield return new Person() { Name = "中津山 亜希子", Age = 20 };
  30.         }
  31.     }
  32. }


Max 拡張メソッドに数値を返す匿名メソッドを指定することで、その戻り値の最大値を取得できます。ただし、先ほどまでは変数 max の型が Person クラスでしたが、今回は int 型となっています。単に最大値が欲しいときは後者のほうが簡単ですが、最大値を持つ要素そのものを抽出したい場合はやはり前者のように IComparable<T> インターフェースを実装しないといけません。