{ }codelper
列挙型switch 文P0156

注文の状態機械

オンラインショップに注文が n 件あり、1 番から n 番まですべて CREATED の状態から始まる。
注文の状態は CREATED、PAID、SHIPPED、DELIVERED、CANCELLED、REFUNDED、RETURNED の七つである。
イベントは pay、ship、deliver、cancel、return の五つで、状態は次の六つの場合にだけ変わる。
- CREATED で pay → PAID - CREATED で cancel → CANCELLED - PAID で ship → SHIPPED - PAID で cancel → REFUNDED - SHIPPED で deliver → DELIVERED - DELIVERED で return → RETURNED それ以外の(状態, イベント)の組はすべて拒否され、その注文の状態は変わらない。
CANCELLED、REFUNDED、RETURNED ではどのイベントも受け付けない。
一行目に n とイベント数 q、続く q 行に「注文番号 イベント」が起きた順に与えられる。
1 番から n 番の注文の最後の状態を一行に一つずつ大文字の名前で出力し、最後の行に拒否されたイベントの数を出力せよ。
状態を七つの値を持つ列挙型(enum)にし、いまの状態とイベントから次の状態を決める関数一つで状態機械を組んでみよう。

入力
1 ≤ n ≤ 1,000、1 ≤ q ≤ 10,000、1 ≤ 注文番号 ≤ n、イベントは pay・ship・deliver・cancel・return のどれか
出力
n+1 行:注文ごとの状態名、最後の行に拒否されたイベント数
例 1 · stdin
3 6
1 pay
1 ship
2 cancel
2 pay
3 pay
3 cancel
stdout
SHIPPED
CANCELLED
REFUNDED
1
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 6 言語の解答

この問題は列挙型がある Python・TypeScript・Java・C#・C・C++ だけを受け付けます。
JavaScript には列挙型の構文がなく(Object.freeze({...}) でまねるだけ)、Craykon にも列挙型はありません。
状態の名前の出し方が言語ごとに違います。
Python class State(Enum) の値は s.name が "PAID" で、print(s) は State.PAID を出力します。
TypeScript の数値 enum の値は 0, 1, 2… なので console.log(s) は 1 を出力し、名前は逆引き表 State[s] で取り出します。
Java の enum はクラスなので toString() がそのまま名前で、switch 式はすべての定数を扱わないとコンパイルできません。
C# の enum の ToString() は宣言した名前そのまま(Paid)なので、この問題では ToUpperInvariant() で合わせ、(s, ev) switch { (State.Paid, "cancel") => ... } のように状態とイベントをタプルパターンで一度に照合します。
C の enum は名前付きの int 定数にすぎないので名前表の配列 NAME[s] を別に置き、C++ の enum class も名前を知らないので switch で名前を返します。
落とし穴は四つ:① C・C++ の switch で break を忘れると、CREATED が受け付けない ship が下の PAID の case へ流れ落ちて SHIPPED になります。
② print(s)(Python)や console.log(s)(TypeScript)のように列挙型の値をそのまま出力すると、名前ではなく State.PAID・1 が出ます。
③ cancel は状態によって結果が違います。
CREATED なら CANCELLED、PAID なら REFUNDED、それ以外は拒否です。
(_, "cancel") => Cancelled のように状態を見ずに一行で書くと誤りです。
④ Java でイベント文字列を event == "pay" で比べると、読み込んだ文字列とリテラルは別のオブジェクトなので常に偽です。
equals で比べます。

Pythonmain.py · 38 行
import sysfrom enum import Enum  class State(Enum):    CREATED = 1    PAID = 2    SHIPPED = 3    DELIVERED = 4    CANCELLED = 5    REFUNDED = 6    RETURNED = 7  # 상태 기계: (지금 상태, 사건) → 다음 상태. 표에 없는 짝은 거절NEXT = {    (State.CREATED, "pay"): State.PAID,    (State.CREATED, "cancel"): State.CANCELLED,    (State.PAID, "ship"): State.SHIPPED,    (State.PAID, "cancel"): State.REFUNDED,    (State.SHIPPED, "deliver"): State.DELIVERED,    (State.DELIVERED, "return"): State.RETURNED,} data = sys.stdin.read().split()n, q = int(data[0]), int(data[1])orders = [State.CREATED] * nrejected = 0for i in range(q):    k, event = int(data[2 + 2 * i]) - 1, data[3 + 2 * i]    nxt = NEXT.get((orders[k], event))    if nxt is None:        rejected += 1    else:        orders[k] = nxt print("\n".join(s.name for s in orders))  # print(s)는 "State.PAID"를 찍는다print(rejected)
TypeScriptmain.ts · 43 行
import { readFileSync } from "node:fs"; enum State {  CREATED,  PAID,  SHIPPED,  DELIVERED,  CANCELLED,  REFUNDED,  RETURNED,} // 상태 기계: 받을 수 없는 사건이면 nullfunction next(s: State, event: string): State | null {  switch (s) {    case State.CREATED:      return event === "pay" ? State.PAID : event === "cancel" ? State.CANCELLED : null;    case State.PAID:      return event === "ship" ? State.SHIPPED : event === "cancel" ? State.REFUNDED : null;    case State.SHIPPED:      return event === "deliver" ? State.DELIVERED : null;    case State.DELIVERED:      return event === "return" ? State.RETURNED : null;    default:      return null; // CANCELLED·REFUNDED·RETURNED는 끝 상태  }} const words: string[] = readFileSync(0, "utf8").trim().split(/\s+/);const n = Number(words[0]);const q = Number(words[1]);const orders: State[] = new Array<State>(n).fill(State.CREATED);let rejected = 0;for (let i = 0; i < q; i++) {  const k = Number(words[2 + 2 * i]) - 1;  const ns = next(orders[k], words[3 + 2 * i]);  if (ns === null) rejected++;  else orders[k] = ns;} // 숫자 enum의 값은 0, 1, 2…: 이름은 역방향 표 State[s]로 꺼낸다console.log(orders.map((s) => State[s]).join("\n"));console.log(rejected);
JavaMain.java · 37 行
import java.io.*;import java.util.*; public class Main {    enum State { CREATED, PAID, SHIPPED, DELIVERED, CANCELLED, REFUNDED, RETURNED }     // 상태 기계: 받을 수 없는 사건이면 null    static State next(State s, String event) {        return switch (s) {            case CREATED -> event.equals("pay") ? State.PAID : event.equals("cancel") ? State.CANCELLED : null;            case PAID -> event.equals("ship") ? State.SHIPPED : event.equals("cancel") ? State.REFUNDED : null;            case SHIPPED -> event.equals("deliver") ? State.DELIVERED : null;            case DELIVERED -> event.equals("return") ? State.RETURNED : null;            case CANCELLED, REFUNDED, RETURNED -> null; // 끝 상태        };    }     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());        State[] orders = new State[n];        Arrays.fill(orders, State.CREATED);        int rejected = 0;        for (int i = 0; i < q; i++) {            st = new StringTokenizer(br.readLine());            int k = Integer.parseInt(st.nextToken()) - 1;            State ns = next(orders[k], st.nextToken());            if (ns == null) rejected++;            else orders[k] = ns;        }        StringBuilder sb = new StringBuilder();        for (State s : orders) sb.append(s).append('\n'); // enum의 toString()은 이름        sb.append(rejected).append('\n');        System.out.print(sb);    }}
C#Main.cs · 40 行
using System;using System.Linq;using System.Text; enum State { Created, Paid, Shipped, Delivered, Cancelled, Refunded, Returned } class Program{    // 상태 기계: (상태, 사건) 튜플 패턴. 받을 수 없는 사건이면 null    static State? Next(State s, string ev) => (s, ev) switch    {        (State.Created, "pay") => State.Paid,        (State.Created, "cancel") => State.Cancelled,        (State.Paid, "ship") => State.Shipped,        (State.Paid, "cancel") => State.Refunded,        (State.Shipped, "deliver") => State.Delivered,        (State.Delivered, "return") => State.Returned,        _ => null,    };     static void Main()    {        var first = Console.ReadLine()!.Split(' ');        int n = int.Parse(first[0]), q = int.Parse(first[1]);        var orders = Enumerable.Repeat(State.Created, n).ToArray();        int rejected = 0;        for (int i = 0; i < q; i++)        {            var p = Console.ReadLine()!.Split(' ');            int k = int.Parse(p[0]) - 1;            State? ns = Next(orders[k], p[1]);            if (ns is State s) orders[k] = s;            else rejected++;        }        var sb = new StringBuilder();        foreach (var s in orders) sb.Append(s.ToString().ToUpperInvariant()).Append('\n'); // ToString()은 "Paid"        sb.Append(rejected).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 60 行
#include <iostream>#include <optional>#include <string>#include <vector> enum class State { Created, Paid, Shipped, Delivered, Cancelled, Refunded, Returned }; // enum class도 이름을 모른다: switch로 이름표를 만든다const char *name(State s) {    switch (s) {    case State::Created: return "CREATED";    case State::Paid: return "PAID";    case State::Shipped: return "SHIPPED";    case State::Delivered: return "DELIVERED";    case State::Cancelled: return "CANCELLED";    case State::Refunded: return "REFUNDED";    case State::Returned: return "RETURNED";    }    return "?";} // 상태 기계: 받을 수 없는 사건이면 빈 optionalstd::optional<State> next(State s, const std::string &ev) {    switch (s) {    case State::Created:        if (ev == "pay") return State::Paid;        if (ev == "cancel") return State::Cancelled;        break;    case State::Paid:        if (ev == "ship") return State::Shipped;        if (ev == "cancel") return State::Refunded;        break;    case State::Shipped:        if (ev == "deliver") return State::Delivered;        break;    case State::Delivered:        if (ev == "return") return State::Returned;        break;    default:        break;  // 끝 상태    }    return std::nullopt;} int main() {    std::ios::sync_with_stdio(false);    int n, q;    std::cin >> n >> q;    std::vector<State> orders(n, State::Created);    int rejected = 0;    for (int i = 0; i < q; i++) {        int k;        std::string ev;        std::cin >> k >> ev;        if (auto ns = next(orders[k - 1], ev)) orders[k - 1] = *ns;        else rejected++;    }    for (State s : orders) std::cout << name(s) << '\n';    std::cout << rejected << '\n';}
Cmain.c · 46 行
#include <stdio.h>#include <string.h> enum State { CREATED, PAID, SHIPPED, DELIVERED, CANCELLED, REFUNDED, RETURNED }; /* C 열거형은 이름 붙은 int일 뿐이라 이름표를 따로 둔다(차례가 enum과 같아야 한다) */static const char *NAME[] = { "CREATED", "PAID", "SHIPPED", "DELIVERED", "CANCELLED", "REFUNDED", "RETURNED" }; /* 다음 상태를 *s에 쓰고 1, 받을 수 없는 사건이면 0 */int step(enum State *s, const char *ev) {    switch (*s) {    case CREATED:        if (strcmp(ev, "pay") == 0) { *s = PAID; return 1; }        if (strcmp(ev, "cancel") == 0) { *s = CANCELLED; return 1; }        break;                       /* break가 없으면 아래 case로 흘러내린다 */    case PAID:        if (strcmp(ev, "ship") == 0) { *s = SHIPPED; return 1; }        if (strcmp(ev, "cancel") == 0) { *s = REFUNDED; return 1; }        break;    case SHIPPED:        if (strcmp(ev, "deliver") == 0) { *s = DELIVERED; return 1; }        break;    case DELIVERED:        if (strcmp(ev, "return") == 0) { *s = RETURNED; return 1; }        break;    default:                         /* 끝 상태 */        break;    }    return 0;} int main(void) {    static enum State orders[1000];    int n, q, rejected = 0;    scanf("%d %d", &n, &q);    for (int i = 0; i < n; i++) orders[i] = CREATED;    for (int i = 0; i < q; i++) {        int k;        char ev[16];        scanf("%d %15s", &k, ev);        if (!step(&orders[k - 1], ev)) rejected++;    }    for (int i = 0; i < n; i++) puts(NAME[orders[i]]);    printf("%d\n", rejected);    return 0;}