{ }codelper
接口P0383

按能做的事归类的智能家居设备

家里有 n 台设备,设备有四种。
- 灯(lamp):可以开关。
开着时用 9W,关着时用 0W。
- 调光灯(dimlamp):可以开关,也可以设定档位。
开着时用 档位 × 2W,关着时用 0W。
- 风扇(fan):可以设定档位,但不能开关。
始终用 档位 × 3W。
- 传感器(sensor):既不能开关,也不能设定档位。
始终用 1W。
可以开关的设备一开始是关着的,可以设定档位的设备一开始是 1 档。
调光灯关着的时候也可以改档位,改过的档位在下次打开时使用。
按给出的顺序处理 q 条命令,每条命令输出一行。
- power:输出所有设备现在用的功率之和。
- allon、alloff:把所有可以开关的设备打开或关掉。
输出状态发生变化的设备数。
- alldim v:把所有可以设定档位的设备调到 v 档。
输出档位发生变化的设备数。
- on 名字、off 名字:把这台设备打开或关掉。
- dim 名字 v:把这台设备调到 v 档。
带名字的命令,如果这台设备做不了这件事就输出 no,做得了但已经是那个状态、什么都没变就输出 same,变了就输出 ok。
把“可以开关”和“可以设定档位”做成两个接口,让每个设备类只实现自己能做的事。
把所有设备放进一个列表,然后不问种类,只问它是否实现了那个接口来处理命令。

输入
第一行给出 n 和 q,用一个空格隔开。
接下来 n 行,每行给出设备的种类(lamp、dimlamp、fan、sensor 之一)和名字,用一个空格隔开。
接下来 q 行,每行一条命令,命令里的词用一个空格隔开。
1 ≤ n ≤ 100,1 ≤ q ≤ 1000,1 ≤ v ≤ 10。
名字由 1~10 个英文小写字母或数字组成,互不相同。
命令里的名字都是存在的设备
输出
q 行。
每条命令一行:一个数,或者 ok、same、no 之一
样例 1 · stdin
4 12
lamp desk
dimlamp bed
fan air
sensor door
power
allon
dim desk 4
alldim 4
on air
power
alloff
dim bed 7
on bed
on bed
allon
power
stdout
4
2
no
2
no
30
2
ok
ok
same
1
36
main.py
输入(stdin)

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

$ 就绪

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

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

这道题只接受有接口或起同样作用的语法的 Python、TypeScript、Java、C# 和 C++。
JavaScript 没有写接口的地方,C 和 Craykon 没有类。
要点是一个类可以实现多个接口,以及运行时怎样问“这个对象实现了那个接口吗”。
Java 写 class DimLamp extends Device implements Switchable, Dimmable,用 d instanceof Switchable s 一边问一边用 s 接住。
C# 是 class DimLamp : Device, ISwitchable, IDimmable 和 d is ISwitchable s。
C++ 继承多个只有纯虚函数的结构体,看 dynamic_cast<Switchable*>(d) 是不是 nullptr。
Python 继承多个 abc.ABC,用 isinstance(d, Switchable) 来问。
TypeScript 的接口在运行的代码里会消失,所以写 d instanceof Switchable 是类型错误(TS2693)。
因此解法里写了类型守卫函数,用 "turn" in d 看有没有这个方法,并返回 d is Device & Switchable。
陷阱一:不问接口而问具体的类(dynamic_cast<Lamp*>),调光灯就被漏掉了。
例子里的 allon 变成 1,on bed 变成 no。
陷阱二:不问就直接转换,各语言的表现也不同。
Java 的 (Dimmable) d 在运行时检查,在例子的 dim desk 4 处因 ClassCastException 而停止。
TypeScript 的 as Device & Dimmable 在运行时什么都不检查,于是同一行出现 TypeError: d.setLevel is not a function。
陷阱三:在 Python 里如果用 typing.Protocol 来写接口,除非加上 @runtime_checkable,否则 isinstance 会抛出 TypeError(在例子的 allon 处停止)。
陷阱四:allon 输出的不是能打开的设备数,而是状态发生变化的设备数。
把能开关的设备全都数上,例子里最后一个 allon 就不是 1 而是 2。
所以让接口的方法返回“有没有变”。
陷阱五:如果不改关着的调光灯的档位,例子里的 dim bed 7 会变成 same,最后的功率变成 30。

Pythonmain.py · 118 行
import sysfrom abc import ABC, abstractmethod  class Device(ABC):    def __init__(self, name: str):        self.name = name     @abstractmethod    def power(self) -> int: ...  class Switchable(ABC):    """켜고 끌 수 있다. 상태가 바뀌었으면 True를 돌려준다"""     @abstractmethod    def turn(self, on: bool) -> bool: ...  class Dimmable(ABC):    """단계를 정할 수 있다. 단계가 바뀌었으면 True를 돌려준다"""     @abstractmethod    def set_level(self, v: int) -> bool: ...  class Lamp(Device, Switchable):    def __init__(self, name: str):        super().__init__(name)        self.on = False     def turn(self, on: bool) -> bool:        changed = self.on != on        self.on = on        return changed     def power(self) -> int:        return 9 if self.on else 0  class DimLamp(Device, Switchable, Dimmable):    def __init__(self, name: str):        super().__init__(name)        self.on = False        self.level = 1     def turn(self, on: bool) -> bool:        changed = self.on != on        self.on = on        return changed     def set_level(self, v: int) -> bool:  # 꺼져 있어도 단계는 바뀐다        changed = self.level != v        self.level = v        return changed     def power(self) -> int:        return 2 * self.level if self.on else 0  class Fan(Device, Dimmable):    def __init__(self, name: str):        super().__init__(name)        self.level = 1     def set_level(self, v: int) -> bool:        changed = self.level != v        self.level = v        return changed     def power(self) -> int:        return 3 * self.level  class Sensor(Device):    def power(self) -> int:        return 1  KINDS = {"lamp": Lamp, "dimlamp": DimLamp, "fan": Fan, "sensor": Sensor}  def word(changed: bool) -> str:    return "ok" if changed else "same"  def main() -> None:    lines = sys.stdin.read().split("\n")    n, q = map(int, lines[0].split())    devices: list[Device] = []    by_name: dict[str, Device] = {}    for i in range(1, n + 1):        kind, name = lines[i].split()        d = KINDS[kind](name)        devices.append(d)        by_name[name] = d     out = []    for i in range(n + 1, n + 1 + q):        p = lines[i].split()        cmd = p[0]        if cmd == "power":            out.append(str(sum(d.power() for d in devices)))        elif cmd in ("allon", "alloff"):            out.append(str(sum(1 for d in devices if isinstance(d, Switchable) and d.turn(cmd == "allon"))))        elif cmd == "alldim":            v = int(p[1])            out.append(str(sum(1 for d in devices if isinstance(d, Dimmable) and d.set_level(v))))        elif cmd in ("on", "off"):            d = by_name[p[1]]            out.append(word(d.turn(cmd == "on")) if isinstance(d, Switchable) else "no")        else:  # dim 이름 v            d = by_name[p[1]]            out.append(word(d.set_level(int(p[2]))) if isinstance(d, Dimmable) else "no")    print("\n".join(out))  main()
TypeScriptmain.ts · 120 行
import { readFileSync } from "node:fs"; // 켜고 끌 수 있다. 상태가 바뀌었으면 true를 돌려준다interface Switchable {  turn(on: boolean): boolean;} // 단계를 정할 수 있다. 단계가 바뀌었으면 true를 돌려준다interface Dimmable {  setLevel(v: number): boolean;} abstract class Device {  constructor(readonly name: string) {}  abstract power(): number;} class Lamp extends Device implements Switchable {  private on = false;  turn(on: boolean): boolean {    const changed = this.on !== on;    this.on = on;    return changed;  }  power(): number {    return this.on ? 9 : 0;  }} class DimLamp extends Device implements Switchable, Dimmable {  private on = false;  private level = 1;  turn(on: boolean): boolean {    const changed = this.on !== on;    this.on = on;    return changed;  }  setLevel(v: number): boolean {    // 꺼져 있어도 단계는 바뀐다    const changed = this.level !== v;    this.level = v;    return changed;  }  power(): number {    return this.on ? 2 * this.level : 0;  }} class Fan extends Device implements Dimmable {  private level = 1;  setLevel(v: number): boolean {    const changed = this.level !== v;    this.level = v;    return changed;  }  power(): number {    return 3 * this.level;  }} class Sensor extends Device {  power(): number {    return 1;  }} // 인터페이스는 실행할 때 없으므로, 메서드가 있는지를 보고 타입을 좁혀 주는 함수를 만든다function isSwitchable(d: Device): d is Device & Switchable {  return "turn" in d;}function isDimmable(d: Device): d is Device & Dimmable {  return "setLevel" in d;} function make(kind: string, name: string): Device {  if (kind === "lamp") return new Lamp(name);  if (kind === "dimlamp") return new DimLamp(name);  if (kind === "fan") return new Fan(name);  return new Sensor(name);} const word = (changed: boolean): string => (changed ? "ok" : "same"); const lines: string[] = readFileSync(0, "utf8").split("\n");const [n, q] = lines[0].trim().split(/\s+/).map(Number);const devices: Device[] = [];const byName = new Map<string, Device>();for (let i = 1; i <= n; i++) {  const [kind, name] = lines[i].trim().split(/\s+/);  const d = make(kind, name);  devices.push(d);  byName.set(name, d);} const out: string[] = [];for (let i = n + 1; i <= n + q; i++) {  const p = lines[i].trim().split(/\s+/);  const cmd = p[0];  if (cmd === "power") {    let total = 0;    for (const d of devices) total += d.power();    out.push(String(total));  } else if (cmd === "allon" || cmd === "alloff") {    let count = 0;    for (const d of devices) if (isSwitchable(d) && d.turn(cmd === "allon")) count++;    out.push(String(count));  } else if (cmd === "alldim") {    const v = Number(p[1]);    let count = 0;    for (const d of devices) if (isDimmable(d) && d.setLevel(v)) count++;    out.push(String(count));  } else if (cmd === "on" || cmd === "off") {    const d = byName.get(p[1])!;    out.push(isSwitchable(d) ? word(d.turn(cmd === "on")) : "no");  } else {    const d = byName.get(p[1])!;    out.push(isDimmable(d) ? word(d.setLevel(Number(p[2]))) : "no");  }}console.log(out.join("\n"));
JavaMain.java · 121 行
import java.io.*;import java.util.*; // 켜고 끌 수 있다. 상태가 바뀌었으면 true를 돌려준다interface Switchable {    boolean turn(boolean on);} // 단계를 정할 수 있다. 단계가 바뀌었으면 true를 돌려준다interface Dimmable {    boolean setLevel(int v);} abstract class Device {    final String name;    Device(String name) { this.name = name; }    abstract int power();} class Lamp extends Device implements Switchable {    private boolean on = false;    Lamp(String name) { super(name); }    public boolean turn(boolean on) {        boolean changed = this.on != on;        this.on = on;        return changed;    }    int power() { return on ? 9 : 0; }} class DimLamp extends Device implements Switchable, Dimmable {    private boolean on = false;    private int level = 1;    DimLamp(String name) { super(name); }    public boolean turn(boolean on) {        boolean changed = this.on != on;        this.on = on;        return changed;    }    public boolean setLevel(int v) { // 꺼져 있어도 단계는 바뀐다        boolean changed = level != v;        level = v;        return changed;    }    int power() { return on ? 2 * level : 0; }} class Fan extends Device implements Dimmable {    private int level = 1;    Fan(String name) { super(name); }    public boolean setLevel(int v) {        boolean changed = level != v;        level = v;        return changed;    }    int power() { return 3 * level; }} class Sensor extends Device {    Sensor(String name) { super(name); }    int power() { return 1; }} public class Main {    static Device make(String kind, String name) {        switch (kind) {            case "lamp": return new Lamp(name);            case "dimlamp": return new DimLamp(name);            case "fan": return new Fan(name);            default: return new Sensor(name);        }    }     static String word(boolean changed) { return changed ? "ok" : "same"; }     public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(br.readLine());        int n = Integer.parseInt(st.nextToken()), q = Integer.parseInt(st.nextToken());        List<Device> devices = new ArrayList<>();        Map<String, Device> byName = new HashMap<>();        for (int i = 0; i < n; i++) {            st = new StringTokenizer(br.readLine());            String kind = st.nextToken(), name = st.nextToken();            Device d = make(kind, name);            devices.add(d);            byName.put(name, d);        }         StringBuilder sb = new StringBuilder();        for (int i = 0; i < q; i++) {            st = new StringTokenizer(br.readLine());            String cmd = st.nextToken();            if (cmd.equals("power")) {                int total = 0;                for (Device d : devices) total += d.power();                sb.append(total);            } else if (cmd.equals("allon") || cmd.equals("alloff")) {                int count = 0;                for (Device d : devices)                    if (d instanceof Switchable s && s.turn(cmd.equals("allon"))) count++;                sb.append(count);            } else if (cmd.equals("alldim")) {                int v = Integer.parseInt(st.nextToken());                int count = 0;                for (Device d : devices)                    if (d instanceof Dimmable m && m.setLevel(v)) count++;                sb.append(count);            } else if (cmd.equals("on") || cmd.equals("off")) {                Device d = byName.get(st.nextToken());                sb.append(d instanceof Switchable s ? word(s.turn(cmd.equals("on"))) : "no");            } else {                Device d = byName.get(st.nextToken());                int v = Integer.parseInt(st.nextToken());                sb.append(d instanceof Dimmable m ? word(m.setLevel(v)) : "no");            }            sb.append('\n');        }        System.out.print(sb);    }}
C#Main.cs · 146 行
using System;using System.Collections.Generic;using System.Text; // 켜고 끌 수 있다. 상태가 바뀌었으면 true를 돌려준다interface ISwitchable{    bool Turn(bool on);} // 단계를 정할 수 있다. 단계가 바뀌었으면 true를 돌려준다interface IDimmable{    bool SetLevel(int v);} abstract class Device{    public string Name { get; }    protected Device(string name) { Name = name; }    public abstract int Power();} class Lamp : Device, ISwitchable{    private bool on = false;    public Lamp(string name) : base(name) { }    public bool Turn(bool on)    {        bool changed = this.on != on;        this.on = on;        return changed;    }    public override int Power() => on ? 9 : 0;} class DimLamp : Device, ISwitchable, IDimmable{    private bool on = false;    private int level = 1;    public DimLamp(string name) : base(name) { }    public bool Turn(bool on)    {        bool changed = this.on != on;        this.on = on;        return changed;    }    public bool SetLevel(int v) // 꺼져 있어도 단계는 바뀐다    {        bool changed = level != v;        level = v;        return changed;    }    public override int Power() => on ? 2 * level : 0;} class Fan : Device, IDimmable{    private int level = 1;    public Fan(string name) : base(name) { }    public bool SetLevel(int v)    {        bool changed = level != v;        level = v;        return changed;    }    public override int Power() => 3 * level;} class Sensor : Device{    public Sensor(string name) : base(name) { }    public override int Power() => 1;} class Program{    static Device Make(string kind, string name)    {        switch (kind)        {            case "lamp": return new Lamp(name);            case "dimlamp": return new DimLamp(name);            case "fan": return new Fan(name);            default: return new Sensor(name);        }    }     static string Word(bool changed) => changed ? "ok" : "same";     static void Main()    {        var lines = Console.In.ReadToEnd().Split('\n');        var first = lines[0].Split(' ', StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(first[0]), q = int.Parse(first[1]);        var devices = new List<Device>();        var byName = new Dictionary<string, Device>();        for (int i = 1; i <= n; i++)        {            var p = lines[i].Trim().Split(' ');            Device d = Make(p[0], p[1]);            devices.Add(d);            byName[p[1]] = d;        }         var sb = new StringBuilder();        for (int i = n + 1; i <= n + q; i++)        {            var p = lines[i].Trim().Split(' ');            string cmd = p[0];            if (cmd == "power")            {                int total = 0;                foreach (Device d in devices) total += d.Power();                sb.Append(total);            }            else if (cmd == "allon" || cmd == "alloff")            {                int count = 0;                foreach (Device d in devices)                    if (d is ISwitchable s && s.Turn(cmd == "allon")) count++;                sb.Append(count);            }            else if (cmd == "alldim")            {                int v = int.Parse(p[1]);                int count = 0;                foreach (Device d in devices)                    if (d is IDimmable m && m.SetLevel(v)) count++;                sb.Append(count);            }            else if (cmd == "on" || cmd == "off")            {                Device d = byName[p[1]];                sb.Append(d is ISwitchable s ? Word(s.Turn(cmd == "on")) : "no");            }            else            {                Device d = byName[p[1]];                sb.Append(d is IDimmable m ? Word(m.SetLevel(int.Parse(p[2]))) : "no");            }            sb.Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 129 行
#include <iostream>#include <map>#include <memory>#include <string>#include <vector>using namespace std; // 켜고 끌 수 있다. 상태가 바뀌었으면 true를 돌려준다struct Switchable {    virtual bool turn(bool on) = 0;    virtual ~Switchable() = default;}; // 단계를 정할 수 있다. 단계가 바뀌었으면 true를 돌려준다struct Dimmable {    virtual bool setLevel(int v) = 0;    virtual ~Dimmable() = default;}; struct Device {    string name;    explicit Device(string name) : name(std::move(name)) {}    virtual int power() const = 0;    virtual ~Device() = default; // 가상 함수가 있어야 dynamic_cast를 쓸 수 있다}; class Lamp : public Device, public Switchable {    bool on_ = false;public:    using Device::Device;    bool turn(bool on) override {        bool changed = on_ != on;        on_ = on;        return changed;    }    int power() const override { return on_ ? 9 : 0; }}; class DimLamp : public Device, public Switchable, public Dimmable {    bool on_ = false;    int level_ = 1;public:    using Device::Device;    bool turn(bool on) override {        bool changed = on_ != on;        on_ = on;        return changed;    }    bool setLevel(int v) override { // 꺼져 있어도 단계는 바뀐다        bool changed = level_ != v;        level_ = v;        return changed;    }    int power() const override { return on_ ? 2 * level_ : 0; }}; class Fan : public Device, public Dimmable {    int level_ = 1;public:    using Device::Device;    bool setLevel(int v) override {        bool changed = level_ != v;        level_ = v;        return changed;    }    int power() const override { return 3 * level_; }}; class Sensor : public Device {public:    using Device::Device;    int power() const override { return 1; }}; unique_ptr<Device> make(const string& kind, const string& name) {    if (kind == "lamp") return make_unique<Lamp>(name);    if (kind == "dimlamp") return make_unique<DimLamp>(name);    if (kind == "fan") return make_unique<Fan>(name);    return make_unique<Sensor>(name);} const char* word(bool changed) { return changed ? "ok" : "same"; } int main() {    int n, q;    cin >> n >> q;    vector<unique_ptr<Device>> devices;    map<string, Device*> byName;    for (int i = 0; i < n; i++) {        string kind, name;        cin >> kind >> name;        devices.push_back(make(kind, name));        byName[name] = devices.back().get();    }     for (int i = 0; i < q; i++) {        string cmd;        cin >> cmd;        if (cmd == "power") {            int total = 0;            for (const auto& d : devices) total += d->power();            cout << total << '\n';        } else if (cmd == "allon" || cmd == "alloff") {            int count = 0;            for (const auto& d : devices)                if (auto* s = dynamic_cast<Switchable*>(d.get()); s && s->turn(cmd == "allon")) count++;            cout << count << '\n';        } else if (cmd == "alldim") {            int v;            cin >> v;            int count = 0;            for (const auto& d : devices)                if (auto* m = dynamic_cast<Dimmable*>(d.get()); m && m->setLevel(v)) count++;            cout << count << '\n';        } else if (cmd == "on" || cmd == "off") {            string name;            cin >> name;            auto* s = dynamic_cast<Switchable*>(byName[name]);            cout << (s ? word(s->turn(cmd == "on")) : "no") << '\n';        } else {            string name;            int v;            cin >> name >> v;            auto* m = dynamic_cast<Dimmable*>(byName[name]);            cout << (m ? word(m->setLevel(v)) : "no") << '\n';        }    }    return 0;}