{ }codelper
查询式处理(LINQ、流)P0263

二手市场搜索框

二手市场上有 n 件物品,要依次回答 q 个搜索提问。
每件物品有名字、种类和价格。
提问“种类 最高价 k”表示选出该种类且价格不超过最高价的物品。
种类为 * 时不论种类,从所有物品中选。
对每个提问,在一行中先写选出的物品数 c,再写出选出物品中前 k 个的名字,用一个空格隔开。
选出的物品价格便宜的在前,价格相同时按名字的字典序(字符编码顺序,所以数字在英文小写字母之前,ab 在 abc 之前)在前。
若 c 小于 k,则写出全部 c 个;若 c 为 0,只输出 0。
价格只以不带逗号的数字给出,输出也只写数字。
第一行给出 n 和 q,接下来 n 行以“名字 种类 价格”给出物品,再接下来 q 行每行一个提问。
试着不用循环,而用 筛选 → 排序 → 取前 k 个 → 只取名字 连起来的查询式处理来解题。

输入
1 ≤ n ≤ 1,000,1 ≤ q ≤ 1,000,1 ≤ k ≤ 10。
物品名字由 1~10 个英文小写字母或数字组成,互不相同。
种类由 1~10 个英文小写字母组成(提问中的种类可能不属于任何物品)。
1 ≤ 价格、最高价 ≤ 1,000,000,000(整数)
输出
每个提问一行
样例 1 · stdin
6 4
lamp9 lamp 12000
bluebike bike 85000
redbike bike 85000
kidbike bike 30000
oldlamp lamp 9000
citybike bike 120000
bike 90000 2
lamp 5000 3
* 12000 5
bike 1000000000 10
stdout
3 kidbike bluebike
0
2 oldlamp lamp9
4 kidbike bluebike redbike citybike
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

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

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

本题只接受语言自带把筛选、排序、截取、选取连起来的工具的 Python(列表推导式、sorted)、JavaScript/TypeScript(数组方法)、Java(流)、C#(LINQ)和 C++(C++20 的 std::views)。
C 和 Craykon 没有接收函数来筛选或变换的列表工具。
同样的事情:Python [it for it in items if ...] → sorted(key=lambda it: (it.price, it.name))[:k],JS/TS filter → sort → slice → map,Java 先 filter → toList() 再 sorted → limit → map,C# 先 Where → ToList() 再 OrderBy → ThenBy → Take → Select,C++ 把 items | std::views::filter(...) 装进 vector,用 std::ranges::sort 排序后再 std::views::take(k)。
本题的陷阱是把筛选结果用两次:一次数个数,一次取名字。
① Python 的 filter()、map() 返回只能走一遍的迭代器,为数个数走完之后,sorted 拿到的是空的。
用列表推导式或 list(...) 先存下来。
② Java 的流也只能流一次,对流变量 count() 后再 sorted() 会抛出 IllegalStateException。
先用 toList() 收集,需要时再新建 stream()。
C# 的 LINQ 正相反,它是延迟执行的,不加 ToList() 用两次虽然不报错,但每次都从头重新筛选。
③ C# 的 OrderBy(...).OrderBy(...) 中,第二个 OrderBy 成为新的首要键,按名字重新排序(前面的价格只在名字相同时才起作用,而名字互不相同)。
第二个键要用 ThenBy(Java 用 thenComparing)接上。
名字顺序传入 StringComparer.Ordinal 就确定是字符编码顺序。
④ JS 中 split 得到的价格是字符串,忘了 Number() 就会按字典序比较,"100" <= "99" 为真。
TypeScript 在类型写明 price: number 时放入字符串会报类型错误。
⑤ 只按价格排序,价格相同的会保持输入顺序。
要同时给出两个键。
另外,C++ 的 std::views::filter 是惰性视图,不能直接排序(不支持随机访问);JS 的 sort 会就地修改数组,但 filter 返回的是新数组,原列表保持不变。

Pythonmain.py · 22 行
import sysfrom collections import namedtuple Item = namedtuple("Item", "name kind price") lines = sys.stdin.read().split("\n")n, q = map(int, lines[0].split())items = []for line in lines[1 : n + 1]:    name, kind, price = line.split()    items.append(Item(name, kind, int(price))) out = []for line in lines[n + 1 : n + 1 + q]:    kind, limit, k = line.split()    limit, k = int(limit), int(k)    # 거르기: 목록 내포는 목록을 만들어 두므로 개수를 센 뒤에도 다시 쓸 수 있다    picked = [it for it in items if (kind == "*" or it.kind == kind) and it.price <= limit]    # 정렬 → 앞에서 k개 → 이름만    top = [it.name for it in sorted(picked, key=lambda it: (it.price, it.name))[:k]]    out.append(" ".join([str(len(picked))] + top))print("\n".join(out))
JavaScriptmain.js · 19 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [n, q] = lines[0].trim().split(" ").map(Number);const items = lines.slice(1, n + 1).map((line) => {  const [name, kind, price] = line.trim().split(" ");  return { name, kind, price: Number(price) }; // Number가 없으면 가격이 문자열로 비교된다}); const byPriceThenName = (a, b) => a.price - b.price || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0); const out = [];for (const line of lines.slice(n + 1, n + 1 + q)) {  const [kind, limitText, kText] = line.trim().split(" ");  const limit = Number(limitText);  const picked = items.filter((it) => (kind === "*" || it.kind === kind) && it.price <= limit);  // filter는 새 배열을 주므로 sort가 picked를 바꾸어도 items는 그대로다  const top = picked.sort(byPriceThenName).slice(0, Number(kText)).map((it) => it.name);  out.push([picked.length, ...top].join(" "));}console.log(out.join("\n"));
TypeScriptmain.ts · 26 行
import { readFileSync } from "node:fs"; interface Item {  name: string;  kind: string;  price: number;} const lines: string[] = readFileSync(0, "utf8").split("\n");const [n, q] = lines[0].trim().split(" ").map(Number);const items: Item[] = lines.slice(1, n + 1).map((line) => {  const [name, kind, price] = line.trim().split(" ");  return { name, kind, price: Number(price) }; // Item 타입이 price를 number로 요구해서 Number를 빠뜨리면 타입 오류다}); const byPriceThenName = (a: Item, b: Item): number => a.price - b.price || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0); const out: string[] = [];for (const line of lines.slice(n + 1, n + 1 + q)) {  const [kind, limitText, kText] = line.trim().split(" ");  const limit = Number(limitText);  const picked: Item[] = items.filter((it) => (kind === "*" || it.kind === kind) && it.price <= limit);  const top: string[] = picked.sort(byPriceThenName).slice(0, Number(kText)).map((it) => it.name);  out.push([picked.length, ...top].join(" "));}console.log(out.join("\n"));
JavaMain.java · 40 行
import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.Comparator;import java.util.List;import java.util.stream.Collectors; public class Main {    record Item(String name, String kind, long price) {}     public static void main(String[] args) throws Exception {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        String[] first = in.readLine().trim().split(" ");        int n = Integer.parseInt(first[0]), q = Integer.parseInt(first[1]);        List<Item> items = new ArrayList<>();        for (int i = 0; i < n; i++) {            String[] p = in.readLine().trim().split(" ");            items.add(new Item(p[0], p[1], Long.parseLong(p[2])));        }        Comparator<Item> byPriceThenName = Comparator.comparingLong(Item::price).thenComparing(Item::name);        StringBuilder out = new StringBuilder();        for (int i = 0; i < q; i++) {            String[] p = in.readLine().trim().split(" ");            String kind = p[0];            long limit = Long.parseLong(p[1]);            int k = Integer.parseInt(p[2]);            // 스트림은 한 번만 흘려보낼 수 있다: 거른 결과를 목록으로 받아 두고 두 번 쓴다            List<Item> picked = items.stream()                    .filter(it -> (kind.equals("*") || it.kind().equals(kind)) && it.price() <= limit)                    .toList();            String top = picked.stream()                    .sorted(byPriceThenName)                    .limit(k)                    .map(it -> " " + it.name())                    .collect(Collectors.joining());            out.append(picked.size()).append(top).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 37 行
using System;using System.Collections.Generic;using System.Linq;using System.Text; record Item(string Name, string Kind, long Price); class Program{    static void Main()    {        int[] first = Console.ReadLine()!.Trim().Split(' ').Select(int.Parse).ToArray();        int n = first[0], q = first[1];        List<Item> items = Enumerable.Range(0, n)            .Select(_ => Console.ReadLine()!.Trim().Split(' '))            .Select(p => new Item(p[0], p[1], long.Parse(p[2])))            .ToList();        var sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            string[] p = Console.ReadLine()!.Trim().Split(' ');            string kind = p[0];            long limit = long.Parse(p[1]);            int k = int.Parse(p[2]);            List<Item> picked = items                .Where(it => (kind == "*" || it.Kind == kind) && it.Price <= limit)                .ToList(); // ToList로 한 번 계산해 둔다(그냥 두면 쓸 때마다 다시 거른다)            IEnumerable<string> top = picked                .OrderBy(it => it.Price)                .ThenBy(it => it.Name, StringComparer.Ordinal) // 두 번째 기준은 OrderBy가 아니라 ThenBy                .Take(k)                .Select(it => it.Name);            sb.Append(string.Join(" ", top.Prepend(picked.Count.ToString()))).Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 38 行
#include <algorithm>#include <iostream>#include <ranges>#include <string>#include <tuple>#include <vector> struct Item {    std::string name, kind;    long long price;}; int main() {    std::ios::sync_with_stdio(false);    int n, q;    std::cin >> n >> q;    std::vector<Item> items(n);    for (auto& it : items) std::cin >> it.name >> it.kind >> it.price;     auto byPriceThenName = [](const Item& a, const Item& b) {        return std::tie(a.price, a.name) < std::tie(b.price, b.name);    };    for (int i = 0; i < q; i++) {        std::string kind;        long long limit;        int k;        std::cin >> kind >> limit >> k;        // views::filter는 게으른 보기(view)라서 정렬할 수 없다: vector로 받아 둔다        auto view = items | std::views::filter([&](const Item& it) {                        return (kind == "*" || it.kind == kind) && it.price <= limit;                    });        std::vector<Item> picked(view.begin(), view.end());        std::ranges::sort(picked, byPriceThenName);        std::cout << picked.size();        for (const Item& it : picked | std::views::take(k)) std::cout << ' ' << it.name;        std::cout << '\n';    }}