difflib有两个以上的文件名

时间:2013-12-19 23:30:59

标签: python regex difflib

我想要比较几个文件名。以下是一些例子:

files = ['FilePrefix10.jpg', 'FilePrefix11.jpg', 'FilePrefix21.jpg', 'FilePrefixOoufhgonstdobgfohj#lwghkoph[]**^.jpg']

我需要做的是从每个文件名中提取“FilePrefix”,该文件名会根据目录而改变。我有几个包含许多jpg的文件夹。在每个文件夹中,每个jpg都有一个与该目录中每个其他jpg共同的FilePrefix。我需要jpg文件名的可变部分。我无法提前预测FilePrefix会是什么。

我有想法只使用difflib(在Python中)比较两个文件名,并以这种方式提取FilePrefix(以及随后的变量部分)。我遇到了以下问题:

>>>> comp1 = SequenceMatcher(None, files[0], files[1])
>>>> comp1.get_matching_blocks()
[Match(a=0, b=0, size=11), Match(a=12, b=12, size=4), Match(a=16, b=16, size=0)]

>>>> comp1 = SequenceMatcher(None, files[1], files[2])
>>>> comp1.get_matching_blocks()
[Match(a=0, b=0, size=10), Match(a=11, b=11, size=5), Match(a=16, b=16, size=0)]

如您所见,第一个size不匹配。它使十位和数位的位置混淆,使我难以匹配两个以上文件之间的差异。是否有正确的方法可以在目录中的所有文件中找到最小size?或者,有没有更好的方法来提取FilePrefix?

谢谢。

2 个答案:

答案 0 :(得分:2)

并不是说“混淆了十位和数位的位置”,而是在第一场比赛中十位的位置没有区别,所以它被认为是匹配前缀的一部分。

对于您的用例,似乎有一个非常简单的解决方案:只需匹配所有相邻的对,并采取最小值。像这样:

def prefix(x, y):
    comp = SequenceMatcher(None, x, y)
    matches = comp.get_matching_blocks()
    prefix_match = matches[0]
    prefix_size = prefix_match[2]
    return prefix_size

pairs = zip(files, files[1:])
matches = (prefix(x, y) for x, y in pairs)
prefixlen = min(matches)
prefix = files[0][:prefixlen]

prefix函数非常简单,除了一件事:我使用了两个值的单个元组而不是两个参数,只是为了更容易使用map进行调用。我使用[2]代替.size,因为2.7 difflib中存在令人讨厌的错误,其中第二次调用get_matching_blocks可能会返回tuple而不是namedtuple {1}}。这不会影响代码,但如果添加一些调试print,它将会中断。

现在,pairs是由zipping namesnames[1:]共同创建的所有相邻名称对的列表。 (如果不清楚,print(zip(names, names[1:])。如果您使用的是Python 3.x,则需要print(list(zip(names, names[1:])),因为zip返回一个惰性迭代器而不是一个可打印的列表)。

现在我们只想在每对上调用prefix,并获取我们得到的最小值。这就是min的用途。 (我传给它generator expression,这可能是一个棘手的概念 - 但如果你只是把它想象为list comprehension而不构建列表,那就很简单了。)< / p>

你可以将它压缩成两三行,同时仍然让它可读:

prefixlen = min(SequenceMatcher(None, x, y).get_matching_blocks()[0][2] 
                for x, y in zip(files, files[1:]))
prefix = files[0][:prefixlen]

然而,值得考虑的是SequenceMatcher在这里可能有些过分。它正在寻找最长的匹配任何地方,而不仅仅是最长的前缀匹配,这意味着它在字符串长度上基本上是O(N ^ 3),当它只需要是O(NM)时M是结果的长度。另外,如果后缀长于最长的前缀,那么这并不是不可想象的,因此会返回错误的结果。

那么,为什么不手动呢?

def prefixes(name):
    while name:
        yield name
        name = name[:-1]

def maxprefix(names):
    first, names = names[0], names[1:]
    for prefix in prefixes(first):
        if all(name.startswith(prefix) for name in names):
            return prefix

prefixes(first)只会为您提供'FilePrefix10.jpg''FilePrefix10.jp','FilePrefix10.j , etc. down to'F'。所以我们只是循环遍历那些,检查每个是否也是所有其他名称的前缀,并返回第一个。


你可以通过逐字符思考而不是前缀前缀来更快地做到这一点:

def maxprefix(names):
    for i, letters in enumerate(zip(*names)):
        if len(set(letters)) > 1:
            return names[0][:i]

这里,我们只是检查所有名称中的第一个字符是否相同,然后所有名称中的第二个字符是否相同,依此类推。一旦我们找到失败的地方,前缀就是所有字符(来自任何名称)。

zip将名称列表重组为元组列表,其中第一个是每个名称的第一个字符,第二个是每个名称的第二个字符,依此类推。也就是[('F', 'F', 'F', 'F'), ('i', 'i', 'i', 'i'), …]

enumerate只是给我们索引以及值。因此,您得('F', 'F', 'F', 'F')而不是获得0, ('F, 'F', F', 'F')。我们需要最后一步的索引。

现在,要检查('F', 'F', 'F', 'F')是否完全相同,我只需将它们放在set中。如果它们完全相同,则该集合将只有一个元素 - {'F'},然后{'i'},等等。如果它们不存在,它将具有多个元素 - {'1', '2'} - 这就是我们知道我们已经超越前缀的方式。

答案 1 :(得分:1)

唯一可以确定的方法是检查所有文件名。所以只需遍历它们,然后检查保留的最大匹配字符串。

您可以尝试这样的事情:

files = ['FilePrefix10.jpg',
         'FilePrefix11.jpg',
         'FilePrefix21.jpg',
         'FilePrefixOoufhgonstdobgfohj#lwghkoph[]**^.jpg',
         'FileProtector354.jpg
         ]
prefix=files[0]
max = 0
for f in files:
    for c in range(0, len(prefix)):
        if prefix[:c] != f[:c]:
            prefix = f[:c-1]
            max = c - 1
print prefix, max

请原谅解决方案的'非Pythonicness',但我希望算法对于任何级别的程序员都是显而易见的。