今回は.NET 6で追加されたLINQのMaxBy / MinByについてメモ。
.NET 6 より前ではどうしてた?代用できるメソッドはあるの?といったところもあります。
目次
■ まずはMaxBy / MinBy (.NET 6)
■ MaxとFirstの合わせ技 (.NET 5以前)
■ Aggregateで代用 (.NET 5以前)
■ 拡張メソッド (.NET 5以前)
■ 全体ソース
■ まずはMaxBy / MinBy (.NET 6)
■ MaxとFirstの合わせ技 (.NET 5以前)
■ Aggregateで代用 (.NET 5以前)
■ 拡張メソッド (.NET 5以前)
■ 全体ソース
まずはMaxBy / MinBy (.NET 6)
こんなListがあったとして
new List<Person>()
{
new Person(Name: "Ichiro", Age: 47),
new Person("Jiro", 47),
new Person("Saburo", 46),
new Person("Siro", 41),
new Person("Goro", 35),
new Person("Rokuro", 35),
};
MaxBy、MinByを呼んでやると
var maxAgePerson = People.MaxBy(_ => _.Age);
Console.WriteLine($"\tMax : {maxAgePerson}");
var minAgePerson = People.MinBy(_ => _.Age);
Console.WriteLine($"\tMin : {minAgePerson}");
最大、最小の年齢に合致した最初の要素を返してくれます。 なんで今までなかったのか不思議ですが、ラクに取得できるようになったので嬉しいですね。
Max : Person { Name = Ichiro, Age = 47 }
Min : Person { Name = Goro, Age = 35 }
MaxとFirstの合わせ技 (.NET 5以前)
LINQでパッと思い付くのはこれでしょうか。Max()で値を取得して、その値を保持している最初の要素をFirst()で取得。
var maxAge = People.Max(_ => _.Age);
var maxAgePerson = People.FirstOrDefault(_ => _.Age == maxAge);
Console.WriteLine($"\tMax : {maxAgePerson}");
var minAge = People.Min(_ => _.Age);
var minAgePerson = People.FirstOrDefault(_ => _.Age == minAge);
Console.WriteLine($"\tMin : {minAgePerson}");
Aggregateで代用 (.NET 5以前)
集計処理をしてくれるAggregateを使用して実現できます。
xに大きい方の値を保持していき、各要素yとの比較を繰り返していきます。
今回はMaxBy/MinByに合わせて最初の要素を得るために「>=」としていますが、
最後の要素が欲しいということであれば「>」にすればOK。
var maxAgePerson = People.Aggregate((x, y) => x.Age >= y.Age ? x : y);
Console.WriteLine($"\tMax : {maxAgePerson}");
var minAgePerson = People.Aggregate((x, y) => x.Age <= y.Age ? x : y);
Console.WriteLine($"\tMin : {minAgePerson}");
拡張メソッド (.NET 5以前)
何度もAggregateを呼ぶことになるなら拡張メソッドを用意しておけばOK。
public static class LinqExtension
{
public static T MinBy<T, U>(this IEnumerable<T> source, Func<T, U> func) where U : IComparable<U>
=> source.Aggregate((x, y) => func(x).CompareTo(func(y)) <= 0 ? x : y);
public static T MaxBy<T, U>(this IEnumerable<T> source, Func<T, U> func) where U : IComparable<U>
=> source.Aggregate((x, y) => func(x).CompareTo(func(y)) >= 0 ? x : y);
}
呼び方は.NET 6のMaxBy()と全く同じように書けます。
var maxAgePerson = People.MaxBy(_ => _.Age);
Console.WriteLine($"\tMax : {maxAgePerson}");
var minAgePerson = People.MinBy(_ => _.Age);
Console.WriteLine($"\tMin : {minAgePerson}");
全体ソース
全体ソースを貼っておきます。 .NET 6 はmainメソッドを書く必要なくなりましたね。 .NET 5 以前のソースは言語バージョンによっては動かない書き方もしてますが、 そのままコピペしてvscodeのCode Runnerで実行できます。
.NET 6
Console.WriteLine($"Main() Start");
MaxBySample.Test1();
Console.WriteLine($"Main() End");
public record Person(string Name, int Age);
internal static class MaxBySample
{
private static List<Person> People => new List<Person>()
{
new Person(Name: "Ichiro", Age: 47),
new Person("Jiro", 47),
new Person("Saburo", 46),
new Person("Siro", 41),
new Person("Goro", 35),
new Person("Rokuro", 35),
};
public static void Test1()
{
Console.WriteLine($"*** MaxBy ***");
var maxAgePerson = People.MaxBy(_ => _.Age);
Console.WriteLine($"\tMax : {maxAgePerson}");
var minAgePerson = People.MinBy(_ => _.Age);
Console.WriteLine($"\tMin : {minAgePerson}");
}
}
.NET 5 以前
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Main() Start");
MaxBySample.Test2();
MaxBySample.Test3();
MaxBySample.Test4();
Console.WriteLine($"Main() End");
}
}
public class Person
{
public string Name {get; set;}
public int Age {get; set;}
public override string ToString()
=> $"Person {{ Name = {this.Name}, Age = {this.Age} }} ";
}
internal static class MaxBySample
{
private static List<Person> People => new List<Person>()
{
new Person() {Name = "Ichiro", Age = 47},
new Person() {Name = "Jiro", Age = 47},
new Person() {Name = "Saburo", Age = 46},
new Person() {Name = "Siro", Age = 41},
new Person() {Name = "Goro", Age = 35},
new Person() {Name = "Rokuro", Age = 35},
};
public static void Test2()
{
Console.WriteLine($"*** Max & First ***");
var maxAge = People.Max(_ => _.Age);
var maxAgePerson = People.FirstOrDefault(_ => _.Age == maxAge);
Console.WriteLine($"\tMax : {maxAgePerson}");
var minAge = People.Min(_ => _.Age);
var minAgePerson = People.FirstOrDefault(_ => _.Age == minAge);
Console.WriteLine($"\tMin : {minAgePerson}");
}
public static void Test3()
{
Console.WriteLine($"*** Aggregate ***");
var maxAgePerson = People.Aggregate((x, y) => x.Age >= y.Age ? x : y);
Console.WriteLine($"\tMax : {maxAgePerson}");
var minAgePerson = People.Aggregate((x, y) => x.Age <= y.Age ? x : y);
Console.WriteLine($"\tMin : {minAgePerson}");
}
public static void Test4()
{
Console.WriteLine($"*** MaxByExtention ***");
var maxAgePerson = People.MaxBy(_ => _.Age);
Console.WriteLine($"\tMax : {maxAgePerson}");
var minAgePerson = People.MinBy(_ => _.Age);
Console.WriteLine($"\tMin : {minAgePerson}");
}
}
public static class LinqExtension
{
public static T MinBy<T, U>(this IEnumerable<T> source, Func<T, U> func) where U : IComparable<U>
=> source.Aggregate((x, y) => func(x).CompareTo(func(y)) <= 0 ? x : y);
public static T MaxBy<T, U>(this IEnumerable<T> source, Func<T, U> func) where U : IComparable<U>
=> source.Aggregate((x, y) => func(x).CompareTo(func(y)) >= 0 ? x : y);
}
以上、もりもりでした。
0 件のコメント:
コメントを投稿