{ }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()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 6 种语言题解

这道题只接受有枚举的 Python、TypeScript、Java、C#、C、C++。
JavaScript 和 Craykon 没有枚举语法。
给枚举指定编号的写法,以及在名字 ↔ 编号 ↔ 枚举值之间转换的方法,各语言都不一样。
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;}