并查集模板:find / union / count 从零推导

副标题 / 摘要 并查集不是从 find 和 union 这两个函数名开始背。它要解决的问题是:怎样判断两个点是否已经属于同一个集合,并在看到一条连接关系时把两个集合合并。 预计阅读时长:10~12 分钟 标签:Hot100、并查集、Union-Find、DSU、图 SEO 关键词:并查集, Union-Find, DSU, find, union, count, 路径压缩 元描述:用 Python 从零推导并查集模板,讲清 parent、find、union、count、路径压缩和连通分量计数。 A — Algorithm(从集合合并压力开始) 小任务:不断合并集合,并回答连通性 假设有 5 个点: 0, 1, 2, 3, 4 一开始,每个点都是一个独立集合: {0}, {1}, {2}, {3}, {4} 现在依次发生两次合并: union(0, 1) union(1, 2) 我们想回答三个问题: 0 和 2 是否在同一个集合? 3 和 4 是否在同一个集合? 当前一共有几个集合? 手工看答案是: {0, 1, 2}, {3}, {4} 所以: 0 和 2 在同一个集合 3 和 4 不在同一个集合 当前 count = 3 这就是并查集要解决的核心任务: ...

2026年6月30日 · 5 分钟 · map[name:Jeanphilo]

LeetCode 208:实现 Trie(前缀树)模板题解析

副标题 / 摘要 208 题的难点不在算法变化,而在把 Trie 模板按平台接口写稳:insert 建路径,search 查完整单词,startsWith 只查前缀路径。 预计阅读时长:8~10 分钟 标签:Hot100、Trie、前缀树、LeetCode 208 SEO 关键词:LeetCode 208, Implement Trie, Prefix Tree, startsWith 元描述:从接口要求出发实现 LeetCode 208,讲清 Trie 节点、children、is_end、insert/search/startsWith 的区别。 A — Algorithm(题目与算法) 先看最小操作压力 208 题最关键的操作序列是: Trie trie = new Trie() trie.insert("apple") trie.search("apple") -> true trie.search("app") -> false trie.startsWith("app") -> true trie.insert("app") trie.search("app") -> true 这个例子说明: app 可以是 apple 的前缀 但只有插入过 app 后,search("app") 才能返回 True 所以这题不是“路径存在就算命中”。 我们必须同时维护: 路径是否存在 这条路径是否刚好是完整单词 题目接口 设计一个 Trie,也叫前缀树,支持三个操作: ...

2026年6月24日 · 3 分钟 · map[name:Jeanphilo]

Trie 模板:从节点字段到插入查询 invariant

副标题 / 摘要 Trie 的重点不是背代码,而是理解“一个节点代表一个前缀”。只要这个模型稳定,插入、完整单词查询和前缀查询都会变成同一个循环。 预计阅读时长:8~10 分钟 标签:Hot100、Trie、前缀树、字典树 SEO 关键词:Trie, 前缀树, 字典树, children, is_end 元描述:用 Python 写一个最小 Trie 模板,讲清节点字段、children 走法、结束标记和循环 invariant。 A — Algorithm(从一个小任务开始) 小任务:同时回答完整单词和前缀 假设已经插入: app apple bat 现在要问: app 是不是完整单词? ap 是不是某个单词的前缀? apply 是否存在? 这个小任务暴露了两个缺口: 只用哈希集合,可以快速判断完整单词,但不能自然回答前缀问题 只看路径存在,又会把 app 和 apple 的前缀关系混成一件事 Trie 要解决的就是:让很多字符串共享公共前缀,同时还能区分“前缀存在”和“完整单词存在”。 从压力反推要支持什么 我们先不管任何题目接口,只定义一个自己的模板: insert(word):把一个单词插入 Trie search(word):判断完整单词是否存在 starts_with(prefix):判断是否存在以 prefix 开头的单词 最小结构图 插入 app 和 apple 后,结构可以想成: root └─ a └─ p └─ p [end] └─ l └─ e [end] 这里最重要的是: ...

2026年6月24日 · 3 分钟 · map[name:Jeanphilo]

Python Callable 类型标注:怎么描述一个 handler 函数?

Python Callable 类型标注:怎么描述一个 handler 函数? 副标题: Callable[[dict[str, Any]], Any] 不是一个新类,而是在类型层面说明: 这个参数是一个“能被调用的对象”,它接收什么参数,返回什么结果。 标签: Python / 类型标注 / Callable / Type Alias / Pipeline 适读人群: 正在写回调、handler、pipeline step、插件机制的 Python 开发者 阅读时间: 6 min 背景:为什么 handler 类型看起来这么长? 在 pipeline runner 里,经常会有这种 handler 表: handlers = { "prepare": prepare, "enrich": enrich, } 每个 handler 都接收同一个运行时上下文: def prepare(context: dict[str, Any]) -> dict[str, bool]: context["prepared"] = True return {"prepared": True} 如果直接给 handler 表写类型,会变成: from collections.abc import Callable, Mapping from typing import Any handlers: Mapping[str, Callable[[dict[str, Any]], Any]] 这一串不难,但读起来吵。它把“workflow name 到 workflow handler 的映射”写成了一段类型语法。 ...

2026年6月3日 · 3 分钟 · map[name:Jeanphilo]

Python 从 dict/list 原型到稳定接口:什么时候该整理类型?

Python 从 dict/list 原型到稳定接口:什么时候该整理类型? 副标题: 验证想法时先用普通变量和 dict/list 跑通行为;模型稳定后,再把它们整理成函数参数、dataclass、helper 和类型标注。 标签: Python / 原型验证 / 类型标注 / 接口设计 / Pipeline 适读人群: 正在从脚本式代码过渡到可维护模块的 Python 开发者 阅读时间: 7 min 背景:为什么一开始不用把类型设计得很完整? 写一个新功能时,经常会卡在这个问题上: 我应该一开始就写 dataclass、class、Mapping、Callable 吗? 还是先用普通 dict/list 把行为跑通? 答案不是固定的,但有一个很实用的判断: 模型还不确定时,先用 dict/list 验证行为; 模型稳定后,再把稳定的东西整理成接口。 这不是偷懒,而是在降低错误抽象的成本。 比如你想验证一个 graph pipeline runner,最早可以只写: graph_plans = { "parallel": { "prepare": (), "enrich": ("prepare",), "extract": ("prepare",), } } 这个变量一开始不是“最终架构”,只是为了回答一个问题: dependency map 能不能表达 prepare 之后 enrich 和 extract 同时 ready? 如果这个问题都没验证清楚,提前设计完整类层次反而会遮住真正的行为压力。 第一阶段:用普通变量验证模型 最早的脚本里,很多东西看起来像“常量”: ...

2026年6月3日 · 3 分钟 · map[name:Jeanphilo]

Python 类型标注里为什么用 Mapping、Sequence 这类抽象接口?

Python 类型标注里为什么用 Mapping、Sequence 这类抽象接口? 副标题: 当一个函数只读取配置,不修改调用方传入的字典时,用 Mapping 比直接写 dict 更能表达接口意图;同理,Iterable、Sequence 和 Callable 也在表达“我需要什么能力”。 标签: Python / 类型标注 / Mapping / Sequence / Callable / 接口设计 适读人群: 正在写 Python 配置对象、runner、pipeline、SDK 参数的开发者 阅读时间: 7 min 背景:为什么不是所有字典参数都写成 dict? 写 Python 配置型代码时,经常会有这种参数: graph_plans = { "parallel": { "prepare": (), "enrich": ("prepare",), "extract": ("prepare",), } } 如果一个 runner 只是读取它: runner = GraphPipelineRunner(graph_plans=graph_plans, handlers=handlers) 那么类型标注可以写成: from collections.abc import Mapping def __init__( self, *, graph_plans: Mapping[str, Mapping[str, tuple[str, ...]]], handlers: Mapping[str, WorkflowHandler], ) -> None: ... 刚开始看会觉得复杂:为什么不直接写 dict? ...

2026年6月3日 · 3 分钟 · map[name:Jeanphilo]

函数命名不是取名字:它是在描述系统边界

函数命名不是取名字:它是在描述系统边界 副标题: 好的函数名不只是“好听”。它应该让读者看出:这个函数在做读取、 解析、校验、筛选、创建,还是修改状态。 适读人群: 正在写 Python service、runner、pipeline、SDK 或业务模块的开发者 阅读时间: 8 min 背景:为什么 get_dependencies 不是总比 resolve_graph 直观? 看一个小型 graph pipeline runner: def trigger(self, mode, context): dependencies = self._resolve_graph(mode) self._validate_graph(dependencies) record = self._create_graph_record(mode, dependencies, context) while True: ready_nodes = self._ready_nodes(record) if not ready_nodes: break for node_name in ready_nodes: ... 刚开始可能会问: 为什么不叫 get_dependencies? 为什么不叫 get_handler? 为什么 ready_nodes 没有动词? 为什么 skip_dependents 一看就像会改状态? 这些不是风格洁癖。函数命名背后其实是在表达系统边界: mode 是外部语言,graph 是内部执行结构。 handler name 是配置语言,handler 是可调用对象。 ready node 是运行时状态,不是普通列表。 skip dependents 是副作用,不是查询。 这篇文章用一个 pipeline runner 作为例子,讲清楚函数命名应该如何暴露设计意图。 ...

2026年6月3日 · 4 分钟 · map[name:Jeanphilo]

从顺序 Pipeline Runner 演进到 Graph Runner

本文是最小 Pipeline Runner 系列的 v1 后续章节:从 v0 的顺序 workflow list 出发,推导 dependency graph、ready wave、graph validation、node record 和 failed dependency blocking。

2026年6月3日 · 5 分钟 · map[name:Jeanphilo]

为什么开会,以及怎么开一个有效会议

副标题 / 摘要 会议不是为了把人聚在一起聊天,而是为了让团队形成决策、拆清任务、暴露风险、关闭问题或明确不做。本文给出一套判断会议是否必要、如何组织会议、如何记录会议结果的实用框架。 目标读者 需要组织项目会议、需求会议、技术方案会议的人 经常参加会议但觉得产出不明确的开发者 负责团队协作、项目推进、风险管理的技术负责人 想把会议从“状态广播”改成“问题解决工具”的团队 背景 / 动机 很多会议的问题不是时间太长,而是目的不清。 常见情况包括: 开会前没人知道这次要解决什么 会中不断发散,新问题挤掉原本议题 讨论很多,但没有明确决策 行动项只写“继续跟进”,没有负责人和时间 下次会议又重新讨论同一个问题 会议是一种高成本同步沟通方式。只要多人同时停下手头工作,它就必须产出足够清晰的结果,否则会议成本会被浪费。 核心概念 会议是问题解决工具 一次会议应该至少解决一种问题: 对齐目标:确认当前阶段要完成什么,不做什么 做出决策:在多个方案之间做选择 暴露问题:把卡点、风险、依赖和不确定性讲清楚 分配行动:明确谁负责什么,什么时候完成 检查进度:确认上次行动项是否完成,以及是否需要调整 管理范围:判断新增需求是否进入当前阶段 形成记录:沉淀结论、决策、待办、风险和下次检查点 如果会议不解决这些问题,它很可能只是聊天或状态广播。 会议产出比会议过程更重要 有效会议结束后,至少应该产出下面之一: 一个明确决策 一个被拆清楚的任务 一个被确认的风险 一个被关闭的问题 一个新的行动项 一个明确的“不做” 如果会议结束后没有这些产出,就应该反思这次会议是否有必要。 不是所有沟通都需要会议 会议适合处理高互动、高不确定、高依赖的问题。 不适合会议的事情包括: 单向通知 纯状态同步 没有决策人的讨论 没有输入材料的临时闲聊 可以用文档或消息异步解决的问题 越是多人会议,越要提前确认它不能被异步沟通替代。 实践指南 / 步骤 1. 会前先问:这次会议要解决什么 开会前先写一句话: 这次会议的目的是什么? 例如: 确认登录模块是否采用方案 A,并拆出本周可交付任务。 这个目的比“讨论登录模块”更好,因为它明确了会议产出:选方案、拆任务。 2. 明确本轮做什么和不做什么 会议一开始要对齐范围: 当前阶段到底要完成什么 什么是本轮不做的 优先级是否变化 成功标准是什么 很多会议低效,是因为大家在讨论不同层级的问题。有人在讨论本周交付,有人在讨论长期架构,有人在讨论资源分配,最后没有一个问题被解决。 3. 只保留最多 3 个核心议题 一次会议不要试图解决所有问题。 ...

2026年5月31日 · 2 分钟 · map[name:Jeanphilo]

从普通函数调用推导一个最小 Pipeline Runner

本文保留 nano-pipeline-runner v0 的完整推导链条:先证明普通函数调用什么时候足够,再通过工程接入、运行观察、失败记录、重复执行策略和共享 context 的压力,构建一个最小顺序 Pipeline Runner。

2026年5月29日 · 11 分钟 · map[name:Jeanphilo]