在Python中生成两个文件的"模糊"差异,浮点数近似比较

7 投票
1 回答
2384 浏览
提问于 2025-04-16 00:22

我遇到了一个比较两个文件的问题。简单来说,我想在两个文件之间做一个类似于UNIX的diff,比如:

$ diff -u left-file right-file

但是我的这两个文件里包含浮点数,而且因为这些文件是在不同的计算机架构上生成的(但计算的内容是一样的),所以浮点值并不完全相同(它们可能相差,比如1e-10)。我想通过'比较'这两个文件,找出我认为重要的差异(比如差异大于1e-4);而使用UNIX命令diff时,我几乎所有包含浮点值的行都被标记为不同!这就是我的问题:我怎么才能得到一个像'diff -u'那样的结果,但在比较浮点数时限制少一些呢?

我想我可以写一个Python脚本来解决这个问题,发现了一个叫做difflib的模块,它提供了类似diff的比较功能。但是我找到的文档只解释了怎么直接使用它(通过一个方法),并且讲解了内部对象,但我找不到任何关于如何定制difflib对象以满足我的需求的内容(比如只重写比较方法之类的)……我想一个解决方案可能是获取统一的差异,然后'手动'解析它,去掉我的'错误'差异,但这样不太优雅;我更希望能用现有的框架。

所以,有没有人知道怎么定制这个库,以便我能实现我想要的功能?或者至少给我指个方向……如果在Python中做不到,也许用shell脚本可以完成这个工作?

任何帮助都将非常感谢!提前谢谢大家的回答!

1 个回答

4

在你的情况下,我们要专门处理一个一般情况:在把内容传给difflib之前,我们需要先找出并单独处理那些包含浮点数的行。这里有一个基本的方法,如果你想生成差异、上下文行等等,可以在这个基础上进行扩展。需要注意的是,比较浮点数时,直接把它们当作浮点数来比较会更简单,而不是把它们当作字符串(当然,你也可以写一个逐列比较的程序,忽略1-e4之后的字符)。

import re

float_pat = re.compile('([+-]?\d*\.\d*)')
def fuzzydiffer(line1,line2):
    """Perform fuzzy-diff on floats, else normal diff."""
    floats1 = float_pat.findall(line1)
    if not floats1:
        pass # run your usual diff() 
    else:
        floats2 = float_pat.findall(line2)
        for (f1,f2) in zip(floats1,floats2):
            (col1,col2) = line1.index(f1),line2.index(f2)
            if not fuzzy_float_cmp(f1,f2):
                print "Lines mismatch at col %d", col1, line1, line2
            continue
    # or use a list comprehension like all(fuzzy_float_cmp(f1,f2) for f1,f2 in zip(float_pat.findall(line1),float_pat.findall(line2)))
    #return match

def fuzzy_float_cmp(f1,f2,epsilon=1e-4):
    """Fuzzy-compare two strings representing floats."""
    float1,float2 = float(f1),float(f2)
    return (abs(float1-float2) < epsilon)

一些测试:

fuzzydiffer('text: 558.113509766 +23477547.6407 -0.867086648057 0.009291785451', 
'text: 558.11351 +23477547.6406 -0.86708665 0.009292000001')

另外,作为额外的内容,这里有一个版本,可以突出显示列之间的差异:

import re

float_pat = re.compile('([+-]?\d*\.\d*)')
def fuzzydiffer(line1,line2):
    """Perform fuzzy-diff on floats, else normal diff."""
    floats1 = float_pat.findall(line1)
    if not floats1:
        pass # run your usual diff() 
    else:
        match = True
        coldiffs1 = ' '*len(line1)
        coldiffs2 = ' '*len(line2)
        floats2 = float_pat.findall(line2)
        for (f1,f2) in zip(floats1,floats2):
            (col1s,col2s) = line1.index(f1),line2.index(f2)
            col1e = col1s + len(f1)
            col2e = col2s + len(f2)
            if not fuzzy_float_cmp(f1,f2):
                match = False
                #print 'Lines mismatch:'
                coldiffs1 = coldiffs1[:col1s] + ('v'*len(f1)) + coldiffs1[col1e:]
                coldiffs2 = coldiffs2[:col2s] + ('^'*len(f2)) + coldiffs2[col2e:]
            #continue # if you only need to highlight first mismatch
        if not match:
            print 'Lines mismatch:'
            print '  ', coldiffs1
            print '< ', line1
            print '> ', line2
            print '  ', coldiffs2
        # or use a list comprehension like
        #    all()
        #return True

def fuzzy_float_cmp(f1,f2,epsilon=1e-4):
    """Fuzzy-compare two strings representing floats."""
    print "Comparing:", f1, f2
    float1,float2 = float(f1),float(f2)
    return (abs(float1-float2) < epsilon)

撰写回答