
本文介绍一种贪心算法,用于将多个带格式标记的文本片段智能合并为结构最优的html代码,在保证语义正确的前提下最小化标签数量,并自动实现“长格式外裹、短格式内嵌”的嵌套策略。
本文介绍一种贪心算法,用于将多个带格式标记的文本片段智能合并为结构最优的html代码,在保证语义正确的前提下最小化标签数量,并自动实现“长格式外裹、短格式内嵌”的嵌套策略。
要解决多段带格式文本(如 [bold, italic])的 HTML 合并问题,核心挑战在于:避免冗余嵌套、复用共用格式区间、按作用域长度决定 DOM 层级顺序。一个直观但低效的做法是为每段文本独立包裹全部格式标签(如 <b><i>text1</i></b> + <b>text2</b>),这会产生大量重复开闭标签;而理想输出应像 <b><i>text1</i>text2</b>text3——即把所有连续处于 bold 状态的文本合并到同一 <b></b> 标签内,再在其中对子区间进一步应用更细粒度格式(如 italic)。
✅ 核心思想:贪心驱动的“格式栈”管理
我们将整个过程建模为一次从左到右的扫描,维护一个当前生效的格式栈(stack) 和一个各格式作用域长度映射(scope length map)。关键洞察是:
- 每种格式(如
"bold"、"italic")在序列中覆盖的连续文本段数量决定了其应处的 DOM 深度:覆盖越长 → 越靠外层; - 格式开启/关闭事件应按「作用域长度降序」处理:先开长作用域(外层),再开短作用域(内层);关闭时则逆序(先关内层,再关外层)。
? 算法步骤(伪代码)
def format_to_html(texts: List[Tuple[str, List[str]]]) -> str:
# Step 1: 预计算每个格式的作用域长度(覆盖多少连续 text[i])
formats = set(f for _, fs in texts for f in fs)
scope_len = {f: 0 for f in formats}
for f in formats:
# 找最长连续前缀,其中每个 text[i] 都包含 f
length = 0
for i in range(len(texts)):
if f in texts[i][1]:
length += 1
else:
break
scope_len[f] = length
# Step 2: 初始化状态
html_parts = []
stack = [] # 当前打开的格式列表,按嵌套顺序(外→内)
i = 0
while i < len(texts):
text, curr_formats = texts[i]
# (a) 关闭不再需要的格式:从栈顶开始,若某格式不在 curr_formats 中,则关闭
while stack and stack[-1] not in curr_formats:
f = stack.pop()
html_parts.append(f"</{tag_name(f)}>")
# (b) 开启新格式:仅开启 curr_formats 中、且尚未在 stack 中的格式,
# 按 scope_len[f] 降序排列(长作用域优先入栈 → 外层)
to_open = [f for f in curr_formats if f not in stack]
to_open.sort(key=lambda f: scope_len[f], reverse=True)
for f in to_open:
stack.append(f)
html_parts.append(f"<{tag_name(f)}>")
# (c) 添加当前文本内容
html_parts.append(text)
i += 1
# (d) 关闭剩余所有格式(从内到外)
while stack:
f = stack.pop()
html_parts.append(f"</{tag_name(f)}>")
return "".join(html_parts)
def tag_name(fmt: str) -> str:
return {"bold": "b", "italic": "i", "underline": "u"}.get(fmt, fmt)? 示例验证
输入:
生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur
texts = [
("text1", ["bold", "italic"]),
("text2", ["bold"]),
("text3", []),
]-
scope_len["bold"] = 2(覆盖 text1、text2) -
scope_len["italic"] = 1(仅 text1)
→ 排序后开标签顺序:["bold", "italic"]
→ 输出:<b><i>text1</i>text2</b>text3✅
再试:
立即学习“前端免费学习笔记(深入)”;
[("text1",["bold","italic"]), ("text2",["italic"]), ("text3",[])]-
scope_len["italic"] = 2,scope_len["bold"] = 1
→ 开标签顺序:["italic", "bold"]
→ 输出:<i><b>text1</b>text2</i>text3✅
⚠️ 注意事项与增强建议
-
格式冲突处理:本算法假设格式间无逻辑冲突(如
["strikethrough", "underline"]可共存)。若需互斥(如"dark-mode"vs"light-mode"),应在预处理阶段归一化。 -
性能优化:
scope_len可在 O(n×m) 预处理完成(n=文本数,m=格式种类),整体时间复杂度为 O(n×m log m),实际中 m 极小,可视为线性。 -
扩展性:支持任意自定义格式名和对应 HTML 标签,只需完善
tag_name()映射。 -
空格与换行:生产环境建议在
html_parts.append(text)前做 HTML 实体转义(如html.escape(text))。
该算法简洁、可证明最优(贪心选择性质成立),且天然支持“最长格式外裹”这一设计目标,是富文本序列化为语义化 HTML 的工业级实践方案。


















