{ }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> 存 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;}