{ }codelper
リストに含まれるかリストからの削除大文字・小文字P0391

画面に残っている発言で連投を弾く

ゲームのチャットルームが、同じ発言を繰り返す連投を弾く。
1 行目に、画面に残るメッセージの数 k とブロックの基準 s が空白 1 つで区切って与えられる。
2 行目にメッセージの数 n が与えられ、続く n 行にメッセージが「名前 単語」の形で 1 行に 1 つずつ与えられる。
チャットルームには画面リストとブロックリストがあり、どちらも最初は空である。
メッセージを受け取った順に 1 つずつ、次の 3 つを上から調べ、最初に当てはまるもの 1 つだけを行う。
1.
送った名前がブロックリストにあれば、そのメッセージを無視する。
このとき他のものは何も変わらない。
2.
画面リストに名前が同じで単語も同じメッセージがあれば連投である。
このメッセージは出力せず、画面リストにも入れず、その名前の警告回数を 1 増やす。
その名前の警告回数が s になったら、その名前をブロックリストの末尾に入れる。
3.
それ以外のメッセージは画面に表示する。
受け取ったとおりに「名前 単語」を 1 行に出力し、画面リストの末尾に入れる。
入れたあと画面リストのメッセージが k 個より多ければ、最も先に入れたメッセージを 1 つ取り除く。
単語が同じかどうかは、英字の大文字と小文字を区別せずに見る。
したがって hi と HI は同じ単語である。
名前は、すべての文字が同じときだけ同じ名前である。
名前が違えば、単語が同じでも連投ではない。
警告回数は名前ごとに 0 から始まり、減ることはない。
名前がブロックされても、その名前ですでに画面リストに入っているメッセージは取り除かない。
すべてのメッセージを処理したあと、連投として弾いたメッセージの数と無視したメッセージの数を空白 1 つで区切って 1 行に出力する。
最後の行には、ブロックリストの名前をブロックされた順に空白 1 つずつで区切って出力する。
ブロックされた名前がなければ "-" を出力する。

入力
1 ≤ k ≤ 50、1 ≤ s ≤ 5、1 ≤ n ≤ 2,000。
名前は英小文字 1〜8 文字、単語は英大文字・小文字 1〜8 文字で、名前と単語の間は空白 1 つである
出力
画面に表示したメッセージごとに 1 行(受け取ったとおり)、続いて「連投の数 無視の数」を 1 行、最後の行にブロックされた名前(なければ -)
例 1 · stdin
2 2
10
mina hi
jun hi
mina HI
jun yo
mina hi
mina Hi
mina hi
jun yo
ab c
a bc
stdout
mina hi
jun hi
jun yo
mina hi
ab c
a bc
3 1
mina
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

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

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

この問題は「リストにあるか」を 2 回たずねます。
ブロックリストには名前 1 つを、画面リストには(名前、単語)の組をたずねます。
文字列 1 つを探すのは Python name in blocked、JS・TS blocked.includes(name)、Java blocked.contains(name)、C# blocked.Contains(name)、C++ std::find(blocked.begin(), blocked.end(), name) != blocked.end()、Craykon blocked.含む(name) で、C は strcmp で 1 つずつ比べるループを書きます。
組を探すところで言語が分かれます。
Python のタプル (name, low) in screen、C++ の std::pair に対する std::find、C# のタプルを入れた List<(string, string)> の Contains、Java の List.of(name, low) を入れたリストの contains は中の値を比べます。
落とし穴その一:JS・TS screen.includes([name, low])、Java List<String[]> の contains(new String[] {name, low})、Craykon screen.含む([name, low]) は、中身が同じでも今作ったばかりの別の配列・リストなので常に偽です。
エラーも出ずに連投を 1 つも捕まえられず、例の 3 番目のメッセージ mina HI がそのまま出力されます。
JS・TS は screen.some(([a, b]) => a === name && b === low) で中身を比べ、Craykon は名前のリストと単語のリストを並べて持ち、位置ごとに比べます。
落とし穴その二:組を 1 つの文字列につないで探すこともできますが、区切りなしで name + low とつなぐと ab + c と a + bc がどちらも abc になり、例の最後のメッセージ a bc が連投として弾かれます。
名前にも単語にも出ない空白を間に入れます。
落とし穴その三:大文字と小文字を区別しないためには、入れるときも探すときも小文字にした単語を使います(Python lower()、JS・TS toLowerCase()、Java toLowerCase(Locale.ROOT)、C# ToLowerInvariant()、C・C++ は 1 文字ずつ tolower、Craykon 小文字())。
変えないと mina HI が連投でなくなります。
出力は受け取ったとおりなので、元の単語も持っておきます。
落とし穴その四:名前を見ずに単語だけを探すと、例の 2 番目のメッセージ jun hi のように他の人の同じ発言まで連投になります。
落とし穴その五:連投として弾いたメッセージを画面リストに入れると、画面にあったメッセージが押し出されたり残りすぎたりして、例の 5 番目のメッセージ mina hi が誤って弾かれます。
落とし穴その六:入れたあと >= k のときに取り除くと、画面リストには k − 1 個しか残りません。
先頭を取り除くのは Python pop(0)、JS・TS shift()、Java remove(0)、C# RemoveAt(0)、C++ erase(begin())、Craykon 位置で取り除く(0) で、C は配列を 1 つずつ前に詰めます。
落とし穴その七:ブロックリストを先にたずねます。
画面リストを先にたずねると、すでにブロックされた mina の 7 番目のメッセージが無視ではなく連投として数えられ、3 1 が 4 0 になります。

Pythonmain.py · 28 行
import sys lines = sys.stdin.read().split("\n")k, s = map(int, lines[0].split())n = int(lines[1])screen = []   # 화면에 남은 (이름, 소문자 낱말) 쌍, 오래된 것이 앞blocked = []  # 차단된 이름, 차단된 차례대로warn = {}repeats = ignored = 0out = []for i in range(n):    name, word = lines[2 + i].split()    key = (name, word.lower())    if name in blocked:        ignored += 1    elif key in screen:  # 튜플은 값으로 비교한다        repeats += 1        warn[name] = warn.get(name, 0) + 1        if warn[name] == s:            blocked.append(name)    else:        out.append(f"{name} {word}")        screen.append(key)        if len(screen) > k:            screen.pop(0)out.append(f"{repeats} {ignored}")out.append(" ".join(blocked) if blocked else "-")print("\n".join(out))
JavaScriptmain.js · 28 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [k, s] = lines[0].trim().split(/\s+/).map(Number);const n = Number(lines[1]);const screen = []; // 화면에 남은 [이름, 소문자 낱말] 쌍, 오래된 것이 앞const blocked = []; // 차단된 이름, 차단된 차례대로const warn = new Map();let repeats = 0;let ignored = 0;const out = [];for (let i = 0; i < n; i++) {  const [name, word] = lines[2 + i].trim().split(/\s+/);  const low = word.toLowerCase();  if (blocked.includes(name)) {    ignored++;  } else if (screen.some(([a, b]) => a === name && b === low)) {    // includes([name, low])는 새 배열이라 늘 false다. 속을 하나씩 견준다    repeats++;    warn.set(name, (warn.get(name) ?? 0) + 1);    if (warn.get(name) === s) blocked.push(name);  } else {    out.push(`${name} ${word}`);    screen.push([name, low]);    if (screen.length > k) screen.shift();  }}out.push(`${repeats} ${ignored}`);out.push(blocked.length > 0 ? blocked.join(" ") : "-");console.log(out.join("\n"));
TypeScriptmain.ts · 30 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const [k, s]: number[] = lines[0].trim().split(/\s+/).map(Number);const n: number = Number(lines[1]);const screen: [string, string][] = []; // 화면에 남은 [이름, 소문자 낱말] 쌍, 오래된 것이 앞const blocked: string[] = []; // 차단된 이름, 차단된 차례대로const warn = new Map<string, number>();let repeats = 0;let ignored = 0;const out: string[] = [];for (let i = 0; i < n; i++) {  const [name, word]: string[] = lines[2 + i].trim().split(/\s+/);  const low: string = word.toLowerCase();  if (blocked.includes(name)) {    ignored++;  } else if (screen.some(([a, b]) => a === name && b === low)) {    // includes([name, low])는 새 배열이라 늘 false다. 속을 하나씩 견준다    repeats++;    const count: number = (warn.get(name) ?? 0) + 1;    warn.set(name, count);    if (count === s) blocked.push(name);  } else {    out.push(`${name} ${word}`);    screen.push([name, low]);    if (screen.length > k) screen.shift();  }}out.push(`${repeats} ${ignored}`);out.push(blocked.length > 0 ? blocked.join(" ") : "-");console.log(out.join("\n"));
JavaMain.java · 44 行
import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Locale;import java.util.Map; public class Main {    public static void main(String[] args) throws Exception {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        String[] first = in.readLine().trim().split(" ");        int k = Integer.parseInt(first[0]);        int s = Integer.parseInt(first[1]);        int n = Integer.parseInt(in.readLine().trim());        // List.of(이름, 낱말)은 equals가 속을 견주므로 contains로 찾을 수 있다(String[]는 안 된다)        List<List<String>> screen = new ArrayList<>();        List<String> blocked = new ArrayList<>();        Map<String, Integer> warn = new HashMap<>();        int repeats = 0;        int ignored = 0;        StringBuilder out = new StringBuilder();        for (int i = 0; i < n; i++) {            String[] parts = in.readLine().trim().split(" ");            String name = parts[0];            String word = parts[1];            List<String> key = List.of(name, word.toLowerCase(Locale.ROOT));            if (blocked.contains(name)) {                ignored++;            } else if (screen.contains(key)) {                repeats++;                int count = warn.merge(name, 1, Integer::sum);                if (count == s) blocked.add(name);            } else {                out.append(name).append(' ').append(word).append('\n');                screen.add(key);                if (screen.size() > k) screen.remove(0);            }        }        out.append(repeats).append(' ').append(ignored).append('\n');        out.append(blocked.isEmpty() ? "-" : String.join(" ", blocked)).append('\n');        System.out.print(out);    }}
C#Main.cs · 45 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        string[] first = Console.ReadLine().Trim().Split(' ');        int k = int.Parse(first[0]);        int s = int.Parse(first[1]);        int n = int.Parse(Console.ReadLine());        // 튜플 (string, string)은 값으로 비교하므로 Contains로 찾을 수 있다(string[]은 안 된다)        var screen = new List<(string, string)>();        var blocked = new List<string>();        var warn = new Dictionary<string, int>();        int repeats = 0, ignored = 0;        var sb = new StringBuilder();        for (int i = 0; i < n; i++)        {            string[] parts = Console.ReadLine().Trim().Split(' ');            string name = parts[0], word = parts[1];            var key = (name, word.ToLowerInvariant());            if (blocked.Contains(name))            {                ignored++;            }            else if (screen.Contains(key))            {                repeats++;                warn[name] = warn.GetValueOrDefault(name, 0) + 1;                if (warn[name] == s) blocked.Add(name);            }            else            {                sb.Append(name).Append(' ').Append(word).Append('\n');                screen.Add(key);                if (screen.Count > k) screen.RemoveAt(0);            }        }        sb.Append(repeats).Append(' ').Append(ignored).Append('\n');        sb.Append(blocked.Count > 0 ? string.Join(" ", blocked) : "-").Append('\n');        Console.Write(sb);    }}
C++main.cpp · 42 行
#include <algorithm>#include <cctype>#include <iostream>#include <map>#include <string>#include <utility>#include <vector>using namespace std; int main() {    int k, s, n;    cin >> k >> s >> n;    vector<pair<string, string>> screen; // 화면에 남은 (이름, 소문자 낱말), 오래된 것이 앞    vector<string> blocked;              // 차단된 이름, 차단된 차례대로    map<string, int> warn;    int repeats = 0, ignored = 0;    for (int i = 0; i < n; i++) {        string name, word;        cin >> name >> word;        string low = word;        for (char &c : low) c = (char)tolower((unsigned char)c);        pair<string, string> key{name, low};        if (find(blocked.begin(), blocked.end(), name) != blocked.end()) {            ignored++;        } else if (find(screen.begin(), screen.end(), key) != screen.end()) { // pair는 값으로 비교한다            repeats++;            if (++warn[name] == s) blocked.push_back(name);        } else {            cout << name << ' ' << word << '\n';            screen.push_back(key);            if ((int)screen.size() > k) screen.erase(screen.begin());        }    }    cout << repeats << ' ' << ignored << '\n';    if (blocked.empty()) {        cout << "-\n";    } else {        for (size_t i = 0; i < blocked.size(); i++) cout << (i ? " " : "") << blocked[i];        cout << '\n';    }    return 0;}
Cmain.c · 68 行
#include <ctype.h>#include <stdio.h>#include <string.h> #define MAXK 50#define MAXN 2000 static char scr_name[MAXK][9], scr_word[MAXK][9]; /* 화면 목록, 오래된 것이 앞 */static char people[MAXN][9];                      /* 도배한 적이 있는 이름 */static int warn[MAXN];static char blocked[MAXN][9]; /* C에는 목록 찾기가 없다. strcmp로 하나씩 견준다 */static int find_name(char list[][9], int count, const char *name) {    for (int i = 0; i < count; i++)        if (strcmp(list[i], name) == 0) return i;    return -1;} int main(void) {    int k, s, n;    if (scanf("%d %d %d", &k, &s, &n) != 3) return 1;    int screen = 0, npeople = 0, nblocked = 0, repeats = 0, ignored = 0;    for (int i = 0; i < n; i++) {        char name[9], word[9], low[9];        if (scanf("%8s %8s", name, word) != 2) return 1;        size_t len = strlen(word);        for (size_t j = 0; j <= len; j++) low[j] = (char)tolower((unsigned char)word[j]);         if (find_name(blocked, nblocked, name) >= 0) {            ignored++;            continue;        }        int on_screen = 0;        for (int j = 0; j < screen; j++)            if (strcmp(scr_name[j], name) == 0 && strcmp(scr_word[j], low) == 0) on_screen = 1;        if (on_screen) {            repeats++;            int p = find_name(people, npeople, name);            if (p < 0) {                p = npeople++;                strcpy(people[p], name);                warn[p] = 0;            }            if (++warn[p] == s) strcpy(blocked[nblocked++], name);        } else {            printf("%s %s\n", name, word);            if (screen == k) { /* 꽉 찼으면 가장 오래된 것을 빼고 한 칸씩 당긴다 */                for (int j = 1; j < screen; j++) {                    strcpy(scr_name[j - 1], scr_name[j]);                    strcpy(scr_word[j - 1], scr_word[j]);                }                screen--;            }            strcpy(scr_name[screen], name);            strcpy(scr_word[screen], low);            screen++;        }    }    printf("%d %d\n", repeats, ignored);    if (nblocked == 0) {        printf("-\n");    } else {        for (int i = 0; i < nblocked; i++) printf("%s%s", i ? " " : "", blocked[i]);        printf("\n");    }    return 0;}
Craykonmain.cray · 66 行
first = コンソール.数読み();k = first[0];s = first[1];n = コンソール.行読み().数に();names = []; // 화면에 남은 메시지의 이름, 오래된 것이 앞words = []; // 같은 자리의 소문자 낱말blocked = []; // 차단된 이름, 차단된 차례대로warn = 辞書();repeats = 0;ignored = 0;繰り返す (i = 0; i < n; i++){    parts = コンソール.行読み().分ける();    name = parts[0];    low = parts[1].小文字();    もし (blocked.含む(name))    {        ignored += 1;    }    でなければ もし (onScreen(names, words, name, low))    {        repeats += 1;        もし (!warn.含む(name))        {            warn[name] = 0;        }        warn[name] += 1;        もし (warn[name] == s)        {            blocked.追加(name);        }    }    でなければ    {        コンソール.書く(name + " " + parts[1]);        names.追加(name);        words.追加(low);        もし (names.個数 > k)        {            names.位置で取り除く(0);            words.位置で取り除く(0);        }    }}コンソール.書く(repeats + " " + ignored);もし (blocked.個数 > 0){    コンソール.書く(blocked.つなぐ(" "));}でなければ{    コンソール.書く("-");} // 목록.있나([name, low])는 새 목록이라 늘 거짓이다. 이름과 낱말을 자리마다 견준다onScreen(names, words, name, low){    繰り返す (j = 0; j < names.個数; j++)    {        もし (names[j] == name && words[j] == low)        {            返す 真;        }    }    返す 偽;}