{ }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()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 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와 크레이콘은 수로 바꾼 값을 "A 7 3" 같은 문자열 하나로 이어 열쇠로 쓰고, 정렬할 때 비교 함수를 넘겨요(rows.sort((a, b) => ...), 크레이콘 keys.정렬(차례)).
함정 하나: 입력에 적힌 문자열 그대로 열쇠를 만들면 07과 7이 다른 열쇠가 돼요.
예시의 A 7 03 6과 A 07 3 -1이 묶이지 않고 두 줄로 나와요.
수로 바꾼 뒤에 열쇠를 만들어요.
함정 둘: JS Map의 열쇠로 배열 [zone, floor, cell]을 쓰면 내용이 같아도 서로 다른 열쇠라서 하나도 묶이지 않아요.
예시의 기록 9개가 9줄로 나와요.
함정 셋: 층과 칸은 수로 비교해요.
문자열로 비교하면 "7"이 "10"보다 커서 예시의 A 7 3이 A 10 1보다 먼저 나와요.
크레이콘에서 문자열 열쇠를 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·크레이콘의 <, 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];}