{ }codelper
按键排序自定义排序更新字典P0394

整理仓库各格的库存账本

仓库的库存账本上有 n 行记录,记下每个格子里货物的进出。
每行给出区名、楼层、格子编号和变化量,用一个空格隔开。
区名由英文字母组成,区分大小写。
楼层是不为 0 的整数,地下楼层是前面带 '-' 的负数。
楼层、格子编号和变化量前面可能带有 0,变化量前面可能带有 '+'。
所以 "A 07 3" 和 "A 7 03" 是同一个格子。
把区名、楼层、格子编号都相同的记录的变化量加起来。
和为 0 的格子不输出。
其余的格子按区名升序输出,区相同时楼层高的在前,楼层也相同时格子编号小的在前。
区名从前往后按第一个不同字符的字符编码比较,一个名字与另一个名字的开头部分相同时,短的名字在前。
因此所有大写字母都在小写字母之前,"A" 在 "AB" 之前,"AB" 在 "B" 之前。
每行写出区名、楼层、格子编号和总和,用一个空格隔开,数的前面不加 0 或 '+'。
最后一行输出已输出的格子数和所有变化量的总和,用一个空格隔开;没有输出任何格子时只输出这一行。

输入
第一行是 n(1 ≤ n ≤ 5,000)。
接下来 n 行,每行是区名(1~3 个英文字母)、楼层(−99 到 999,不为 0)、格子编号(1 到 9,999)、变化量(−100,000 到 100,000)。
每个数去掉符号后有 1~7 个数字
输出
每个总和不为 0 的格子一行“区 楼层 格子 总和”,最后一行“格子数 全部总和”
样例 1 · stdin
9
B 2 5 +10
A 10 1 4
A 7 03 6
a 1 1 2
A 07 3 -1
A -1 12 3
B 2 5 -10
A 10 01 +1
AB 1 1 7
stdout
A 10 1 5
A 7 3 5
A -1 12 3
AB 1 1 7
a 1 1 2
5 22
main.py
输入(stdin)

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

$ 就绪

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

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

这道题要建立一个键由三部分组成的字典,并且每一部分按不同的方向排序。
Python 直接用元组 (zone, int(floor), int(cell)) 作键,像 sort(key=lambda k: (k[0], -k[1], k[2])) 这样只把楼层的符号反过来。
C# 也用元组作 Dictionary 的键,再用 OrderBy(...).ThenByDescending(...).ThenBy(...) 连起来。
Java 用 record Key 作键,把比较规则传给 TreeMap,放进去时就按这个顺序排好。
C++ std::map<std::tuple<std::string, int, int>, int> 也是有序的,但不能选择方向,所以把楼层存成 -floor。
C 没有字典,要用 qsort 把记录排序,让相同的格子挨在一起再合并。
JS/TS 和 Craykon 把转成数的值连成 "A 7 3" 这样的一个字符串作键,排序时传入比较函数(rows.sort((a, b) => ...),Craykon keys.排序(顺序))。
陷阱一:直接用输入中的字符串作键,07 和 7 就成了不同的键。
示例的 A 7 03 6 和 A 07 3 -1 不会合并,输出成两行。
要先转成数再作键。
陷阱二:用数组 [zone, floor, cell] 作 JS Map 的键,内容相同也是不同的键,一个都不会合并。
示例的 9 条记录会输出 9 行。
陷阱三:楼层和格子要按数比较。
按字符串比较时 "7" 比 "10" 大,示例的 A 7 3 会排在 A 10 1 前面。
在 Craykon 中用 keys.排序() 给字符串键排序,楼层也会按字符顺序排列,示例的 A 区会按 -1、10、7 的顺序输出。
陷阱四:只有楼层是降序。
Python sort(reverse=True) 会把整个排序反过来,区也变成降序(示例中 a 排在最前面);在 Java 中写 comparing(Key::zone).thenComparingInt(Key::floor).reversed(),reversed() 会把前面连起来的整个比较反过来,区同样会倒过来。
要像 thenComparing(Key::floor, Comparator.reverseOrder()) 这样只作用在楼层上。
把楼层原样存进 C++ std::map 再遍历,会从低楼层开始输出,示例的 A -1 12 3 排在最前面。
陷阱五:区名按字符编码比较。
Python、JS/TS、Craykon 的 <,Java compareTo,C strcmp,C++ std::string 的 < 都是字符编码顺序。
但是 C# OrderBy(k => k.zone) 的默认比较和 JS localeCompare 是词典顺序,示例的 a 甚至会排在 A 前面。
C# 要传入 StringComparer.Ordinal。
陷阱六:不去掉总和变为 0 的格子,示例中会输出 B 2 5 0,格子数变成 6。

Pythonmain.py · 15 行
import sys data = sys.stdin.read().split("\n")n = int(data[0])total = {}for line in data[1:n + 1]:    zone, floor, cell, delta = line.split()    key = (zone, int(floor), int(cell))      # 07과 7이 같은 열쇠가 되도록 수로 바꿔 묶는다    total[key] = total.get(key, 0) + int(delta) keys = [k for k in total if total[k] != 0]   # 합이 0인 칸은 빼고keys.sort(key=lambda k: (k[0], -k[1], k[2])) # 구역 오름차순, 층 내림차순, 칸 오름차순out = [f"{z} {f} {c} {total[(z, f, c)]}" for z, f, c in keys]out.append(f"{len(keys)} {sum(total.values())}")print("\n".join(out))
JavaScriptmain.js · 25 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const total = new Map();for (let i = 1; i <= n; i++) {  const [zone, floor, cell, delta] = lines[i].split(" ");  // 배열은 열쇠로 쓸 수 없으니(같은 내용이어도 다른 열쇠) 수로 바꾼 뒤 문자열 하나로 만든다  const key = `${zone} ${Number(floor)} ${Number(cell)}`;  total.set(key, (total.get(key) ?? 0) + Number(delta));}const rows = [];let grand = 0;for (const [key, sum] of total) {  grand += sum;  if (sum === 0) continue; // 합이 0인 칸은 뺀다  const [zone, floor, cell] = key.split(" ");  rows.push({ zone, floor: Number(floor), cell: Number(cell), sum });}rows.sort((a, b) => {  if (a.zone !== b.zone) return a.zone < b.zone ? -1 : 1; // 구역: 글자 코드 순  if (a.floor !== b.floor) return b.floor - a.floor; // 층: 높은 층 먼저  return a.cell - b.cell; // 칸: 작은 번호 먼저});const out = rows.map((r) => `${r.zone} ${r.floor} ${r.cell} ${r.sum}`);out.push(`${rows.length} ${grand}`);console.log(out.join("\n"));
TypeScriptmain.ts · 27 行
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const total = new Map<string, number>();for (let i = 1; i <= n; i++) {  const [zone, floor, cell, delta] = lines[i].split(" ");  // 배열은 열쇠로 쓸 수 없으니(같은 내용이어도 다른 열쇠) 수로 바꾼 뒤 문자열 하나로 만든다  const key = `${zone} ${Number(floor)} ${Number(cell)}`;  total.set(key, (total.get(key) ?? 0) + Number(delta));}const rows: { zone: string; floor: number; cell: number; sum: number }[] = [];let grand = 0;for (const [key, sum] of total) {  grand += sum;  if (sum === 0) continue; // 합이 0인 칸은 뺀다  const [zone, floor, cell] = key.split(" ");  rows.push({ zone, floor: Number(floor), cell: Number(cell), sum });}rows.sort((a, b) => {  if (a.zone !== b.zone) return a.zone < b.zone ? -1 : 1; // 구역: 글자 코드 순  if (a.floor !== b.floor) return b.floor - a.floor; // 층: 높은 층 먼저  return a.cell - b.cell; // 칸: 작은 번호 먼저});const out = rows.map((r) => `${r.zone} ${r.floor} ${r.cell} ${r.sum}`);out.push(`${rows.length} ${grand}`);console.log(out.join("\n"));
JavaMain.java · 41 行
import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.Comparator;import java.util.Map;import java.util.StringTokenizer;import java.util.TreeMap; public class Main {    record Key(String zone, int floor, int cell) {}     public static void main(String[] args) throws Exception {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(in.readLine().trim());        // 구역 오름차순, 층 내림차순, 칸 오름차순. 층에만 reverseOrder를 건다        Comparator<Key> order = Comparator.comparing(Key::zone)                .thenComparing(Key::floor, Comparator.reverseOrder())                .thenComparingInt(Key::cell);        TreeMap<Key, Integer> total = new TreeMap<>(order);        for (int i = 0; i < n; i++) {            StringTokenizer st = new StringTokenizer(in.readLine());            String zone = st.nextToken();            int floor = Integer.parseInt(st.nextToken());            int cell = Integer.parseInt(st.nextToken());            int delta = Integer.parseInt(st.nextToken());            total.merge(new Key(zone, floor, cell), delta, Integer::sum);        }        StringBuilder out = new StringBuilder();        int count = 0;        int grand = 0;        for (Map.Entry<Key, Integer> e : total.entrySet()) {            int sum = e.getValue();            grand += sum;            if (sum == 0) continue;            // 합이 0인 칸은 뺀다            Key k = e.getKey();            out.append(k.zone()).append(' ').append(k.floor()).append(' ').append(k.cell()).append(' ').append(sum).append('\n');            count++;        }        out.append(count).append(' ').append(grand).append('\n');        System.out.print(out);    }}
C#Main.cs · 32 行
using System;using System.Collections.Generic;using System.Linq;using System.Text; class Program{    static void Main()    {        int n = int.Parse(Console.ReadLine()!.Trim());        var total = new Dictionary<(string zone, int floor, int cell), int>();        for (int i = 0; i < n; i++)        {            string[] p = Console.ReadLine()!.Split(' ');            var key = (p[0], int.Parse(p[1]), int.Parse(p[2]));            total[key] = total.GetValueOrDefault(key) + int.Parse(p[3]);        }        // 문자열은 꼭 StringComparer.Ordinal로: 기본 비교는 문화권 순서라 a가 B보다 앞에 온다        var keys = total.Keys.Where(k => total[k] != 0)            .OrderBy(k => k.zone, StringComparer.Ordinal)            .ThenByDescending(k => k.floor)            .ThenBy(k => k.cell)            .ToList();        var sb = new StringBuilder();        foreach (var k in keys)        {            sb.Append(k.zone).Append(' ').Append(k.floor).Append(' ').Append(k.cell).Append(' ').Append(total[k]).Append('\n');        }        sb.Append(keys.Count).Append(' ').Append(total.Values.Sum()).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 28 行
#include <iostream>#include <map>#include <string>#include <tuple> int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    int n;    std::cin >> n;    // std::map은 열쇠를 모두 오름차순으로 둔다. 층만 내림차순이 되도록 부호를 뒤집어 넣는다    std::map<std::tuple<std::string, int, int>, int> total;    for (int i = 0; i < n; i++) {        std::string zone;        int floor, cell, delta;        std::cin >> zone >> floor >> cell >> delta;        total[{zone, -floor, cell}] += delta;    }    int count = 0, grand = 0;    for (const auto& [key, sum] : total) {        grand += sum;        if (sum == 0) continue;            // 합이 0인 칸은 뺀다        std::cout << std::get<0>(key) << ' ' << -std::get<1>(key) << ' ' << std::get<2>(key) << ' ' << sum << '\n';        count++;    }    std::cout << count << ' ' << grand << '\n';    return 0;}
Cmain.c · 43 行
#include <stdio.h>#include <stdlib.h>#include <string.h> typedef struct {    char zone[4];    int floor, cell, delta;} Rec; /* 구역 오름차순, 층 내림차순, 칸 오름차순 */static int cmp(const void *pa, const void *pb) {    const Rec *a = pa, *b = pb;    int z = strcmp(a->zone, b->zone);    if (z != 0) return z;    if (a->floor != b->floor) return a->floor > b->floor ? -1 : 1;    if (a->cell != b->cell) return a->cell < b->cell ? -1 : 1;    return 0;} int main(void) {    static Rec r[5000];    int n;    if (scanf("%d", &n) != 1) return 0;    for (int i = 0; i < n; i++) {        /* %d는 앞에 붙은 0과 '+'를 그대로 10진수로 읽는다 */        if (scanf("%3s %d %d %d", r[i].zone, &r[i].floor, &r[i].cell, &r[i].delta) != 4) return 0;    }    /* 사전이 없으니 정렬해서 같은 칸을 이웃하게 만든 뒤 묶는다 */    qsort(r, (size_t)n, sizeof r[0], cmp);    int count = 0, grand = 0;    for (int i = 0; i < n;) {        int j = i, sum = 0;        while (j < n && cmp(&r[i], &r[j]) == 0) sum += r[j++].delta;        grand += sum;        if (sum != 0) {                 /* 합이 0인 칸은 뺀다 */            printf("%s %d %d %d\n", r[i].zone, r[i].floor, r[i].cell, sum);            count++;        }        i = j;    }    printf("%d %d\n", count, grand);    return 0;}
Craykonmain.cray · 57 行
n = 控制台.读行().转数字();total = 字典();parts = 字典();grand = 0;循环 (i = 0; i < n; i++){    p = 控制台.读行().分割();    floor = p[1].转数字();    cell = p[2].转数字();    delta = p[3].转数字();    // 07과 7이 같은 열쇠가 되도록 수로 바꾼 뒤 문자열 하나로 만든다    key = p[0] + " " + floor + " " + cell;    如果 (total.包含(key))    {        total[key] += delta;    }    否则    {        total[key] = delta;        parts[key] = [p[0], floor, cell];    }    grand += delta;}keys = [];遍历 (key 在 total.键列表){    如果 (total[key] != 0)    {        keys.添加(key);    }}keys.排序(차례);遍历 (key 在 keys){    控制台.输出(key + " " + total[key]);}控制台.输出(keys.数量 + " " + grand); // 구역 오름차순, 층 내림차순, 칸 오름차순차례(앞, 뒤){    a = parts[앞];    b = parts[뒤];    如果 (a[0] != b[0])    {        如果 (a[0] < b[0])        {            返回 -1;        }        返回 1;    }    如果 (a[1] != b[1])    {        返回 b[1] - a[1];    }    返回 a[2] - b[2];}