{ }codelper
ジェネリクスP0204

一つを二度使う最近のリスト

最近見たものを k 個までだけ覚えるリストを二つ使う。
一つは整数を、もう一つは単語を入れる。
リストはいちばん最近のものが先頭に来るように順番を保つ。
最初はどちらも空である。
命令は「種類 動作 [値]」の形で、種類は num(整数のリスト)か word(単語のリスト)である。
- add x:リストに x と同じものがすでにあれば、まずそれを取り除く。
次に x を先頭に入れる。
そのとき項目が k 個を超えたら、末尾(いちばん古いもの)を一つ捨てる。
- del x:リストに x と同じものがあれば取り除く。
なければ何もしない。
- show:リストの項目を先頭から空白一つで区切って一行に出力する。
リストが空なら - を出力する。
整数は値が同じなら同じものである。
入力の整数は前に 0 がついていることがある(007 と 7 は同じ)。
出力するときは前の 0 なしのふつうの形で書く。
単語は文字がすべて同じときだけ同じものである。
一行目に k と命令数 q、続く q 行に命令が一つずつ与えられる。
show ごとに一行ずつ出力せよ。
項目の型を型引数 T で受け取るジェネリッククラス(C++ はクラステンプレート)Recent<T> を一つだけ作り、整数のリストも単語のリストもそのクラスで作って解いてみよう。

入力
1 ≤ k ≤ 100、1 ≤ q ≤ 100,000。
整数の値は -10⁹ 以上 10⁹ 以下で、負の数には - が一つ前につく(0 にはつかない)。
整数は数字 13 個以下で書かれる。
単語は英小文字 1〜10 文字。
show が出力する項目は全部合わせて 30,000 個以下。
出力
show ごとに一行
例 1 · stdin
3 13
num add 5
word add kiwi
num add 0012
num add 300
num add 5
num add 7
word add fig
word add kiwi
num del 300
num show
word show
word del plum
word show
stdout
7 5
kiwi fig
kiwi fig
main.py
入力(stdin)

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

$ 準備完了

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

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

この問題はジェネリクスのある TypeScript・Java・C#・C++ だけを受け付けます(C++ はクラステンプレート)。
Python・JavaScript は型を書かないのでクラス一つが最初から何でも入れられ(Python の TypeVar は書いておくだけで実行時には検査しません)、C と Craykon には型引数がありません。
四つの言語は「同じものを探す」やり方が違います。
TS の indexOf は === で探すので、数と文字列は値で比べられます。
Java は Recent<int> と書けないので Recent<Integer> に入れ、T どうしは equals で比べます。
C# は制約のない T に == を使うとコンパイルエラーなので EqualityComparer<T>.Default.Equals を使い、Recent<int> はボックス化せずに入れます。
C++ のテンプレートは T ごとに別のコードが作られ、std::find がその T の == を呼びます。
落とし穴は三つ:① Java のジェネリックなコードで items.get(i) == x は同じオブジェクトかどうかを聞きます。
Integer は -128〜127 だけあらかじめ作ったオブジェクトを共有するので小さい数では正しく見えますが、1000 や読み込んだ単語は値が同じでも別のオブジェクトなので、取り除かれずに二回入ります。
② TS・C++ で整数を読んだ文字列のまま Recent<string> に入れると、007 と 7 が別の項目になり、出力も 007 になります。
Number(x)・std::stoll で変換して Recent<number>・Recent<long long> に入れます。
③ C# で == を使うために where T : class 制約をつけるとコンパイルは通りますが、ジェネリックの中の == は string の値の比較ではなく参照の比較なので、読み込んだ同じ単語を違うものとみなします(しかも int は入れられません)。

TypeScriptmain.ts · 39 行
import { readFileSync } from "node:fs"; // 가장 최근 것부터 k개까지만 기억하는 목록. T가 무엇이든 같은 코드로 쓴다class Recent<T> {  private items: T[] = [];  constructor(private readonly capacity: number) {}   add(x: T): void {    this.remove(x);           // 이미 있으면 빼고    this.items.unshift(x);    // 맨 앞(가장 최근)에 넣고    if (this.items.length > this.capacity) this.items.pop(); // 넘치면 가장 오래된 것을 버린다  }  remove(x: T): void {    const i = this.items.indexOf(x); // indexOf는 ===로 찾는다: 수와 문자열은 값으로 같다    if (i >= 0) this.items.splice(i, 1);  }  show(): string {    return this.items.length === 0 ? "-" : this.items.join(" ");  }} const lines = readFileSync(0, "utf8").split("\n");const [k, q] = lines[0].split(" ").map(Number);const nums = new Recent<number>(k);   // 수는 수로 바꿔 담아야 "007"과 "7"이 같아진다const words = new Recent<string>(k);const out: string[] = [];for (const line of lines.slice(1, 1 + q)) {  const [kind, cmd, arg] = line.trim().split(" ");  if (kind === "num") {    if (cmd === "show") out.push(nums.show());    else if (cmd === "add") nums.add(Number(arg));    else nums.remove(Number(arg));  } else {    if (cmd === "show") out.push(words.show());    else if (cmd === "add") words.add(arg);    else words.remove(arg);  }}console.log(out.join("\n"));
JavaMain.java · 63 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.List; // 가장 최근 것부터 capacity개까지만 기억하는 목록. T가 무엇이든 같은 코드로 쓴다class Recent<T> {    private final List<T> items = new ArrayList<>();    private final int capacity;     Recent(int capacity) { this.capacity = capacity; }     void add(T x) {        remove(x);        items.add(0, x);        if (items.size() > capacity) items.remove(items.size() - 1);    }     void remove(T x) {        // T는 객체라서 ==는 같은 객체인지를 묻는다. 값이 같은지는 equals로 묻는다        for (int i = 0; i < items.size(); i++) {            if (items.get(i).equals(x)) {                items.remove(i);                return;            }        }    }     String show() {        if (items.isEmpty()) return "-";        StringBuilder sb = new StringBuilder();        for (T x : items) {            if (sb.length() > 0) sb.append(' ');            sb.append(x);        }        return sb.toString();    }} public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        String[] first = br.readLine().trim().split(" ");        int k = Integer.parseInt(first[0]), q = Integer.parseInt(first[1]);        Recent<Integer> nums = new Recent<>(k); // Recent<int>는 안 된다: 제네릭에는 Integer로 담는다        Recent<String> words = new Recent<>(k);        StringBuilder out = new StringBuilder();        for (int e = 0; e < q; e++) {            String[] p = br.readLine().trim().split(" ");            boolean isNum = p[0].equals("num");            if (p[1].equals("show")) {                out.append(isNum ? nums.show() : words.show()).append('\n');            } else if (isNum) {                int x = Integer.parseInt(p[2]); // "007"도 7로 읽힌다                if (p[1].equals("add")) nums.add(x); else nums.remove(x);            } else {                if (p[1].equals("add")) words.add(p[2]); else words.remove(p[2]);            }        }        System.out.print(out);    }}
C#Main.cs · 57 行
using System;using System.Collections.Generic;using System.IO;using System.Text; // 가장 최근 것부터 capacity개까지만 기억하는 목록. T가 무엇이든 같은 코드로 쓴다class Recent<T>{    private readonly List<T> items = new List<T>();    private readonly int capacity;    // 제약 없는 T에는 ==를 쓸 수 없다(컴파일 오류). 그 T에 맞는 같음은 EqualityComparer가 안다    private static readonly EqualityComparer<T> Same = EqualityComparer<T>.Default;     public Recent(int capacity) { this.capacity = capacity; }     public void Add(T x)    {        Remove(x);        items.Insert(0, x);        if (items.Count > capacity) items.RemoveAt(items.Count - 1);    }     public void Remove(T x)    {        int i = items.FindIndex(y => Same.Equals(y, x));        if (i >= 0) items.RemoveAt(i);    }     public string Show() => items.Count == 0 ? "-" : string.Join(" ", items);} class Program{    static void Main()    {        var input = Console.In;        string[] first = input.ReadLine()!.Trim().Split(' ');        int k = int.Parse(first[0]), q = int.Parse(first[1]);        var nums = new Recent<int>(k);   // C# 제네릭은 int를 포장(boxing) 없이 담는다        var words = new Recent<string>(k);        var sb = new StringBuilder();        for (int e = 0; e < q; e++)        {            string[] p = input.ReadLine()!.Trim().Split(' ');            bool isNum = p[0] == "num";            if (p[1] == "show") sb.Append(isNum ? nums.Show() : words.Show()).Append('\n');            else if (isNum)            {                int x = int.Parse(p[2]);  // "007"도 7로 읽힌다                if (p[1] == "add") nums.Add(x); else nums.Remove(x);            }            else if (p[1] == "add") words.Add(p[2]);            else words.Remove(p[2]);        }        Console.Write(sb);    }}
C++main.cpp · 59 行
#include <algorithm>#include <deque>#include <iostream>#include <sstream>#include <string> // 가장 최근 것부터 capacity개까지만 기억하는 목록. 템플릿이라 T마다 따로 만들어진다template <typename T>class Recent {public:    explicit Recent(std::size_t capacity) : capacity_(capacity) {}     void add(const T &x) {        remove(x);        items_.push_front(x);        if (items_.size() > capacity_) items_.pop_back();    }    void remove(const T &x) {        auto it = std::find(items_.begin(), items_.end(), x);  // T의 ==로 찾는다        if (it != items_.end()) items_.erase(it);    }    std::string show() const {        if (items_.empty()) return "-";        std::ostringstream os;        for (std::size_t i = 0; i < items_.size(); i++) os << (i ? " " : "") << items_[i];        return os.str();    } private:    std::deque<T> items_;    std::size_t capacity_;}; int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    std::size_t k;    int q;    std::cin >> k >> q;    Recent<long long> nums(k);  // 수는 수로 읽어야 "007"과 "7"이 같아진다    Recent<std::string> words(k);    std::string kind, cmd, arg;    std::string out;    for (int e = 0; e < q; e++) {        std::cin >> kind >> cmd;        if (cmd == "show") {            out += (kind == "num" ? nums.show() : words.show()) + '\n';            continue;        }        std::cin >> arg;        if (kind == "num") {            long long x = std::stoll(arg);            if (cmd == "add") nums.add(x); else nums.remove(x);        } else {            if (cmd == "add") words.add(arg); else words.remove(arg);        }    }    std::cout << out;}