Создать «нечеткую» разницу двух файлов в Python с приблизительным сравнением чисел с плавающей запятой - PullRequest
7 голосов
/ 24 июня 2010

У меня есть проблема для сравнения двух файлов. По сути, я хочу сделать UNIX-подобный diff между двумя файлами, например:

$ diff -u left-file right-file

Однако мои два файла содержат числа с плавающей запятой; и поскольку эти файлы были сгенерированы на разных архитектурах (но вычисляющих одни и те же вещи), плавающие значения не совсем одинаковы (они могут отличаться, скажем, на 1e-10). Но то, что я ищу, «разбирая» файлы, - это найти то, что я считаю значительными отличиями (например, разница больше 1e-4); используя UNIX команду diff, я получаю почти все мои строки, содержащие плавающие значения, отличающиеся! Это моя проблема: как я могу получить результирующий diff, как обеспечивает 'diff -u', но с меньшими ограничениями относительно сравнения чисел?

Я подумал, что напишу скрипт Python для этого, и обнаружил модуль difflib, который обеспечивает сравнение по типу diff. Но документация, которую я нашел, объясняет, как использовать ее как есть (с помощью одного метода), и объясняет внутренние объекты, но я не могу найти ничего относительно того, как настроить объект difflib для удовлетворения моих потребностей (например, переписать только метод сравнения или такие) ... я думаю, что решение могло бы заключаться в том, чтобы извлечь объединенную разницу и проанализировать ее «вручную», чтобы удалить мои «ложные» различия, что не является элегантным; Я бы предпочел использовать уже существующие рамки.

Итак, кто-нибудь знает, как настроить эту библиотеку, чтобы я мог делать то, что я ищу? Или, по крайней мере, указать мне правильное направление ... Если не в Python, может быть, сценарий оболочки может работать?

Любая помощь будет принята с благодарностью! Заранее спасибо за ваши ответы!

1 Ответ

4 голосов
/ 03 июля 2011

В вашем случае мы специализируемся на общем случае : перед тем, как мы передадим вещи в 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)
...