{ }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。
有三个陷阱。
第一,5000 笔 10 亿的订单合计 5 万亿,必须用 long 相加。
两者用 int 都会出错,但表现不同:Java 的 mapToInt(...).sum()、summingInt 会不抛异常地悄悄溢出,得出离谱的数;C# LINQ 的 Sum 即使对 int 也检查溢出,会抛出 OverflowException(C# 中非 checked 的普通 + 也会悄悄溢出)。
第二,分组出现的顺序。
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);    }}