{ }codelper
reading a lineconstantsjoined outputP0165

Filtering and grouping a server log

Pick out the important lines of a server log and shorten them for reading. There are four log levels, from lowest: DEBUG, INFO, WARN, ERROR.
Keep this order as a constant in your program. The first line gives the name of the lowest level to keep and the number of records n, separated by one space. Each of the next n lines is one record of the form "time level message", with a single space between the three parts. A message may contain spaces, including two or more spaces in a row.
A message never starts or ends with a space. Keep only the records whose level is at least the lowest level to keep. Going through the kept records in order, consecutive records with exactly the same level and the same message form one group.
Messages are the same only if even their spaces match. Records that were filtered out do not break a group: two kept records with only filtered-out records between them count as consecutive. For each group print one line "time [level] message", where the time is that of the group's first record. If the group has k records (k ≥ 2), append one space and "(xk)" to the line, with the number in place of k ("(x3)" for 3).
A group of a single record gets nothing appended.
Write the message exactly as in the input, character for character. On the last line print the number of records of each level among all n records (filtered ones included) in the form "DEBUG=a, INFO=b, WARN=c, ERROR=d". If no record is kept, print only the last line.

Input
First line "level n" (1 ≤ n ≤ 2000).
The time is HH:MM:SS (two digits each).
The level is one of DEBUG, INFO, WARN, ERROR.
The message is 1–50 characters of English letters, digits, spaces and punctuation (.,:;!?'()=_/#-)
Output
One line "time [level] message" per group (with " (xk)" for 2 or more), then "DEBUG=a, INFO=b, WARN=c, ERROR=d"
Example 1 · stdin
INFO 7
08:00:01 DEBUG boot  sequence start
08:00:02 INFO disk ok
08:00:03 WARN fan  slow
08:00:04 DEBUG fan check
08:00:05 WARN fan  slow
08:00:09 WARN fan slow
08:00:10 ERROR disk ok
stdout
08:00:02 [INFO] disk ok
08:00:03 [WARN] fan  slow (x2)
08:00:09 [WARN] fan slow
08:00:10 [ERROR] disk ok
DEBUG=2, INFO=1, WARN=3, ERROR=1
main.py
Input (stdin)

When you run, these lines are fed to input()/stdin in order (the program doesn't stop to ask).

$ Ready

Open this on a computer to write and run code. On mobile you can read the problem and the solutions.

In other languagesShow solutions by language · 8 languages

(1) Reading and splitting a line: to keep the spaces inside the message, read the whole line and split only at the first two spaces.
Python line.split(" ", 2), Java split(" ", 3) and C# Split(' ', 3) let you set how many pieces to make.
JS/TS split(" ", 3) throws the rest away, so find the positions with indexOf and cut with slice.
C++ uses find + substr, and C turns the spaces into '\0' with strchr to get three pieces.
Craykon also cuts with IndexOf and Substring (Split() splits at every space).
Splitting everything with Python split() and gluing back with " ".join turns fan slow into fan slow, so it groups with a different message.
In Java, calling nextLine() after Scanner.next()/nextInt() first reads the rest of the first line (an empty string), shifting every record by one.
Reading each line with BufferedReader.readLine() is safe.
(2) Order as a constant: a level's rank is its position in the constant list, not its name.
Comparing names as strings uses alphabetical order, so "WARN" > "ERROR" and WARN survives when only ERROR should.
Keep the list as a Python tuple, JS Object.freeze([...]), TS readonly string[], Java List.of(...) (static final String[] fixes only the name; its slots can change), C# static readonly string[] (an array cannot be const, and its slots can change), C static const char *const LEVELS[], C++ constexpr std::array<std::string_view, 4>.
Craykon has no constants, so keep a list you promise not to change.
(3) Joining: Python ", ".join(...), JS/TS join(", "), Java StringJoiner/String.join, C# string.Join, Craykon Join(", ").
In C/C++ put ", " in front of every count except the first.
Appending it after every count leaves a , at the end of the line.
Groups compare both level and message and are not broken by filtered lines.
The count line counts all records, filtered ones included.

Pythonmain.py · 24 lines
import sys # 차례가 곧 단계의 높이다. 튜플이라 칸을 바꿀 수 없다LEVELS = ("DEBUG", "INFO", "WARN", "ERROR") min_name, n = sys.stdin.readline().split()min_rank = LEVELS.index(min_name)counts = [0] * len(LEVELS)runs = []  # [시각, 단계, 메시지, 개수]for _ in range(int(n)):    line = sys.stdin.readline().rstrip("\n")    # 앞의 두 빈칸에서만 나눈다: 메시지 안의 빈칸(두 칸 이상도)은 그대로 남는다    time, level, message = line.split(" ", 2)    rank = LEVELS.index(level)    counts[rank] += 1    if rank < min_rank:        continue  # 걸러진 줄은 묶음을 끊지 않는다    if runs and runs[-1][1] == level and runs[-1][2] == message:        runs[-1][3] += 1    else:        runs.append([time, level, message, 1])for time, level, message, k in runs:    print(f"{time} [{level}] {message}" + (f" (x{k})" if k > 1 else ""))print(", ".join(f"{name}={c}" for name, c in zip(LEVELS, counts)))
JavaScriptmain.js · 26 lines
// const 배열: 이름은 다시 넣을 수 없고, Object.freeze로 칸도 못 바꾸게 한다const LEVELS = Object.freeze(["DEBUG", "INFO", "WARN", "ERROR"]); const lines = require("fs").readFileSync(0, "utf8").split("\n");const [minName, n] = lines[0].trim().split(/\s+/);const minRank = LEVELS.indexOf(minName);const counts = [0, 0, 0, 0];const runs = []; // { time, level, message, k }for (let i = 1; i <= Number(n); i++) {  const line = lines[i];  // 앞의 두 빈칸 자리로 자른다: split(" ")은 메시지 안의 빈칸까지 나눈다  const a = line.indexOf(" ");  const b = line.indexOf(" ", a + 1);  const time = line.slice(0, a);  const level = line.slice(a + 1, b);  const message = line.slice(b + 1);  const rank = LEVELS.indexOf(level);  counts[rank]++;  if (rank < minRank) continue; // 걸러진 줄은 묶음을 끊지 않는다  const last = runs[runs.length - 1];  if (last && last.level === level && last.message === message) last.k++;  else runs.push({ time, level, message, k: 1 });}const out = runs.map((r) => `${r.time} [${r.level}] ${r.message}` + (r.k > 1 ? ` (x${r.k})` : ""));out.push(LEVELS.map((name, i) => `${name}=${counts[i]}`).join(", "));console.log(out.join("\n"));
TypeScriptmain.ts · 29 lines
import { readFileSync } from "node:fs";// readonly 배열: 타입 검사기가 칸을 바꾸지 못하게 막는다const LEVELS: readonly string[] = ["DEBUG", "INFO", "WARN", "ERROR"]; interface Run { time: string; level: string; message: string; k: number } const lines: string[] = readFileSync(0, "utf8").split("\n");const [minName, n] = lines[0].trim().split(/\s+/);const minRank: number = LEVELS.indexOf(minName);const counts: number[] = [0, 0, 0, 0];const runs: Run[] = [];for (let i = 1; i <= Number(n); i++) {  const line: string = lines[i];  // 앞의 두 빈칸 자리로 자른다: 메시지 안의 빈칸은 그대로  const a: number = line.indexOf(" ");  const b: number = line.indexOf(" ", a + 1);  const time = line.slice(0, a);  const level = line.slice(a + 1, b);  const message = line.slice(b + 1);  const rank: number = LEVELS.indexOf(level);  counts[rank]++;  if (rank < minRank) continue; // 걸러진 줄은 묶음을 끊지 않는다  const last: Run | undefined = runs[runs.length - 1];  if (last !== undefined && last.level === level && last.message === message) last.k++;  else runs.push({ time, level, message, k: 1 });}const out: string[] = runs.map((r) => `${r.time} [${r.level}] ${r.message}` + (r.k > 1 ? ` (x${r.k})` : ""));out.push(LEVELS.map((name, i) => `${name}=${counts[i]}`).join(", "));console.log(out.join("\n"));
JavaMain.java · 46 lines
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.List;import java.util.StringJoiner; public class Main {    // final은 이름만 묶는다: LEVELS[0] = "X"는 막지 않으니 List.of로 바꿀 수 없는 목록을 쓴다    static final List<String> LEVELS = List.of("DEBUG", "INFO", "WARN", "ERROR");     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        String[] first = in.readLine().trim().split("\\s+");        int minRank = LEVELS.indexOf(first[0]);        int n = Integer.parseInt(first[1]);        int[] counts = new int[LEVELS.size()];        List<String[]> runs = new ArrayList<>(); // {시각, 단계, 메시지}        List<Integer> sizes = new ArrayList<>();        for (int i = 0; i < n; i++) {            // split(" ", 3): 앞의 두 빈칸에서만 나눠 메시지 안의 빈칸은 그대로 남는다            String[] p = in.readLine().split(" ", 3);            int rank = LEVELS.indexOf(p[1]);            counts[rank]++;            if (rank < minRank) continue; // 걸러진 줄은 묶음을 끊지 않는다            int last = runs.size() - 1;            if (last >= 0 && runs.get(last)[1].equals(p[1]) && runs.get(last)[2].equals(p[2]))                sizes.set(last, sizes.get(last) + 1);            else {                runs.add(p);                sizes.add(1);            }        }        StringBuilder out = new StringBuilder();        for (int i = 0; i < runs.size(); i++) {            String[] r = runs.get(i);            out.append(r[0]).append(" [").append(r[1]).append("] ").append(r[2]);            if (sizes.get(i) > 1) out.append(" (x").append(sizes.get(i)).append(')');            out.append('\n');        }        StringJoiner sj = new StringJoiner(", ");        for (int i = 0; i < LEVELS.size(); i++) sj.add(LEVELS.get(i) + "=" + counts[i]);        out.append(sj).append('\n');        System.out.print(out);    }}
C#Main.cs · 37 lines
using System;using System.Collections.Generic;using System.Linq;using System.Text; class Program{    // const는 배열이 될 수 없다: static readonly는 이름만 묶고, 칸은 바꿀 수 있다    static readonly string[] Levels = { "DEBUG", "INFO", "WARN", "ERROR" };     static void Main()    {        string[] first = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);        int minRank = Array.IndexOf(Levels, first[0]);        int n = int.Parse(first[1]);        int[] counts = new int[Levels.Length];        var runs = new List<(string Time, string Level, string Message, int K)>();        for (int i = 0; i < n; i++)        {            // Split(' ', 3): 앞의 두 빈칸에서만 나눠 메시지 안의 빈칸은 그대로            string[] p = Console.ReadLine().Split(' ', 3);            int rank = Array.IndexOf(Levels, p[1]);            counts[rank]++;            if (rank < minRank) continue; // 걸러진 줄은 묶음을 끊지 않는다            int last = runs.Count - 1;            if (last >= 0 && runs[last].Level == p[1] && runs[last].Message == p[2])                runs[last] = runs[last] with { K = runs[last].K + 1 };            else                runs.Add((p[0], p[1], p[2], 1));        }        var sb = new StringBuilder();        foreach (var r in runs)            sb.Append($"{r.Time} [{r.Level}] {r.Message}").Append(r.K > 1 ? $" (x{r.K})" : "").Append('\n');        sb.Append(string.Join(", ", Levels.Select((name, i) => $"{name}={counts[i]}"))).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 55 lines
#include <algorithm>#include <array>#include <iostream>#include <sstream>#include <string>#include <string_view>#include <vector> // constexpr 배열: 컴파일할 때 정해지고 칸도 바꿀 수 없다constexpr std::array<std::string_view, 4> LEVELS{"DEBUG", "INFO", "WARN", "ERROR"}; int rankOf(const std::string& name) {    return static_cast<int>(std::find(LEVELS.begin(), LEVELS.end(), name) - LEVELS.begin());} struct Run {    std::string time, level, message;    int k;}; int main() {    std::string line;    std::getline(std::cin, line);    std::istringstream head(line);    std::string minName;    int n;    head >> minName >> n;    int minRank = rankOf(minName);    std::array<int, 4> counts{};    std::vector<Run> runs;    for (int i = 0; i < n; i++) {        std::getline(std::cin, line); // 빈칸이 든 한 줄을 통째로        // 앞의 두 빈칸 자리로 자른다: 메시지 안의 빈칸은 그대로        std::size_t a = line.find(' '), b = line.find(' ', a + 1);        std::string time = line.substr(0, a), level = line.substr(a + 1, b - a - 1), message = line.substr(b + 1);        int rank = rankOf(level);        counts[rank]++;        if (rank < minRank) continue; // 걸러진 줄은 묶음을 끊지 않는다        if (!runs.empty() && runs.back().level == level && runs.back().message == message)            runs.back().k++;        else            runs.push_back({time, level, message, 1});    }    std::string out;    for (const Run& r : runs) {        out += r.time + " [" + r.level + "] " + r.message;        if (r.k > 1) out += " (x" + std::to_string(r.k) + ")";        out += '\n';    }    for (std::size_t i = 0; i < LEVELS.size(); i++) {        if (i > 0) out += ", ";        out += std::string(LEVELS[i]) + "=" + std::to_string(counts[i]);    }    std::cout << out << '\n';}
Cmain.c · 55 lines
#include <stdio.h>#include <string.h> #define LEVEL_COUNT 4static const char *const LEVELS[LEVEL_COUNT] = {"DEBUG", "INFO", "WARN", "ERROR"}; static int rank_of(const char *name) {    for (int i = 0; i < LEVEL_COUNT; i++)        if (strcmp(LEVELS[i], name) == 0) return i;    return -1;} int main(void) {    char line[128], min_name[8];    int n;    fgets(line, sizeof line, stdin);    sscanf(line, "%7s %d", min_name, &n);    int min_rank = rank_of(min_name);    int counts[LEVEL_COUNT] = {0};    char run_time[9] = "", run_msg[64] = "";    int run_rank = -1, run_k = 0;    for (int i = 0; i < n; i++) {        fgets(line, sizeof line, stdin); /* 빈칸이 든 한 줄을 통째로 */        line[strcspn(line, "\n")] = '\0';        /* 앞의 두 빈칸을 '\0'으로 바꿔 세 조각으로: 메시지 안의 빈칸은 그대로 */        char *level = strchr(line, ' ');        *level++ = '\0';        char *msg = strchr(level, ' ');        *msg++ = '\0';        int rank = rank_of(level);        counts[rank]++;        if (rank < min_rank) continue; /* 걸러진 줄은 묶음을 끊지 않는다 */        if (run_k > 0 && rank == run_rank && strcmp(msg, run_msg) == 0) {            run_k++;            continue;        }        if (run_k > 0) {            printf("%s [%s] %s", run_time, LEVELS[run_rank], run_msg);            if (run_k > 1) printf(" (x%d)", run_k);            putchar('\n');        }        strcpy(run_time, line);        strcpy(run_msg, msg);        run_rank = rank;        run_k = 1;    }    if (run_k > 0) {        printf("%s [%s] %s", run_time, LEVELS[run_rank], run_msg);        if (run_k > 1) printf(" (x%d)", run_k);        putchar('\n');    }    for (int i = 0; i < LEVEL_COUNT; i++) printf("%s%s=%d", i ? ", " : "", LEVELS[i], counts[i]);    putchar('\n');    return 0;}
Craykonmain.cray · 55 lines
// 크레이콘에는 상수가 없다: 바꾸지 않기로 약속한 목록으로 둔다(차례가 곧 높이)단계들 = ["DEBUG", "INFO", "WARN", "ERROR"]; 첫줄 = Console.ReadLine().Split();최소 = 단계들.IndexOf(첫줄[0]);n = 첫줄[1].ToNumber();개수들 = [0, 0, 0, 0];묶음시각 = [];묶음단계 = [];묶음메시지 = [];묶음개수 = [];for (i = 0; i < n; i++){    줄 = Console.ReadLine();    // 앞의 두 빈칸 자리로 자른다: 나누기()는 메시지 안의 빈칸까지 나눈다    첫빈칸 = 줄.IndexOf(" ");    시각 = 줄.Substring(0, 첫빈칸);    나머지 = 줄.Substring(첫빈칸 + 1);    둘째빈칸 = 나머지.IndexOf(" ");    단계 = 나머지.Substring(0, 둘째빈칸);    메시지 = 나머지.Substring(둘째빈칸 + 1);    높이 = 단계들.IndexOf(단계);    개수들[높이] += 1;    // 걸러진 줄은 묶음을 끊지 않는다    if (높이 >= 최소)    {        끝 = 묶음단계.Count - 1;        if (끝 >= 0 && 묶음단계[끝] == 단계 && 묶음메시지[끝] == 메시지)        {            묶음개수[끝] += 1;        }        else        {            묶음시각.Add(시각);            묶음단계.Add(단계);            묶음메시지.Add(메시지);            묶음개수.Add(1);        }    }}for (j = 0; j < 묶음단계.Count; j++){    출력줄 = 묶음시각[j] + " [" + 묶음단계[j] + "] " + 묶음메시지[j];    if (묶음개수[j] > 1)    {        출력줄 = 출력줄 + " (x" + 묶음개수[j] + ")";    }    Console.WriteLine(출력줄);}조각들 = [];for (j = 0; j < 4; j++){    조각들.Add(단계들[j] + "=" + 개수들[j]);}Console.WriteLine(조각들.Join(", "));