持仓监控面板:当前在场的不同标的数量
Open-Order Dashboard Tile: Distinct Symbols Currently In Play
开始编码实现 solution(events: list[list]) -> list[int]。一台交易台监控面板从订单管理系统的生命周期流读取按到达顺序排列的事件 events,每条事件为 [order_id, symbol, action],action 取值为 "OPEN"(该 order_id 在 symbol 上变为活跃)或 "CLOSE"(该具体 order_id 离开活跃集合)。请在每条事件处理完后,返回当前至少持有一笔 OPEN 单的不同标的的数量,最终输出长度等于 len(events) 的 list[int],顺序与 events 一致。
例
solution([["O-1","S-1","OPEN"],["O-2","S-2","OPEN"],["O-3","S-1","OPEN"],["O-1","S-1","CLOSE"],["O-3","S-1","CLOSE"],["O-2","S-2","CLOSE"]]) 返回 [1,2,2,2,1,0]。事件 0 后活跃标的集合为 {S-1},计数 1;事件 1 加入 S-2,计数变 2;事件 2 在 S-1 上再开一单,但 S-1 已活跃,计数仍为 2;事件 3 关掉 S-1 上两单中的一单(该标的仍有一单未平),计数仍 2;事件 4 关掉 S-1 上最后一单,计数降为 1;事件 5 把 S-2 排空,计数 0。
需要明确的细节:events 为空时返回 [];CLOSE 事件携带的 symbol 字段仅供参考、应被忽略——决定关闭哪一单的只有 order_id,CLOSE 时 symbol 写错也不应让某个标的凭空出现或消失。OPEN 一个已经在场的 order_id 是空操作(第二次 OPEN 静默丢弃,订单保留首次 OPEN 时的标的);CLOSE 一个从未 OPEN 过、或已经被关闭过的 order_id,同样是空操作。order_id 与 symbol 按精确 ASCII 字符串身份比较(大小写、前后空白、纯数字字符串都视为不同)。
朴素地"每条事件后回扫 events[:k] 重算活跃标的"是 O(N^2),会被隐藏的中大型用例打爆;线性解法维护一份每标的活跃单数的字典、一份 order_id -> symbol 的映射,以及一个仅在 0->1 / 1->0 跳变时才更新的"活跃标的数"计数器。
实现细节由 stubs/stub.py 提供。
实践背景
交易台面板上常有一个"在场标的数"的瓦片,直接接到订单管理系统的生命周期流:某标的接到首笔 OPEN 时被点亮加入活跃集合,最后一笔 CLOSE 把该标的清零时从集合中移除,瓦片随事件逐 tick 更新。这是回答"我们当前的覆盖面有多宽"的运营级量度——既是仓位集中度的健康检查、也是给交易所活跃单表内存预算与消息预算的输入信号、还是"会话需要清空时先扫一眼有多少标的待撤"的预判读数。生产系统真正会踩、本题也复现的几处坑:CLOSE 报文只带 order_id(标的字段往往是下游路由层后补的,因此消费侧必须自行按 order 记录标的);对已经关闭过的 order_id 再来一次 CLOSE 必须被静默吃掉(幂等性保证面板在交易所重发 cancel ack 时不会跌到负数);以及把同一个在场 order_id 重复 OPEN(即使第二次写了不同的 symbol)不应被视作一次新的标的激活。把"不同活跃标的数"而不是"未平单总数"作为该瓦片的口径,是因为面板要的是覆盖面的宽度而不是深度。
约束条件
- 0 <= N <= 5000,其中 N 为 solution(events) 的输入长度
- 每条事件为 3 元素列表 [order_id, symbol, action],其中 action 严格为 "OPEN" 或 "CLOSE"
- order_id 与 symbol 均为长度在 1 到 32 之间的 ASCII 字符串,区分大小写、不做归一化
- 重复 OPEN 同一个在场 order_id、CLOSE 一个从未 OPEN 过的 order_id、CLOSE 一个已经关闭过的 order_id,全部按空操作处理;CLOSE 事件中的 symbol 字段被忽略——决定关闭哪一单的只有 order_id
- 输出为长度等于 N 的 list[int](按到达顺序每条事件一个值),使用精确比对;events 为空时返回空列表
样例
Case 1 · statement-example: 2 symbols rise then drain to 0
输入: [[["O-1","S-1","OPEN"],["O-2","S-2","OPEN"],["O-3","S-1","OPEN"],["O-1","S-1","CLOSE"],["O-3","S-1","CLOSE"],["O-2","S-2","CLOSE"]]]
期望: [1,2,2,2,1,0]
前两个 OPEN 让 S-1、S-2 各活跃 1 单 -> [1,2];O-3 OPEN 在 S-1 上仅增加 S-1 的计数(不引起 active 变化)-> 2;接下来三次 CLOSE 依次让 O-1(S-1 计数仍 >0)、O-3(S-1 归零)、O-2(S-2 归零)下线 -> [2,1,0]。
Case 2 · visible: empty events returns empty list
输入: [[]]
期望: []
无事件,输出空列表。
Case 3 · visible: single OPEN puts active at 1
输入: [[["O-1","S-1","OPEN"]]]
期望: [1]
单条 OPEN 后 S-1 活跃,输出 [1]。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: 2 symbols rise then drain to 0
输入: [[["O-1","S-1","OPEN"],["O-2","S-2","OPEN"],["O-3","S-1","OPEN"],["O-1","S-1","CLOSE"],["O-3","S-1","CLOSE"],["O-2","S-2","CLOSE"]]]
期望: [1,2,2,2,1,0]
前两个 OPEN 让 S-1、S-2 各活跃 1 单 -> [1,2];O-3 OPEN 在 S-1 上仅增加 S-1 的计数(不引起 active 变化)-> 2;接下来三次 CLOSE 依次让 O-1(S-1 计数仍 >0)、O-3(S-1 归零)、O-2(S-2 归零)下线 -> [2,1,0]。
Case 2 · visible: empty events returns empty list
输入: [[]]
期望: []
无事件,输出空列表。
Case 3 · visible: single OPEN puts active at 1
输入: [[["O-1","S-1","OPEN"]]]
期望: [1]
单条 OPEN 后 S-1 活跃,输出 [1]。