{ }codelper
クエリ式(LINQ・ストリーム)レコードのリストP0082

都市別の注文集計

注文の記録をレコード(record)として作り、ループの代わりにクエリ式の処理(C# LINQ、Java ストリーム)で絞り込み、まとめ、集計せよ。
一行目に注文の数 n と基準金額 T、続く n 行に注文が "都市 客 金額" として一つずつ与えられる。
金額が T 以上の注文だけを残す。
残った注文を都市ごとにまとめ、都市ごとに "都市 注文数 合計 大口客" を出力する。
大口客はその都市で金額が最大の注文をした客で、最大の金額が複数あれば入力で先に出た注文の客である。
一行目に残った注文がある都市の数を出力し(残った注文がなければ 0 だけを出力)、続けて都市ごとに一行ずつ、合計の大きい都市から、合計が同じなら都市名の辞書順に出力せよ。

入力
1 ≤ n ≤ 5000、1 ≤ T ≤ 1,000,000,000、1 ≤ 金額 ≤ 1,000,000,000(整数)。
都市名と客の名前は英小文字 1〜10 文字
出力
一行目に都市の数 c、続けて c 行(合計は最大 5 兆まで増える)
例 1 · stdin
7 10
seoul mina 30
busan jun 9
seoul ari 50
daegu seo 80
busan hana 10
seoul bo 50
busan kim 70
stdout
3
seoul 3 130 ari
busan 2 80 kim
daegu 1 80 seo
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 2 言語の解答

この問題はクエリ式の処理が言語に組み込まれている C#(LINQ)と Java(ストリーム)だけを受け付けます。
同じことを C# は Where → GroupBy → Select → OrderByDescending → ThenBy、Java は filter → collect(groupingBy) → map → sorted と書きます。
落とし穴は三つです。
一つ目、合計は 10 億の注文 5000 件で 5 兆になるので long で足す必要があります。
どちらも int だと間違いますが、形が違います:Java の mapToInt(...).sum()・summingInt は例外なしに黙ってあふれておかしな数になり、C# LINQ の Sum は int でもあふれを検査して OverflowException を投げます(checked でない普通の + は C# でも黙ってあふれます)。
二つ目、グループが出てくる順です。
C# の GroupBy は最初に出た順にグループを出しますが、Java の groupingBy は HashMap なので順が決まっていません(TreeMap::new を渡せば名前順)。
どちらにしても「合計が同じなら名前順」を ThenBy(s => s.City)・thenComparing(CityStat::city) で自分で書く必要があります。
三つ目、「T 以上」は >= です。
大口客は C# MaxBy(.NET 6)、Java max(Comparator) がどちらも同じ値のうち先に出たものを返すので、そのまま使えます。
Java のストリームは一度しか使えないので、二回回るならリストに入れておきましょう。
Java の record と C# の record はどちらもフィールド・コンストラクタ・等価比較を一行で作ってくれます。

JavaMain.java · 45 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.Comparator;import java.util.List;import java.util.Map;import java.util.stream.Collectors; record Order(String city, String customer, long amount) {} record CityStat(String city, long count, long total, String top) {} public class Main {    public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        String[] first = in.readLine().trim().split(" ");        int n = Integer.parseInt(first[0]);        long t = Long.parseLong(first[1]);        List<Order> orders = new ArrayList<>();        for (int i = 0; i < n; i++) {            String[] p = in.readLine().trim().split(" ");            orders.add(new Order(p[0], p[1], Long.parseLong(p[2])));        }         // 걸러서(filter) → 도시로 묶고(groupingBy) → 묶음마다 셈하기        Map<String, List<Order>> byCity = orders.stream()            .filter(o -> o.amount() >= t)            .collect(Collectors.groupingBy(Order::city)); // HashMap: 차례가 정해져 있지 않다         List<CityStat> stats = byCity.entrySet().stream()            .map(e -> new CityStat(                e.getKey(),                e.getValue().size(),                e.getValue().stream().mapToLong(Order::amount).sum(), // mapToInt면 21억에서 넘친다                e.getValue().stream().max(Comparator.comparingLong(Order::amount)).get().customer()))            .sorted(Comparator.comparingLong(CityStat::total).reversed().thenComparing(CityStat::city))            .toList();         StringBuilder out = new StringBuilder().append(stats.size()).append('\n');        for (CityStat s : stats)            out.append(s.city()).append(' ').append(s.count()).append(' ').append(s.total()).append(' ').append(s.top()).append('\n');        System.out.print(out);    }}
C#Main.cs · 41 行
using System;using System.Collections.Generic;using System.Linq;using System.Text; record Order(string City, string Customer, long Amount); class Program{    static void Main()    {        string[] first = Console.ReadLine().Trim().Split(' ');        int n = int.Parse(first[0]);        long t = long.Parse(first[1]);        var orders = new List<Order>();        for (int i = 0; i < n; i++)        {            string[] p = Console.ReadLine().Trim().Split(' ');            orders.Add(new Order(p[0], p[1], long.Parse(p[2])));        }         // 걸러서(Where) → 도시로 묶고(GroupBy) → 묶음마다 셈하기(Select) → 정렬        var stats = orders            .Where(o => o.Amount >= t)            .GroupBy(o => o.City) // 처음 나온 차례로 묶음이 나온다            .Select(g => new            {                City = g.Key,                Count = g.Count(),                Total = g.Sum(o => o.Amount), // long이라 넘치지 않는다                Top = g.MaxBy(o => o.Amount)!.Customer, // 같으면 먼저 나온 것            })            .OrderByDescending(s => s.Total)            .ThenBy(s => s.City, StringComparer.Ordinal)            .ToList();         var sb = new StringBuilder().Append(stats.Count).Append('\n');        foreach (var s in stats) sb.Append($"{s.City} {s.Count} {s.Total} {s.Top}\n");        Console.Write(sb);    }}