rksoftware

Visual Studio とか C# とかが好きです

ある列の値でグルーピングした中で別の項目値が最大の行のリストを作る C# の別の書き方

以前に Power Automate である列の値でグルーピングした中で別の項目値が最大の行のリストを作る記事を書きました。

rksoftware.hatenablog.com

この中で C# で書くとということでこんなコードを書きました。

var maxs = values?.GroupBy(x => x.A).Select(x => x.OrderByDescending(m => m.B).First()).ToArray();

こういう書き方、嫌う人もいると思います。

データを何回も回して効率が悪い、結果保持用のミュータブルリストを用意して、データを一回ループで回しながらリストを更新し、一回転で終わらせるべきだ、と。
わかります。そういう方も多くいらっしゃります。大丈夫です。そう言うコードも書きましょう!

■ はい!

var maxs = values?.Aggregate(new Dictionary<int, M>(), (list, x) => { if (!list.TryGetValue(x.A, out var m) || x.B > m.B) list[x.A] = x; return list; }).Values.ToArray();

書きました!

■ 比較

比較するとこんな感じです。最初のコードに納得のいかなかった方も納得の一回ループでミュータブルリストを更新になっているのではないでしょうか?

var maxs = values?.GroupBy(x => x.A).Select(x => x.OrderByDescending(m => m.B).First()).ToArray();
var maxs = values?.Aggregate(new Dictionary<int, M>(), (list, x) => { if (!list.TryGetValue(x.A, out var m) || x.B > m.B) list[x.A] = x; return list; }).Values.ToArray();

■ 全体

データ生成や出力まで含めた動くコードはこちらです。

// データ
const string json =
@"[
  {""A"": 1, ""B"": 1}
, {""A"": 2, ""B"": 2}
, {""A"": 2, ""B"": 3}
, {""A"": 3, ""B"": 3}
, {""A"": 3, ""B"": 4}
, {""A"": 3, ""B"": 5}
]";
var values = System.Text.Json.JsonSerializer.Deserialize<M[]>(json);

// A の値ごとの最大の B を求める
var maxs = values?.Aggregate(new Dictionary<int, M>(), (list, x) => { if (!list.TryGetValue(x.A, out var m) || x.B > m.B) list[x.A] = x; return list; }).Values.ToArray();

// 結果を出力
Console.WriteLine(string.Join(Environment.NewLine, maxs?.Select(x => $"{x.A}: {x.B}") ?? Enumerable.Empty<string>()));
// 実行結果
// 1: 1
// 2: 3
// 3: 5

// データクラス
record M(int A, int B);

■ C# っていいですよね

C# ってほんとにいいですよね。