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