{ }codelper
查询式处理(LINQ、流)记录分组P0122

每关前 k 名

把游戏记录写成记录类型(record),不用循环,而用查询式处理(C# LINQ、Java 流)来分组、计数和排序。
第一行是记录数 n 和 k,接下来 n 行每行一条记录,形如 "关卡 玩家 分数"。
一个玩家可能多次玩同一关。
每一关中,玩家的分数取他在该关得到的最高分。
每一关中,按分数从高到低排列玩家;分数相同时按名字的编码顺序(ASCII,大写字母在小写字母之前)排列。
第一行输出关卡数。
然后按关卡名的编码顺序,每关输出一行 "关卡 玩家数 名字:分数 ..."。
玩家数是玩过这一关的不同玩家的个数,后面用空格分隔写出排在最前的 k 名。
若玩家少于 k 名,则全部写出。
名字区分大小写,Ann 和 ann 是不同的玩家。

输入
1 ≤ n ≤ 5000,1 ≤ k ≤ 10。
关卡名为 1 到 8 个小写英文字母,不同关卡不超过 100 个。
玩家名为 1 到 8 个大小写英文字母。
0 ≤ 分数 ≤ 1,000,000,000(整数)
输出
关卡数,然后每关一行
样例 1 · stdin
6 2
cave ann 50
sky Bob 7
cave bob 70
cave ann 90
sky adam 7
cave Ann 70
stdout
2
cave 3 ann:90 Ann:70
sky 2 Bob:7 adam:7
样例 2 · stdin
5 3
moon zed 4
beach Zed 4
moon Zed 9
moon zed 9
moon eve 1
stdout
2
beach 1 Zed:4
moon 3 Zed:9 zed:9 eve:1
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 2 种语言题解

本题只接受语言内置查询式处理的 C#(LINQ)和 Java(流)。
需要在分组里再分组。
C# 在 GroupBy(关卡) 里再用 GroupBy(玩家) 和 Max;Java 像 groupingBy(关卡, TreeMap::new, toMap(玩家, 分数, Math::max)) 那样嵌套收集器(collector)。
取前 k 个用 C# Take(k)、Java limit(k),k 更大时有多少给多少。
五个陷阱:① 不按玩家再分组、直接对记录排序,同一个玩家会多次进入排名。
② Java groupingBy 默认用 HashMap,关卡的顺序不确定(要传 TreeMap::new)。
C# GroupBy 按首次出现的顺序给出分组,所以另外需要 OrderBy(g => g.Key)。
③ 在 Java 中像 comparingInt(Best::score).thenComparing(Best::player).reversed() 那样把 reversed() 放在最后,会把之前构造的整个比较反转,名字顺序也反了。
应像 comparingInt(Best::score).reversed().thenComparing(Best::player) 那样紧跟在分数后面。
④ 在 C# 中 OrderByDescending 之后再写一个 OrderBy,会丢掉前面的排序、从头重新排序。
第二个排序键要用 ThenBy。
⑤ C# OrderBy、ThenBy 比较字符串的默认方式是区域文化规则,adam 会排在 Bob 前面。
本题用编码顺序(大写在前),所以要传 StringComparer.Ordinal。
Java 的 String.compareTo 本来就是编码顺序。

JavaMain.java · 44 行
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.TreeMap;import java.util.stream.Collectors; record Play(String stage, String player, int score) {} record Best(String player, int score) {} 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]), k = Integer.parseInt(first[1]);        List<Play> plays = new ArrayList<>();        for (int i = 0; i < n; i++) {            String[] p = in.readLine().trim().split(" ");            plays.add(new Play(p[0], p[1], Integer.parseInt(p[2])));        }         // 판으로 묶고(TreeMap: 판 이름 순서) → 판 안에서 선수마다 가장 높은 점수(toMap + Math::max)        Map<String, Map<String, Integer>> best = plays.stream()            .collect(Collectors.groupingBy(Play::stage, TreeMap::new,                Collectors.toMap(Play::player, Play::score, Math::max)));         StringBuilder out = new StringBuilder().append(best.size()).append('\n');        best.forEach((stage, players) -> {            String top = players.entrySet().stream()                .map(e -> new Best(e.getKey(), e.getValue()))                .sorted(Comparator.comparingInt(Best::score).reversed() // 점수만 거꾸로                    .thenComparing(Best::player))                       // 이름은 그대로                .limit(k)                .map(b -> b.player() + ":" + b.score())                .collect(Collectors.joining(" "));            out.append(stage).append(' ').append(players.size()).append(' ').append(top).append('\n');        });        System.out.print(out);    }}
C#Main.cs · 42 行
using System;using System.Collections.Generic;using System.Linq;using System.Text; record Play(string Stage, string Player, int Score); class Program{    static void Main()    {        string[] first = Console.ReadLine()!.Trim().Split(' ');        int n = int.Parse(first[0]), k = int.Parse(first[1]);        var plays = new List<Play>();        for (int i = 0; i < n; i++)        {            string[] p = Console.ReadLine()!.Trim().Split(' ');            plays.Add(new Play(p[0], p[1], int.Parse(p[2])));        }         // 판으로 묶고 → 판 안에서 다시 선수로 묶어 가장 높은 점수 → 정렬해서 k명        var stages = plays            .GroupBy(r => r.Stage)            .OrderBy(g => g.Key, StringComparer.Ordinal) // 문화권 규칙이 아니라 코드 순서로            .Select(g =>            {                var ranked = g                    .GroupBy(r => r.Player)                    .Select(pg => (Player: pg.Key, Best: pg.Max(r => r.Score)))                    .OrderByDescending(x => x.Best)                    .ThenBy(x => x.Player, StringComparer.Ordinal)                    .ToList();                return (Stage: g.Key, Players: ranked.Count, Top: ranked.Take(k).Select(x => $"{x.Player}:{x.Best}"));            })            .ToList();         var sb = new StringBuilder().Append(stages.Count).Append('\n');        foreach (var s in stages)            sb.Append(s.Stage).Append(' ').Append(s.Players).Append(' ').Append(string.Join(" ", s.Top)).Append('\n');        Console.Write(sb);    }}