You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

LaTeX公式相似部分检测后无法高亮的问题求助

LaTeX公式相似部分检测后无法高亮的问题求助

我完全懂你现在的痛点!明明已经通过SymPy找出了两个公式里数学上等价的相似部分,但因为它们的LaTeX写法天差地别(比如展开式和因式分解式),直接用字符串替换根本没法匹配,导致高亮功能完全失效,这确实太让人挠头了。

问题根源

SymPy的equals()方法能精准识别数学等价的表达式,但当把这些表达式转回LaTeX时,等价的数学结构可能会被渲染成完全不同的字符串(比如x^2-12x-325(x-25)(x+13)),直接字符串替换自然找不到匹配项。

解决方案:从SymPy表达式层面做替换

与其在最终的LaTeX字符串上碰运气,不如在SymPy表达式的抽象语法树层面操作,给匹配到的子表达式直接加上高亮标记,再转成LaTeX。这样就能彻底避开LaTeX写法差异的问题。

步骤1:自定义高亮函数和LaTeX打印机

我们先定义一个SymPy自定义函数用来标记需要高亮的子表达式,再给它定制LaTeX打印规则,让它输出\colorbox格式:

import sympy
from sympy.parsing.latex import parse_latex
from sympy.printing.latex import LatexPrinter

# 自定义高亮函数,用于包裹需要高亮的子表达式
class Highlight(sympy.Function):
    @classmethod
    def eval(cls, expr):
        if isinstance(expr, sympy.Expr):
            return cls(expr)

# 自定义LaTeX打印机,把Highlight函数转成带颜色框的LaTeX代码
class HighlightLatexPrinter(LatexPrinter):
    def _print_Highlight(self, expr):
        inner_expr_latex = self._print(expr.args[0])
        return f"\\colorbox{{#88E788}}{{${inner_expr_latex}$}}"

步骤2:编写子表达式替换函数

接下来写一个递归函数,遍历SymPy表达式的所有节点,把匹配到的等价子表达式替换成Highlight包裹的版本:

def replace_subexpr(expr, target_subexpr, wrapper_func):
    # 如果当前表达式和目标子表达式等价,就用包裹函数包装
    if expr.equals(target_subexpr):
        return wrapper_func(expr)
    # 如果是原子表达式(比如单个变量、数字),直接返回
    if expr.is_Atom:
        return expr
    # 递归处理所有子节点
    new_args = [replace_subexpr(arg, target_subexpr, wrapper_func) for arg in expr.args]
    return expr.func(*new_args)

# 辅助函数:扁平化get_same_parts返回的嵌套列表,提取有效匹配对
def flatten_matches(nested_matches):
    result = []
    def _flatten(item):
        if isinstance(item, list):
            if len(item) == 2 and all(isinstance(sub, sympy.Expr) for sub in item):
                result.append(item)
            else:
                for sub_item in item:
                    _flatten(sub_item)
    _flatten(nested_matches)
    return result

步骤3:修改主逻辑,完成高亮替换

把原来的字符串替换逻辑改成表达式层面的替换,再用自定义打印机输出LaTeX:

# 你的原始扁平化和表达式树函数保留不变
def flatten(nested_list) -> list:
    result = []
    for item in nested_list:
        if isinstance(item, list):
            result.extend(flatten(item))
        else:
            result.append(item)
    return result

def expression_tree(expression, depth=0, max_depth=1):
    if max_depth is not None and depth > max_depth or expression.is_Atom:
        return str(expression)

    tree_list = []
    for arg in expression.args:
        tree_list.append(expression_tree(arg, depth + 1, max_depth))

    return tree_list

def rec_check_expressions(expr, expressions):
    for i in expressions:
        if sympy.parse_expr(i).equals(expr):
            return [sympy.sympify(i), sympy.sympify(expr)]
    if type(expr) is str:
        return []
    return [rec_check_expressions(a, expressions) for a in flatten(expression_tree(expr))]

def get_same_parts(expr1, expr2, min_len):
    depth = 1
    all_subtrees1 = []
    _ = []
    while 1:
        subtrees1 = flatten(expression_tree(expr1, max_depth=depth))
        if subtrees1 == _:
            break

        all_subtrees1.append(subtrees1)
        all_subtrees1 = flatten(all_subtrees1)

        _ = subtrees1
        depth += 1

    all_subtrees1 = [a for a in set(all_subtrees1) if len(a) > min_len]

    return rec_check_expressions(expr2, all_subtrees1)

# 主逻辑开始
exp1_sym = parse_latex(r"\sqrt{(x^2-12x-325)}-\cos(12\pi-x^2)")
exp2_sym = parse_latex(r"|(x-25)(x+13)|+\sin(-x*x+6\pi+5\pi+\pi)")

# 获取相似部分并扁平化处理
parts = get_same_parts(exp1_sym, exp2_sym, 4)
flat_matches = flatten_matches(parts)

# 给两个表达式中的匹配部分加上高亮标记
for match1, match2 in flat_matches:
    exp1_sym = replace_subexpr(exp1_sym, match1, Highlight)
    exp2_sym = replace_subexpr(exp2_sym, match2, Highlight)

# 用自定义打印机生成带高亮的LaTeX代码
printer = HighlightLatexPrinter()
exp1_latex = printer.doprint(exp1_sym).replace(" ", "")
exp2_latex = printer.doprint(exp2_sym).replace(" ", "")

print(exp1_latex)
print(exp2_latex)

额外提示

  • 这个方法的核心是基于数学结构而非字符串匹配,不管LaTeX写法怎么变,只要SymPy能识别等价性,就能精准高亮。
  • 如果你的get_same_parts函数返回的嵌套结构更复杂,可能需要调整flatten_matches函数来适配,但核心思路不变。

备注:内容来源于stack exchange,提问作者Дмитрий Дашкин

火山引擎 最新活动