Python - diferença entre duas strings

86

Eu gostaria de armazenar muitas palavras em uma lista. Muitas dessas palavras são muito semelhantes. Por exemplo, eu tenho palavra afrykanerskojęzycznye muitos de palavras como afrykanerskojęzycznym, afrykanerskojęzyczni, nieafrykanerskojęzyczni. Qual é a solução eficaz (rápida e com tamanho de diff pequeno) para encontrar a diferença entre duas strings e restaurar a segunda string da primeira e diff?

user2626682
fonte
1
O que você quer dizer com "restaurar a segunda string da primeira e diff"?
jrd1
2
Acredito que ele queira dizer "Faça a segunda corda igual à primeira".
Elias Benevedes
1
@EliasBenevedes, exatamente :).
user2626682
1
Você está procurando por algo parecido difflib? Em caso afirmativo, consulte, por exemplo, stackoverflow.com/questions/774316/…
torek

Respostas:

109

Você pode usar o ndiff no módulo difflib para fazer isso. Ele contém todas as informações necessárias para converter uma string em outra.

Um exemplo simples:

import difflib

cases=[('afrykanerskojęzyczny', 'afrykanerskojęzycznym'),
       ('afrykanerskojęzyczni', 'nieafrykanerskojęzyczni'),
       ('afrykanerskojęzycznym', 'afrykanerskojęzyczny'),
       ('nieafrykanerskojęzyczni', 'afrykanerskojęzyczni'),
       ('nieafrynerskojęzyczni', 'afrykanerskojzyczni'),
       ('abcdefg','xac')] 

for a,b in cases:     
    print('{} => {}'.format(a,b))  
    for i,s in enumerate(difflib.ndiff(a, b)):
        if s[0]==' ': continue
        elif s[0]=='-':
            print(u'Delete "{}" from position {}'.format(s[-1],i))
        elif s[0]=='+':
            print(u'Add "{}" to position {}'.format(s[-1],i))    
    print()      

estampas:

afrykanerskojęzyczny => afrykanerskojęzycznym
Add "m" to position 20

afrykanerskojęzyczni => nieafrykanerskojęzyczni
Add "n" to position 0
Add "i" to position 1
Add "e" to position 2

afrykanerskojęzycznym => afrykanerskojęzyczny
Delete "m" from position 20

nieafrykanerskojęzyczni => afrykanerskojęzyczni
Delete "n" from position 0
Delete "i" from position 1
Delete "e" from position 2

nieafrynerskojęzyczni => afrykanerskojzyczni
Delete "n" from position 0
Delete "i" from position 1
Delete "e" from position 2
Add "k" to position 7
Add "a" to position 8
Delete "ę" from position 16

abcdefg => xac
Add "x" to position 0
Delete "b" from position 2
Delete "d" from position 4
Delete "e" from position 5
Delete "f" from position 6
Delete "g" from position 7
dawg
fonte
14
+1 Python tem assim muitos módulos úteis. Parece que aprendo sobre um novo a cada dia.
arshajii
1
Isso é superar a diferença manualmente; restaurar a diferença entre as duas strings, é claro, é muito mais fácil com difflib.restore
dawg
Obrigado! Mas não tenho certeza se isso é eficiente em termos de memória. list (difflib.ndiff ("afrykanerskojęzyczny", "nieafrykanerskojęzyczny")) ['+ n', '+ i', '+ e', 'a', 'f', 'r', 'y', 'k' , 'a', 'n', 'e', ​​'r', 's', 'k', 'o', 'j', 'ê', 'z', 'y', 'c', ' z ',' n ',' y ']
user2626682
ndiffé um gerador, portanto, é bastante eficiente em termos de memória. Você está chamando listisso, o que transforma as comparações de caracteres geradas individualmente em uma lista completa deles. Você só teria alguns na memória de cada vez se não os chamasse list.
dawg
1
Funciona em Python 2 também (para mim), sugiro fazer uma pergunta com a fonte e saída específicas. Não consigo depurar nos comentários ...
dawg
24

Eu gosto da resposta ndiff, mas se você quiser cuspir tudo em uma lista apenas das mudanças, você pode fazer algo como:

import difflib

case_a = 'afrykbnerskojęzyczny'
case_b = 'afrykanerskojęzycznym'

output_list = [li for li in difflib.ndiff(case_a, case_b) if li[0] != ' ']
Eric
fonte
3
Isso é exatamente o que eu estava procurando no Google. Uma observação rápida, @Eric, suas variáveis ​​não correspondem como mostrado hoje, 20180905. Tanto 1) mude a última linha para output_list = [li for li in list(difflib.ndiff(case_a,case_b)) if li[0] != ' ']ou 2) mude os nomes das variáveis ​​de string como case_a -> ae case_b -> b. Felicidades!
bballdave025
3
Também pode ser útil mostrar a saída do seu comando >>> output_list:; # resultado #['- b', '+ a', '+ m']
bballdave025
2
if not li.startswith(' ')é o equivalente de if li[0] != ' 'Alguns podem achá-lo mais legível. Ou mesmoif item.startswith(('-', '+', ))
dmmfll
@DMfll Downvote. As listas não têm a startswith()partir de python3.7.4
Nathan
3

Você pode olhar para o módulo regex (a seção fuzzy). Não sei se você pode obter as diferenças reais, mas pelo menos você pode especificar o número permitido de diferentes tipos de alterações, como inserir, excluir e substituições:

import regex
sequence = 'afrykanerskojezyczny'
queries = [ 'afrykanerskojezycznym', 'afrykanerskojezyczni', 
            'nieafrykanerskojezyczni' ]
for q in queries:
    m = regex.search(r'(%s){e<=2}'%q, sequence)
    print 'match' if m else 'nomatch'
perreal
fonte
3

O que você está pedindo é uma forma especializada de compressão. O xdelta3 foi projetado para esse tipo específico de compactação e há uma ligação python para ele, mas você provavelmente conseguiria usar o zlib diretamente. Você gostaria de usar zlib.compressobje zlib.decompressobjcom o zdictparâmetro definido para sua "palavra base", por exemplo afrykanerskojęzyczny.

As advertências são zdictsuportadas apenas no python 3.3 e superior, e é mais fácil codificar se você tiver a mesma "palavra base" para todos os seus diffs, que podem ou não ser o que você deseja.

Craig Silverstein
fonte
-2

A resposta ao meu comentário acima sobre a questão original me faz pensar que isso é tudo que ele quer:

loopnum = 0
word = 'afrykanerskojęzyczny'
wordlist = ['afrykanerskojęzycznym','afrykanerskojęzyczni','nieafrykanerskojęzyczni']
for i in wordlist:
    wordlist[loopnum] = word
    loopnum += 1

Isso fará o seguinte:

Para cada valor na lista de palavras, defina esse valor da lista de palavras como o código original.

Tudo o que você precisa fazer é colocar este trecho de código onde deseja alterar a lista de palavras, certificando-se de armazenar as palavras que deseja alterar na lista de palavras e de que a palavra original está correta.

Espero que isto ajude!

Elias Benevedes
fonte
Obrigado, mas na verdade eu gostaria de armazenar palavras como 'nieafrykanerskojęzyczni' de maneira eficiente em termos de memória, usando semelhança com 'afrykanerskojęzyczny'.
user2626682