{ }codelper
열거형세기P0357

로그 등급 거르기

로그의 등급은 다섯 가지이고, 등급마다 이름과 번호가 있다. 선언 차례대로 DEBUG = 10, INFO = 20, WARNING = 30, ERROR = 40, CRITICAL = 50이다. 이 다섯 등급을 열거형으로 선언해서 푼다.
채점은 출력만 본다. 로그 한 줄은 맨 앞의 등급 표시, 빈칸 하나, 내용으로 이루어진다. 등급 표시는 영어 글자로만 된 낱말이거나 0 이상 100 이하의 정수다. 낱말은 다섯 이름 가운데 하나와 대소문자까지 똑같을 때만 그 등급이다. 정수는 다섯 번호 가운데 하나와 같을 때만 그 등급이다. 그 밖의 등급 표시가 붙은 줄은 잘못된 줄이다. 문턱 번호 m이 주어진다.
m은 다섯 번호 가운데 하나가 아닐 수도 있다. 잘못되지 않은 줄 가운데 등급 번호가 m 이상인 줄만 입력 차례대로 출력한다. 출력할 때는 등급 표시가 번호였더라도 등급 이름으로 바꿔 적고, 빈칸 하나를 띄운 뒤 내용을 그대로 적는다. 그다음 다섯 등급을 선언 차례대로 한 줄에 하나씩, 이름과 그 등급인 줄의 수를 빈칸 하나로 띄워 출력한다.
이 수에는 m보다 낮아서 출력하지 않은 줄도 들어간다. 마지막 줄에는 bad와 잘못된 줄의 수를 빈칸 하나로 띄워 출력한다.

입력
첫 줄에 문턱 번호 m (0 ≤ m ≤ 100)과 로그 줄 수 n (1 ≤ n ≤ 1,000)이 빈칸 하나로 띄워 주어진다.
이어지는 n줄에 로그가 한 줄씩 주어진다.
등급 표시가 낱말이면 영어 대소문자 1 ~ 15자이고, 정수이면 앞에 0이나 부호를 붙이지 않는다.
내용은 길이 1 ~ 40이고 영어 글자, 숫자, 빈칸만 쓰며 맨 앞과 맨 끝은 빈칸이 아니다
출력
등급 번호가 m 이상인 줄마다 "이름 내용" 한 줄, 그다음 "이름 줄 수" 다섯 줄, 마지막에 "bad 줄 수" 한 줄
예시 1 · stdin
25 7
INFO server started
10 cache warm
WARNING disk 91 percent
warning disk 92 percent
25 odd level
50 out of memory
toString tricky
stdout
WARNING disk 91 percent
CRITICAL out of memory
DEBUG 1
INFO 1
WARNING 1
ERROR 0
CRITICAL 1
bad 3
예시 2 · stdin
30 4
30 exactly at the bar
ERROR db down
20 skipped
3 not a level
stdout
WARNING exactly at the bar
ERROR db down
DEBUG 0
INFO 1
WARNING 1
ERROR 1
CRITICAL 0
bad 1
main.py
입력(stdin)

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

$ 준비됐어요

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

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

이 문제는 열거형이 있는 Python·TypeScript·Java·C#·C·C++만 받아요.
JavaScript와 크레이콘에는 열거형 문법이 없어요.
열거형에 번호를 직접 붙이고, 이름 ↔ 번호 ↔ 열거형 값을 오가는 법이 언어마다 달라요.
Python은 Level["INFO"]가 이름으로, Level(20)이 번호로 찾고, 이름은 lv.name이에요.
IntEnum으로 만들면 lv >= m처럼 정수와 바로 견줄 수 있고, 그냥 Enum은 >=에서 TypeError가 나요.
함정 하나: 없는 이름은 KeyError, 없는 번호는 ValueError로 서로 달라요.
KeyError만 잡으면 첫 예시의 25에서 멈춰요.
Java 열거형은 번호를 스스로 갖지 않아서 DEBUG(10)처럼 필드와 생성자를 달고, 번호로 찾는 함수는 values()를 돌며 직접 만들어요.
ordinal()은 번호가 아니라 0, 1, 2… 인 선언 차례라서 세는 배열의 자리로만 써요.
함정 둘: Level.valueOf(이름)은 없는 이름에 null을 주지 않고 IllegalArgumentException을 던져요.
잡지 않으면 첫 예시의 warning에서 멈춰요.
TypeScript 숫자 enum은 Level.INFO가 20이고 Level[20]이 "INFO"인, 양방향 표를 가진 객체예요.
함정 셋: 그래서 Object.keys(Level)에는 "10", "20"… 도 들어 있어요.
그대로 돌면 첫 예시에서 10 0 같은 줄이 다섯 개 더 나와요.
풀이는 수가 아닌 열쇠만 골라 이름 목록을 만들어요.
함정 넷: token in Level은 물려받은 toString, constructor에도 참이에요.
첫 예시의 toString 줄을 잘못된 줄로 세지 않아서 bad 2가 돼요.
C#은 Enum.TryParse(token, out Level level) 하나로 이름과 번호를 모두 읽고, 출력하면 이름이 나와요.
함정 다섯: TryParse는 선언에 없는 번호도 받아서 (Level)25를 만들어요.
Enum.IsDefined(typeof(Level), level)로 한 번 더 확인해야 해요.
빼면 풀이는 첫 예시의 25를 세려다 KeyNotFoundException으로 멈춰요.
함정 여섯: Enum.TryParse(token, true, out level)의 true는 대소문자를 무시하라는 뜻이라, 첫 예시의 warning 줄까지 출력해요.
C enum은 이름표가 붙은 int라서 이름 문자열은 NAMES 표로 따로 두고 strcmp로 견줘요.
함정 일곱: 값을 적지 않은 항목은 앞의 값 + 1이에요.
enum Level { DEBUG = 10, INFO, WARNING, ERROR, CRITICAL }로 쓰면 INFO가 11, WARNING이 12가 되어 첫 예시의 WARNING 줄이 출력되지 않고 50은 잘못된 줄이 돼요.
C++ enum class도 이름 문자열이나 모든 값을 도는 방법을 주지 않아서 표를 만들고, 정수와 견줄 때는 static_cast<int>(lv)로 바꿔요.
바꾸지 않고 >=를 쓰면 컴파일되지 않아요.
함정 여덟: 문턱은 '이상'이에요.
>로 쓰면 둘째 예시의 30 줄이 빠져요.

Pythonmain.py · 44 줄
import sysfrom enum import IntEnum  class Level(IntEnum):    DEBUG = 10    INFO = 20    WARNING = 30    ERROR = 40    CRITICAL = 50  def parse(token):    """이름이나 번호를 등급으로 바꾼다. 등급이 아니면 None."""    try:        if token.isdigit():            return Level(int(token))  # 값으로 찾기: 없는 값이면 ValueError        return Level[token]  # 이름으로 찾기: 없는 이름이면 KeyError    except (ValueError, KeyError):        return None  def main():    lines = sys.stdin.read().split("\n")    m, n = map(int, lines[0].split())    count = {lv: 0 for lv in Level}  # 선언한 차례대로 돈다    bad = 0    out = []    for line in lines[1:1 + n]:        token, text = line.split(" ", 1)        lv = parse(token)        if lv is None:            bad += 1            continue        count[lv] += 1        if lv >= m:  # IntEnum이라 정수와 바로 견줄 수 있다            out.append(f"{lv.name} {text}")    for lv in Level:        out.append(f"{lv.name} {count[lv]}")    out.append(f"bad {bad}")    print("\n".join(out))  main()
TypeScriptmain.ts · 44 줄
import { readFileSync } from "node:fs"; enum Level {  DEBUG = 10,  INFO = 20,  WARNING = 30,  ERROR = 40,  CRITICAL = 50,} // 숫자 enum은 이름 → 값, 값 → 이름 두 방향이 한 객체에 들어 있다.// Object.keys(Level)에는 "10", "20"… 도 나오므로 수가 아닌 열쇠만 골라 이름 목록을 만든다const NAMES: string[] = Object.keys(Level).filter((k) => Number.isNaN(Number(k))); function parse(token: string): Level | undefined {  if (/^[0-9]+$/.test(token)) {    const code = Number(token);    return Level[code] !== undefined ? code : undefined;  }  // Level["toString"]은 물려받은 함수라서 undefined가 아니다. 선언한 이름인지 직접 확인한다  return NAMES.includes(token) ? Level[token as keyof typeof Level] : undefined;} const lines: string[] = readFileSync(0, "utf8").split("\n");const [m, n] = lines[0].split(" ").map(Number);const count = new Map<Level, number>();let bad = 0;const out: string[] = [];for (let i = 1; i <= n; i++) {  const line = lines[i];  const cut = line.indexOf(" ");  const lv = parse(line.slice(0, cut));  if (lv === undefined) {    bad++;    continue;  }  count.set(lv, (count.get(lv) ?? 0) + 1);  if (lv >= m) out.push(`${Level[lv]} ${line.slice(cut + 1)}`);}for (const name of NAMES) {  out.push(`${name} ${count.get(Level[name as keyof typeof Level]) ?? 0}`);}out.push(`bad ${bad}`);console.log(out.join("\n"));
JavaMain.java · 62 줄
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader; public class Main {    // Java 열거형은 값을 스스로 갖지 않는다. 번호는 필드로 달아 준다    enum Level {        DEBUG(10), INFO(20), WARNING(30), ERROR(40), CRITICAL(50);         final int code;         Level(int code) {            this.code = code;        }         static Level fromCode(int code) {            for (Level lv : values()) {                if (lv.code == code) return lv;            }            return null;        }    }     static Level parse(String token) {        if (Character.isDigit(token.charAt(0))) {            return Level.fromCode(Integer.parseInt(token));        }        try {            return Level.valueOf(token); // 없는 이름이면 IllegalArgumentException        } catch (IllegalArgumentException e) {            return null;        }    }     public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        String[] first = br.readLine().split(" ");        int m = Integer.parseInt(first[0]);        int n = Integer.parseInt(first[1]);        int[] count = new int[Level.values().length]; // ordinal()은 0, 1, 2…인 선언 차례다        int bad = 0;        StringBuilder out = new StringBuilder();        for (int i = 0; i < n; i++) {            String line = br.readLine();            int cut = line.indexOf(' ');            Level lv = parse(line.substring(0, cut));            if (lv == null) {                bad++;                continue;            }            count[lv.ordinal()]++;            if (lv.code >= m) {                out.append(lv).append(' ').append(line.substring(cut + 1)).append('\n');            }        }        for (Level lv : Level.values()) {            out.append(lv).append(' ').append(count[lv.ordinal()]).append('\n');        }        out.append("bad ").append(bad).append('\n');        System.out.print(out);    }}
C#Main.cs · 44 줄
using System;using System.Collections.Generic;using System.Text; enum Level{    DEBUG = 10,    INFO = 20,    WARNING = 30,    ERROR = 40,    CRITICAL = 50,} class Program{    static void Main()    {        string[] first = Console.ReadLine().Split(' ');        int m = int.Parse(first[0]);        int n = int.Parse(first[1]);        var count = new Dictionary<Level, int>();        foreach (Level lv in Enum.GetValues(typeof(Level))) count[lv] = 0;        int bad = 0;        var sb = new StringBuilder();        for (int i = 0; i < n; i++)        {            string line = Console.ReadLine();            int cut = line.IndexOf(' ');            string token = line.Substring(0, cut);            // TryParse는 이름과 번호를 모두 받지만 "25"처럼 선언에 없는 번호도 통과시킨다.            // IsDefined로 선언한 값인지 한 번 더 확인한다            if (!Enum.TryParse(token, out Level level) || !Enum.IsDefined(typeof(Level), level))            {                bad++;                continue;            }            count[level]++;            if ((int)level >= m) sb.Append(level).Append(' ').Append(line.Substring(cut + 1)).Append('\n');        }        foreach (Level lv in Enum.GetValues(typeof(Level))) sb.Append(lv).Append(' ').Append(count[lv]).Append('\n');        sb.Append("bad ").Append(bad).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 55 줄
#include <array>#include <cctype>#include <iostream>#include <string>using namespace std; enum class Level { DEBUG = 10, INFO = 20, WARNING = 30, ERROR = 40, CRITICAL = 50 }; // enum class는 이름 문자열도, 모든 값을 도는 방법도 주지 않아서 표를 만든다const array<pair<Level, string>, 5> TABLE = {{    {Level::DEBUG, "DEBUG"},    {Level::INFO, "INFO"},    {Level::WARNING, "WARNING"},    {Level::ERROR, "ERROR"},    {Level::CRITICAL, "CRITICAL"},}}; // 등급이면 표의 몇 번째인지, 아니면 -1int parse(const string& token) {    bool number = isdigit(static_cast<unsigned char>(token[0])) != 0;    for (int k = 0; k < static_cast<int>(TABLE.size()); k++) {        if (number) {            // enum class는 정수와 바로 견줄 수 없어서 static_cast로 바꾼다            if (stoi(token) == static_cast<int>(TABLE[k].first)) return k;        } else if (token == TABLE[k].second) {            return k;        }    }    return -1;} int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int m, n;    cin >> m >> n;    string line;    getline(cin, line);    array<int, 5> count{};    int bad = 0;    for (int i = 0; i < n; i++) {        getline(cin, line);        size_t cut = line.find(' ');        int k = parse(line.substr(0, cut));        if (k < 0) {            bad++;            continue;        }        count[k]++;        if (static_cast<int>(TABLE[k].first) >= m) cout << TABLE[k].second << ' ' << line.substr(cut + 1) << '\n';    }    for (int k = 0; k < 5; k++) cout << TABLE[k].second << ' ' << count[k] << '\n';    cout << "bad " << bad << '\n';    return 0;}
Cmain.c · 48 줄
#include <ctype.h>#include <stdio.h>#include <stdlib.h>#include <string.h> /* 값을 적지 않으면 앞의 값 + 1이 된다. 다섯 개 모두 적는다 */enum Level { DEBUG = 10, INFO = 20, WARNING = 30, ERROR = 40, CRITICAL = 50 }; #define KINDS 5/* C 열거형은 이름표가 붙은 정수일 뿐이라 이름 문자열은 따로 표로 만든다 */static const enum Level LEVELS[KINDS] = { DEBUG, INFO, WARNING, ERROR, CRITICAL };static const char *NAMES[KINDS] = { "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL" }; /* 등급이면 표의 몇 번째인지, 아니면 -1 */static int parse(const char *token) {    for (int k = 0; k < KINDS; k++) {        if (isdigit((unsigned char)token[0])) {            if (atoi(token) == (int)LEVELS[k]) return k;        } else if (strcmp(token, NAMES[k]) == 0) {            return k;        }    }    return -1;} int main(void) {    char line[128];    int m, n;    if (!fgets(line, sizeof line, stdin) || sscanf(line, "%d %d", &m, &n) != 2) return 0;    int count[KINDS] = { 0 };    int bad = 0;    for (int i = 0; i < n; i++) {        if (!fgets(line, sizeof line, stdin)) return 0;        line[strcspn(line, "\n")] = '\0';        char *cut = strchr(line, ' ');        *cut = '\0';        int k = parse(line);        if (k < 0) {            bad++;            continue;        }        count[k]++;        if ((int)LEVELS[k] >= m) printf("%s %s\n", NAMES[k], cut + 1);    }    for (int k = 0; k < KINDS; k++) printf("%s %d\n", NAMES[k], count[k]);    printf("bad %d\n", bad);    return 0;}