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