SWE / SDE-2 · Phone Coding / AI Onsite Coding

Rippling Task Manager:任务去重、筛选和多关键字排序

依据多篇面经和公开题库预览,拆解 description+due date 去重、open/unassigned 筛选与排序;明确未公开的优先级和输出格式。

题目重点:依据多篇面经和公开题库预览,拆解 description+due date 去重、open/unassigned 筛选与排序;明确未公开的优先级和输出格式。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练 Task 含 task_id、description、due_at、created_at、high_priority、status、assignee;时间以可比较整数表示。真实题的字段与时间格式未公开。
  • 训练口径先对全部任务按 (description,due_at) 去重,保留 created_at 最早、ID 最小的一条,再筛 status='open' 且 assignee=None;按 due_at 升序、high_priority 在前、created_at 升序、ID 升序。公开预览没确认这些先后和 tie rule。
  • 训练函数返回 Task 列表;原题要求某种指定打印格式,但可见预览截断了该格式,故不声称实现了原题完整输出。

解题路线

  1. 先把题拆成四个独立契约:输入解析、去重等价关系及保留哪条、筛选谓词、稳定排序与输出。面试中优先问去重与筛选顺序:若同键一条 closed、一条 open,先去重和先筛选会得到不同结果。
  2. 用哈希表以 (description,due_at) 分组,每次比较 (created_at,task_id) 选代表。这里不对 description 做大小写、空白标准化,除非原题明确要求;否则可能把本来不同的任务合并。
  3. 筛选代表任务的状态和 assignee 后,以一个元组 key 排序;显式加 task_id 打破所有业务字段相同的平局,使输入顺序变化仍可复现。
  4. 真实题若要求流式处理,去重可在线维护代表但最终按全局排序仍至少需要保存候选或外排;若重复项更新 status,先明确是事件覆盖、保留最早还是保留最新,不要凭直觉决定。

手动走一遍

训练例:同一 description='fix' 且 due=10 的 a(created=1, high) 与 b(created=2, low) 合并保留 a;另一条 c(due=5) 排在前面,返回 c,a。若 a 是 closed、b 是 open,当前‘先去重’口径会把该键整体筛掉;若题目要先筛,这个结果应改变。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

"""Training contract for the publicly visible Task Manager preview.

Exact task schema, sorting precedence, duplicate winner, and print format are
not public. These choices are documented in the accompanying article.
"""

from dataclasses import dataclass


@dataclass(frozen=True)
class Task:
    task_id: str
    description: str
    due_at: int
    created_at: int
    high_priority: bool
    status: str
    assignee: str | None


def open_unassigned_tasks(tasks: list[Task]) -> list[Task]:
    # Winner: earliest created task, then lexicographically smallest id.
    winner: dict[tuple[str, int], Task] = {}
    for task in tasks:
        key = (task.description, task.due_at)
        old = winner.get(key)
        if old is None or (task.created_at, task.task_id) < (old.created_at, old.task_id):
            winner[key] = task

    eligible = (
        task for task in winner.values()
        if task.status == "open" and task.assignee is None
    )
    return sorted(
        eligible,
        key=lambda task: (task.due_at, not task.high_priority, task.created_at, task.task_id),
    )


if __name__ == "__main__":
    sample = [
        Task("b", "fix", 10, 2, False, "open", None),
        Task("a", "fix", 10, 1, True, "open", None),
        Task("c", "ship", 5, 3, False, "open", None),
    ]
    assert [task.task_id for task in open_unassigned_tasks(sample)] == ["c", "a"]

复杂度

N 条任务,哈希分组平均 O(N) 时间和 O(U) 空间,U 是去重键数;K 条合格任务排序 O(K log K) 时间,结果空间 O(K)。若哈希碰撞退化或描述很长,键哈希与比较还受字符串长度影响。

易错点与追问

容易失分的边界

  • 只按 description 去重,误删不同截止日任务。
  • 先筛后重或先重后筛没有问清,却把其中一个当原题。
  • 用不稳定且未补 tie key 的排序,结果随输入顺序变化。
  • 把 due date 当文本做字典序比较,混用不同时区格式。
  • 自行发明会员内容中被截断的输出格式。

继续深挖

  • 同键任务状态/负责人不同,保留哪条?
  • description 是否 trim/casefold?
  • 日期相同、高优先级相同,creation age 越老越先还是越新越先?
  • 如果要分页和频繁更新,怎样建立索引且保持排序稳定?
✓已复制微信号!