我想要比较几个文件名。以下是一些例子:
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?
谢谢。
答案 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
是由zip
ping names
和names[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',但我希望算法对于任何级别的程序员都是显而易见的。