창고의 재고 장부에 칸마다 물건이 들어오고 나간 기록이 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
오류 목록00F8 ↓ · ⇧F8 ↑
입력(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 sysdata = 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];}