{ }codelper
빈 값(null)P0286

세 번 건너 찾는 전기함 담당자

야영장에 손님 n명, 자리 m개, 전기함 k개가 있고, 저마다 1번부터 번호가 붙어 있다. 표가 세 개 있다. 첫째 표에는 손님마다 배정된 자리의 번호가 적혀 있다. 둘째 표에는 자리마다 연결된 전기함의 번호가 적혀 있다. 셋째 표에는 전기함마다 담당 직원의 이름이 적혀 있다. 세 표 모두, 정해지지 않은 칸에는 번호나 이름 대신 -(빼기 기호 하나)가 적혀 있다. 전기가 나갔다는 손님의 번호가 q번 주어진다. 그때마다 손님 → 자리 → 전기함 → 담당 직원의 차례로 표를 따라가서, 끝까지 닿으면 담당 직원의 이름을 출력하라. 가는 길에 - 를 만나면 거기서 멈추고, - 와 끊긴 단계의 번호를 빈칸 하나로 띄워 출력하라. 손님에게 자리가 없으면 1단계, 그 자리에 전기함이 없으면 2단계, 그 전기함에 담당 직원이 없으면 3단계다. 여러 손님이 같은 자리를 쓸 수 있고, 여러 자리가 같은 전기함에 연결될 수 있다.

입력
첫 줄에 n, m, k, q가 빈칸 하나로 띄워 주어진다(1 ≤ n, m, k, q ≤ 100).
둘째 줄에 1번 손님부터 차례로 자리 번호(1 이상 m 이하) 또는 - 가 n개, 셋째 줄에 1번 자리부터 차례로 전기함 번호(1 이상 k 이하) 또는 - 가 m개, 넷째 줄에 1번 전기함부터 차례로 담당 직원의 이름(영어 소문자 1~8글자) 또는 - 가 k개, 저마다 빈칸 하나로 띄워 주어진다.
다음 q줄에 손님 번호(1 이상 n 이하)가 하나씩 주어진다.
출력
q줄.
줄마다 담당 직원의 이름, 또는 "- 단계 번호"(단계 번호는 1, 2, 3 가운데 하나)
예시 1 · stdin
5 4 3 6
2 - 4 1 3
1 3 - 2
dana - miro
1
2
3
4
5
1
stdout
miro
- 1
- 3
dana
- 2
miro
main.py
입력(stdin)

실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

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

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

찾은 값을 다음 표의 번호로 다시 쓰는 문제라서, '없음'을 번호로 쓰면 어떻게 되는지가 언어마다 달라요.
없음을 담는 법: Python None, JS·TS null, Java Integer와 String의 null(int에는 못 담아요), C# int?, C++ std::optional<int>, 크레이콘 빈값.
C의 int에는 없음이 없어서 번호로 쓰지 않는 0을 없음으로 정해 쓰고, 이름은 포인터라서 NULL을 쓸 수 있어요.
없음을 번호로 쓰면: Python box_of[None]은 TypeError, Java는 null인 Integer를 번호로 쓰거나 int에 담는 순간 NullPointerException, 크레이콘은 전기함표[빈값]에서 프로그램이 멈춰요(빈값으로 계산해도 멈춰요).
TS는 엄격 모드(strict)에서 number | null을 번호로 쓰면 컴파일 오류(TS2538)라서 === null 검사를 빼먹을 수 없고, C#도 int?를 그대로 번호로 쓰면 컴파일 오류라 .Value로 꺼내야 해요.
반대로 JS boxOf[null]은 오류 없이 undefined를 줘요.
그래서 어느 언어든 '찾기 → 없음인지 확인 → 다음 찾기' 차례로 써요.
없음을 끝까지 흘려보내는 도구도 있어요.
Java Optional.ofNullable(siteOf[g]).map(s -> boxOf[s]).map(b -> staffOf[b])는 중간에 null이 나오면 빈 Optional로 끝나요.
다만 어느 단계에서 끊겼는지는 알려 주지 않아서, 이 문제에서는 단계마다 물어야 해요.
JS·TS·C#의 ?.는 점 앞의 것이 없을 때를 막아 줄 뿐, 대괄호 안의 번호가 없는 것은 막지 못해요.
함정 일곱: ① JS에서 세 값을 먼저 다 찾으면 앞 단계가 비었을 때 뒤의 값은 null이 아니라 undefined예요.
=== null로는 걸리지 않아요(== null은 둘 다 걸러요).
② Python에서 없음을 -1로 적어 두면 box_of[-1]이 오류 없이 맨 끝 칸을 읽어요.
③ Java에서 int site = siteOf[g]처럼 null을 int에 담으면 0이 되는 게 아니라 예외가 나요.
④ C# GetValueOrDefault()는 null을 0으로 바꿔서, 비어 있는 단계를 지나친 뒤 다음 단계에서 끊긴 것처럼 보이게 해요.
⑤ C에서 0번 칸도 없음(0)이면 없음이 끝까지 번져요.
끝에서부터 물으면 어디서 끊겼든 3단계라고 답하니, 앞 단계부터 물어요.
⑥ C++에서 빈 optional에 .value()를 부르면 std::bad_optional_access 예외가 나고, *를 쓰면 정의되지 않은 동작이에요.
⑦ 크레이콘에서 확인보다 찾기를 먼저 쓰면 빈값을 번호로 쓰는 줄에서 멈춰요.

Pythonmain.py · 25 줄
def read_row(convert):    # "-"는 None으로, 나머지는 convert로 바꾼다. 맨 앞에 None을 하나 두어 번호 1이 1번 칸이 되게 한다    return [None] + [None if word == "-" else convert(word) for word in input().split()]  n, m, k, q = map(int, input().split())site_of = read_row(int)   # 손님 → 자리box_of = read_row(int)    # 자리 → 전기함staff_of = read_row(str)  # 전기함 → 담당자 for _ in range(q):    guest = int(input())    site = site_of[guest]    if site is None:        print("- 1")        continue    box = box_of[site]  # site가 None이면 box_of[None]은 TypeError라서, 위에서 먼저 걸렀다    if box is None:        print("- 2")        continue    staff = staff_of[box]    if staff is None:        print("- 3")    else:        print(staff)
JavaScriptmain.js · 28 줄
const lines = require("fs").readFileSync(0, "utf8").split("\n");const q = Number(lines[0].split(" ")[3]); // "-"는 null로 바꾼다. 맨 앞에 null을 하나 두어 번호 1이 1번 칸이 되게 한다function readRow(line, convert) {  return [null, ...line.split(" ").map((word) => (word === "-" ? null : convert(word)))];}const siteOf = readRow(lines[1], Number); // 손님 → 자리const boxOf = readRow(lines[2], Number); // 자리 → 전기함const staffOf = readRow(lines[3], String); // 전기함 → 담당자 const out = [];for (let i = 0; i < q; i++) {  const guest = Number(lines[4 + i]);  const site = siteOf[guest];  if (site === null) {    out.push("- 1");    continue;  }  const box = boxOf[site]; // boxOf[null]은 오류 없이 undefined가 되므로, 위에서 먼저 걸렀다  if (box === null) {    out.push("- 2");    continue;  }  const staff = staffOf[box];  out.push(staff === null ? "- 3" : staff);}console.log(out.join("\n"));
TypeScriptmain.ts · 30 줄
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const q = Number(lines[0].split(" ")[3]); // "-"는 null로 바꾼다. 맨 앞에 null을 하나 두어 번호 1이 1번 칸이 되게 한다function readRow<T>(line: string, convert: (word: string) => T): (T | null)[] {  return [null, ...line.split(" ").map((word) => (word === "-" ? null : convert(word)))];}const siteOf: (number | null)[] = readRow(lines[1], Number); // 손님 → 자리const boxOf: (number | null)[] = readRow(lines[2], Number); // 자리 → 전기함const staffOf: (string | null)[] = readRow(lines[3], String); // 전기함 → 담당자 const out: string[] = [];for (let i = 0; i < q; i++) {  const guest = Number(lines[4 + i]);  const site = siteOf[guest];  if (site === null) {    out.push("- 1");    continue;  }  // 여기서부터 site는 number다. 위의 검사를 빼면 boxOf[site]는 컴파일 오류(null은 번호로 쓸 수 없다)가 된다  const box = boxOf[site];  if (box === null) {    out.push("- 2");    continue;  }  const staff = staffOf[box];  out.push(staff === null ? "- 3" : staff);}console.log(out.join("\n"));
JavaMain.java · 45 줄
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader; public class Main {    // int에는 null을 담을 수 없어서 Integer를 쓴다. "-"는 null로 두고, 번호 1이 1번 칸이 되게 한 칸 밀어 담는다    static Integer[] readNumbers(String line) {        String[] words = line.split(" ");        Integer[] row = new Integer[words.length + 1];        for (int i = 0; i < words.length; i++) {            row[i + 1] = words[i].equals("-") ? null : Integer.valueOf(words[i]);        }        return row;    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int q = Integer.parseInt(in.readLine().split(" ")[3]);        Integer[] siteOf = readNumbers(in.readLine()); // 손님 → 자리        Integer[] boxOf = readNumbers(in.readLine()); // 자리 → 전기함        String[] words = in.readLine().split(" ");        String[] staffOf = new String[words.length + 1]; // 전기함 → 담당자        for (int i = 0; i < words.length; i++) {            staffOf[i + 1] = words[i].equals("-") ? null : words[i];        }         StringBuilder out = new StringBuilder();        for (int i = 0; i < q; i++) {            int guest = Integer.parseInt(in.readLine().trim());            Integer site = siteOf[guest];            if (site == null) {                out.append("- 1\n");                continue;            }            Integer box = boxOf[site]; // site가 null이면 번호로 쓰는 순간 NullPointerException이 난다            if (box == null) {                out.append("- 2\n");                continue;            }            String staff = staffOf[box];            out.append(staff == null ? "- 3" : staff).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 51 줄
using System;using System.Text; class Program{    // int? 는 '수 또는 null'이다. "-"는 null로 두고, 번호 1이 1번 칸이 되게 한 칸 밀어 담는다    static int?[] ReadNumbers(string line)    {        string[] words = line.Split(' ');        var row = new int?[words.Length + 1];        for (int i = 0; i < words.Length; i++)        {            row[i + 1] = words[i] == "-" ? (int?)null : int.Parse(words[i]);        }        return row;    }     static void Main()    {        int q = int.Parse(Console.ReadLine().Split(' ')[3]);        int?[] siteOf = ReadNumbers(Console.ReadLine()); // 손님 → 자리        int?[] boxOf = ReadNumbers(Console.ReadLine());  // 자리 → 전기함        string[] words = Console.ReadLine().Split(' ');        var staffOf = new string[words.Length + 1];      // 전기함 → 담당자        for (int i = 0; i < words.Length; i++)        {            staffOf[i + 1] = words[i] == "-" ? null : words[i];        }         var sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            int guest = int.Parse(Console.ReadLine());            int? site = siteOf[guest];            if (site == null)            {                sb.Append("- 1\n");                continue;            }            int? box = boxOf[site.Value]; // int? 는 그대로 번호로 쓸 수 없다. 값이 있음을 확인한 뒤 .Value로 꺼낸다            if (box == null)            {                sb.Append("- 2\n");                continue;            }            string staff = staffOf[box.Value];            sb.Append(staff ?? "- 3").Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 45 줄
#include <iostream>#include <optional>#include <string>#include <vector> // std::optional<int>는 '수가 있거나 비어 있거나'다. "-"는 빈 optional로 두고, 번호 1이 1번 칸이 되게 한다std::vector<std::optional<int>> readNumbers(int count) {    std::vector<std::optional<int>> row(count + 1);    for (int i = 1; i <= count; i++) {        std::string word;        std::cin >> word;        if (word != "-") row[i] = std::stoi(word);    }    return row;} int main() {    int n, m, k, q;    std::cin >> n >> m >> k >> q;    auto siteOf = readNumbers(n);  // 손님 → 자리    auto boxOf = readNumbers(m);   // 자리 → 전기함    std::vector<std::optional<std::string>> staffOf(k + 1);  // 전기함 → 담당자    for (int i = 1; i <= k; i++) {        std::string word;        std::cin >> word;        if (word != "-") staffOf[i] = word;    }    for (int i = 0; i < q; i++) {        int guest;        std::cin >> guest;        std::optional<int> site = siteOf[guest];        if (!site) {            std::cout << "- 1\n";            continue;        }        std::optional<int> box = boxOf[*site];  // 비어 있는 optional에 *를 쓰면 정의되지 않은 동작이라, 먼저 확인했다        if (!box) {            std::cout << "- 2\n";            continue;        }        const std::optional<std::string>& staff = staffOf[*box];        if (!staff) std::cout << "- 3\n";        else std::cout << *staff << '\n';    }}
Cmain.c · 44 줄
#include <stdio.h>#include <stdlib.h>#include <string.h> /* C의 int에는 '없음'이 없다. 번호가 1부터라서 0을 '없음'으로 정해 쓴다 */static void read_numbers(int *row, int count) {    char word[16];    for (int i = 1; i <= count; i++) {        scanf("%15s", word);        row[i] = strcmp(word, "-") == 0 ? 0 : atoi(word);    }} int main(void) {    int n, m, k, q;    scanf("%d %d %d %d", &n, &m, &k, &q);    static int site_of[101], box_of[101];    static char names[101][16];    static const char *staff_of[101]; /* 포인터에는 '없음'이 있다: NULL */    read_numbers(site_of, n);    read_numbers(box_of, m);    for (int i = 1; i <= k; i++) {        scanf("%15s", names[i]);        staff_of[i] = strcmp(names[i], "-") == 0 ? NULL : names[i];    }    for (int i = 0; i < q; i++) {        int guest;        scanf("%d", &guest);        int site = site_of[guest];        if (site == 0) {            puts("- 1");            continue;        }        int box = box_of[site];        if (box == 0) {            puts("- 2");            continue;        }        const char *staff = staff_of[box];        if (staff == NULL) puts("- 3"); /* NULL을 puts에 넘기면 정의되지 않은 동작이다 */        else puts(staff);    }    return 0;}
Craykonmain.cray · 59 줄
// "-"는 빈값으로 바꾼다. 맨 앞에 빈값을 하나 두어 번호 1이 1번 칸이 되게 한다수표읽기(){    표 = [빈값];    각각 (낱말 에서 콘솔.줄읽기().나누기(" "))    {        만약 (낱말 == "-")        {            표.넣기(빈값);        }        아니면        {            표.넣기(낱말.수로변환());        }    }    돌려주기 표;} q = 콘솔.줄읽기().나누기(" ")[3].수로변환();자리표 = 수표읽기(); // 손님 → 자리전기함표 = 수표읽기(); // 자리 → 전기함담당표 = [빈값]; // 전기함 → 담당자각각 (낱말 에서 콘솔.줄읽기().나누기(" ")){    만약 (낱말 == "-")    {        담당표.넣기(빈값);    }    아니면    {        담당표.넣기(낱말);    }} 반복 (i = 0; i < q; i++){    손님 = 콘솔.줄읽기().수로변환();    자리 = 자리표[손님];    만약 (자리 == 빈값)    {        콘솔.쓰기("- 1");        건너뛰기;    }    전기함 = 전기함표[자리]; // 자리가 빈값이면 여기서 프로그램이 멈추므로, 위에서 먼저 걸렀다    만약 (전기함 == 빈값)    {        콘솔.쓰기("- 2");        건너뛰기;    }    담당 = 담당표[전기함];    만약 (담당 == 빈값)    {        콘솔.쓰기("- 3");    }    아니면    {        콘솔.쓰기(담당);    }}