{ }codelper
キー順条件つき並べ替え辞書の更新P0394

倉庫の棚ごとの在庫台帳を並べる

倉庫の在庫台帳に、棚ごとに品物が出入りした記録が n 行ある。
各行には区画名、階、棚番号、変化量が空白 1 つずつで区切って与えられる。
区画名は英字で、大文字と小文字を区別する。
階は 0 でない整数で、地下の階は先頭に '-' が付いた負の数である。
階、棚番号、変化量は先頭に 0 が付いていることがあり、変化量には '+' が付いていることがある。
したがって "A 07 3" と "A 7 03" は同じ棚である。
区画名、階、棚番号がすべて同じ記録どうしで変化量を足す。
合計が 0 の棚は出力しない。
残りの棚を区画名の昇順に、区画が同じなら階が高いものから、階も同じなら棚番号が小さいものから出力せよ。
区画名は前から見て最初に異なる文字の文字コードで比べ、一方の名前がもう一方の先頭部分と同じなら短い名前が前である。
したがって大文字はすべて小文字より前であり、"A" は "AB" より前、"AB" は "B" より前である。
1 行に区画名、階、棚番号、合計を空白 1 つで区切って書き、数には先頭の 0 や '+' を付けない。
最後の行には、出力した棚の数とすべての変化量の合計を空白 1 つで区切って出力し、出力した棚がなければこの行だけを出力せよ。

入力
最初の行に n(1 ≤ n ≤ 5,000)。
続く n 行に区画名(英字 1〜3 文字)、階(−99 以上 999 以下、0 ではない)、棚番号(1 以上 9,999 以下)、変化量(−100,000 以上 100,000 以下)。
各数は符号を除いて数字が 1〜7 個である
出力
合計が 0 でない棚ごとに「区画 階 棚 合計」を 1 行、最後の行に「棚の数 全体の合計」
例 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 言語の解答

キーが 3 つの部分からなる辞書を作り、部分ごとに違う向きで並べる問題です。
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" のような 1 つの文字列につないでキーにし、並べ替えるときに比較関数を渡します(rows.sort((a, b) => ...)、Craykon keys.並べ替え(順序))。
落とし穴その一:入力に書かれた文字列のままキーを作ると、07 と 7 が別のキーになります。
例の A 7 03 6 と A 07 3 -1 がまとまらず、2 行で出ます。
数に変えてからキーを作ります。
落とし穴その二:JS Map のキーに配列 [zone, floor, cell] を使うと、中身が同じでも別のキーなので 1 つもまとまりません。
例の 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];}