{ }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()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 5개 언어 풀이

이 문제는 인터페이스나 그 구실을 하는 문법이 있는 Python·TypeScript·Java·C#·C++만 받아요.
JavaScript는 인터페이스를 적을 곳이 없고, C와 크레이콘에는 클래스가 없어요.
한 클래스가 인터페이스를 여러 개 구현할 수 있다는 점과, 실행할 때 '이 객체가 그 인터페이스를 구현했나'를 묻는 법이 요점이에요.
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;}