{ }codelper
列挙型数えるP0357

ログをレベルで絞り込む

ログのレベルは5種類あり、レベルごとに名前と番号がある。
宣言の順に DEBUG = 10, INFO = 20, WARNING = 30, ERROR = 40, CRITICAL = 50 である。
この5つのレベルを列挙型として宣言して解く。
採点は出力だけを見る。
ログの1行は、先頭のレベル表示、空白1つ、内容からなる。
レベル表示は、英字だけからなる単語か、0 以上 100 以下の整数である。
単語は、5つの名前のどれかと大文字・小文字まで同じときだけ、そのレベルである。
整数は、5つの番号のどれかと等しいときだけ、そのレベルである。
それ以外のレベル表示が付いた行は不正な行である。
しきい値の番号 m が与えられる。
m は5つの番号のどれでもないことがある。
不正でない行のうち、レベルの番号が m 以上の行だけを入力の順に出力する。
出力するときは、レベル表示が番号であってもレベルの名前に直して書き、空白1つをあけて内容をそのまま書く。
続いて、5つのレベルを宣言の順に1行に1つずつ、名前とそのレベルの行の数を空白1つで区切って出力する。
この数には、m より低くて出力しなかった行も含める。
最後の行には bad と不正な行の数を空白1つで区切って出力する。

入力
1行目にしきい値の番号 m (0 ≤ m ≤ 100) とログの行数 n (1 ≤ n ≤ 1,000) が空白1つで区切って与えられる。
続く n 行にログが1行ずつ与えられる。
レベル表示が単語なら英字の大文字・小文字 1 ~ 15 文字で、整数なら先頭に 0 や符号は付かない。
内容は長さ 1 ~ 40 で、英字、数字、空白だけからなり、先頭と末尾は空白ではない
出力
レベルの番号が m 以上の行ごとに「名前 内容」を1行、続いて「名前 行数」を5行、最後に「bad 行数」を1行
例 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()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 6 言語の解答

この問題は列挙型がある Python・TypeScript・Java・C#・C・C++ だけを受け付けます。
JavaScript とクレイコンには列挙型の文法がありません。
列挙型に番号を付ける方法と、名前 ↔ 番号 ↔ 列挙型の値を行き来する方法が言語ごとに違います。
Python は Level["INFO"] が名前で、Level(20) が番号で探し、名前は lv.name です。
IntEnum にすると lv >= m のように整数とそのまま比べられ、ただの Enum は >= で TypeError になります。
罠その1: ない名前は KeyError、ない番号は ValueError と別々です。
KeyError だけを捕まえると、1つ目の例の 25 で止まります。
Java の列挙型は番号を自分では持たないので、DEBUG(10) のようにフィールドとコンストラクタを付け、番号で探す関数は values() を回して自分で書きます。
ordinal() は番号ではなく 0, 1, 2… という宣言の順番なので、解答では数える配列の添字にだけ使います。
罠その2: Level.valueOf(名前) はない名前に null を返さず、IllegalArgumentException を投げます。
捕まえないと1つ目の例の warning で止まります。
TypeScript の数値 enum は、Level.INFO が 20 で Level[20] が "INFO" という両方向の表を持つオブジェクトです。
罠その3: そのため Object.keys(Level) には "10", "20"… も入っています。
そのまま回すと1つ目の例で 10 0 のような行が5つ余分に出ます。
解答は数でないキーだけを選んで名前の一覧を作ります。
罠その4: token in Level は継承した toString や constructor にも真になります。
1つ目の例の toString の行を不正な行に数えず、bad 2 になります。
C# は Enum.TryParse(token, out Level level) 1つで名前も番号も読み、出力すると名前になります。
罠その5: TryParse は宣言にない番号も受け取って (Level)25 を作ります。
Enum.IsDefined(typeof(Level), level) でもう一度確かめます。
これを外すと、解答は1つ目の例の 25 を数えようとして KeyNotFoundException で止まります。
罠その6: Enum.TryParse(token, true, out level) の true は大文字・小文字を無視するという意味で、1つ目の例の warning の行まで出力します。
C の enum は名札の付いた int なので、名前の文字列は NAMES という表に別に置き、strcmp で比べます。
罠その7: 値を書かない項目は前の値 + 1 です。
enum Level { DEBUG = 10, INFO, WARNING, ERROR, CRITICAL } と書くと INFO が 11、WARNING が 12 になり、1つ目の例の WARNING の行が出力されず、50 は不正な行になります。
C++ の enum class も名前の文字列やすべての値を回す方法をくれないので表を作り、整数と比べるときは static_cast<int>(lv) で変えます。
変えずに >= を使うとコンパイルできません。
罠その8: しきい値は「以上」です。
> と書くと2つ目の例の 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;}