2013-12-19 4 views
2

У меня есть несколько имен файлов, которые я пытаюсь сравнить. Вот некоторые примеры:difflib с более чем двумя именами файлов

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

Что мне нужно сделать, это извлечь «FilePrefix» от каждого имени файла, который изменяется в зависимости от каталога. У меня есть несколько папок, содержащих много jpg. В каждой папке каждый jpg имеет FilePrefix, общий с каждым другим jpg в этом каталоге. Мне нужна переменная часть имени файла 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

Не то, чтобы это «путало место десяти и цифр», это то, что в первом матче место десяти не отличается, поэтому оно считается частью совпадающего префикса.

Для вашего варианта использования кажется довольно простым решением этой двусмысленности: просто сопоставьте все соседние пары и возьмите минимум. Как это:

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. Это не повлияет на код как есть, но если вы добавите некоторую отладку print, она сломается.

Теперь pairs список всех соседних пар имен, созданный zipping вместе names и names[1:]. (Если это не ясно, print(zip(names, names[1:]). Если вы используете Python 3.x, вам нужно print(list(zip(names, names[1:])) вместо этого, потому что zip возвращает ленивый итератор вместо печати списка.)

Теперь мы просто хотим вызовите prefix на каждую из пар и возьмите наименьшее значение, которое мы вернем. Это то, для чего min. (Я передаю его generator expression, который может быть хитрым понятие в первом, но если вы просто думать о нем, как list comprehension, что не строит список, это довольно просто.)

Вы могли бы, очевидно, компактна этот на две или три линии в то же время оставляя его читаемым:

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', F'` 'FilePrefix10.j , etc. down to'. Поэтому мы просто перебираем их, проверяя, является ли каждый из них также префиксом всех других имен и возвращает первый из них.


И вы можете сделать это даже быстрее, думая посимвольно вместо префикса префикс:

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

@ mh00h: Кого вы пытаетесь понять первым? И знаете ли вы, как работают «карта», «постижения», генераторы и «все»? – abarnert

+0

Теперь я перевариваю все это. У вас есть много документации (я предпочел бы сделать это, чем сначала, чем сообщение здесь); медведь со мной, пока я перевариваю все это! Удивительный ответ. Многому научиться здесь. – mh00h

+1

@ mh00h: Хорошо, круто. Я попытаюсь добавить некоторые объяснения к ответу - какой-то будущий искатель, приходящий на поиски той же проблемы, может быть не так старателен, как вы. Если вы найдете что-то непонятное, дайте мне знать, чтобы мы могли его улучшить. (Кроме того, если какой-либо из них на самом деле не работает, сообщите мне об этом, я несколько раз редактировал их после первоначальной записи и тестирования их ...) – abarnert

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» решения, но я хотел, чтобы алгоритм быть очевидным для любого уровня программиста.

+0

@abarnert Ваше решение по крайней мере в 3 раза быстрее моего. Спасибо за отличный и эффективный пример кода. Очень элегантно! – MrWonderful

Смежные вопросы