2013-12-19 1 views
2

나는 여러 파일 이름을 비교하려고합니다. 다음은 몇 가지 예입니다 : 내가 뭘해야 디렉토리에 따라 변경 각 파일 이름에서 "FilePrefix"를 추출두 개 이상의 파일 이름이있는 difflib

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

입니다. 나는 많은 jpg를 포함하는 몇몇 폴더가있다. 각 폴더 내에서 각 jpg에는 해당 디렉토리의 다른 모든 jpg와 공통된 FilePrefix가 있습니다. jpg 파일 이름의 가변 부분이 필요합니다. FilePrefix가 어떤 파일인지 미리 예측할 수 없습니다.

필자는 difflib (파이썬에서)을 사용하여 두 파일 이름을 비교하고 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가 일치하지 않습니다. 10과 숫자의 자리가 혼란스러워서 두 개 이상의 파일의 차이를 맞추기가 어렵습니다. 디렉토리 내의 모든 파일 중에서 최소 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로 전화를 할 수 있도록, 대신 두 개의 인수의 두 값의 단일 튜플을했다. 그리고 두 번째 호출 get_matching_blocksnamedtuple 대신 tuple을 반환 할 수있는 2.7 difflib에 성가신 버그가있어서 .size 대신 [2]을 사용했습니다. 이것은 코드에 그대로 영향을 미치지 않지만 디버그를 추가하면 print가 중단됩니다.

이제 pairszipping과 함께 및 names[1:]으로 생성 된 모든 인접 쌍 이름 목록입니다. (이, 명확하지 않으면 파이썬 3.x를을 사용하는 경우 zip가 인쇄 목록 대신 게으른 반복자를 반환하기 때문에 print(zip(names, names[1:])., 당신은 대신 print(list(zip(names, names[1:]))해야합니다.)

이제 우리가 원하는 각 쌍에 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) 일 필요가 있음을 의미하는 어디서나을 찾습니다. 결과. 게다가 가장 긴 접두사보다 긴 접미사가있을 수 있다는 것은 상상도 할 수 없으므로 잘못된 결과를 반환합니다.

그래서 수동으로하지 않는 이유는 무엇입니까?

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

@ 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

관련 문제