2009-05-22 20 views
9

¿Pitón proporciona una manera elegante de verificar la "igualdad" de secuencias de diferentes tipos? El siguiente trabajo, pero parece bastante feo y detallado para el código Python:Manera elegante de comparar secuencias

def comp1(a, b): 
    if len(a) != len(b): 
     return False 
    for i, v in enumerate(a): 
     if v != b[i]: 
      return False 
    return True 

El siguiente es un poco más corto, pero también menos eficiente, ya que se crea una tercera secuencia:

def comp2(a, b): 
    for l, r in map(None, a, b): 
     if l != r: 
      return False 
    return True 

shoehorning una de esos ejemplos en una lista de comprensión tampoco es lo que estoy buscando.

Editar: Lo ideal es buscar una solución que no cree otra secuencia durante la comparación.

Respuesta

18

Convierta ambas secuencias en listas y use la comparación de la lista integrada. Debería ser suficiente, a menos que tus secuencias sean realmente grandes.

list(a) == list(b) 

Editar:

pruebas efectuadas por schickb muestra que el uso de tuplas es ligeramente más rápido:

tuple(a) == tuple(b) 
+1

Eso crea dos listas adicionales. Como las listas pueden ser largas, me gustaría evitar esto. – schickb

+2

@schickb: ¿Cuánto tiempo estamos hablando? Según el título y la primera frase de su publicación, es justo poner la elegancia como la máxima prioridad y la eficiencia como una bonificación. Para mí, la conversión a (nuevas) listas es, con mucho, la solución más elegante y más "eficiente en cuanto a programadores". –

+0

@John, sí, estoy de acuerdo después de algunas pruebas más. Esta solución es en realidad mucho más rápida que incluso el ciclo enumerate. Sin embargo, estoy seguro de que cambia con el tamaño de una secuencia. Marcaré esta como la respuesta a pesar de que tupla (a) == tupla (b) parece ser mejor. – schickb

11

se puede determinar la igualdad de las dos iterables (cuerdas, tuplas, listas, incluso personalizada secuencias) sin crear y almacenar listas duplicadas usando lo siguiente:

all(x == y for x, y in itertools.izip_longest(a, b)) 

No que si los dos iterables no tienen la misma longitud, el más corto se rellenará con None s. En otras palabras, considerará que [1, 2, None] es igual a (1, 2).

Editar: Como Kamil señala en los comentarios, izip_longest solo está disponible en Python 2.6. Sin embargo, the docs for the function también proporciona una implementación alternativa que debería funcionar hasta 2.3.

Editar 2: Después de probar en algunas máquinas diferentes, parece que esto es solo más rápido que list(a) == list(b) en ciertas circunstancias, que no puedo aislar. La mayoría de las veces, toma alrededor de siete veces más tiempo. Sin embargo, también encontré que tuple(a) == tuple(b) es consistentemente al menos dos veces más rápido que la versión list.

+0

Exactamente lo que iba a decir, excepto que olvidé el uso de todos(). La otra advertencia con esta solución es que requiere Python 2.6 o superior. –

+0

Agradable y corto, pero esa lista de comprensión crea una tercera secuencia. – schickb

+0

@Kamil - ¡Es cierto! Los 2.6 documentos para itertools ofrecen un 2.Una solución amigable para 5, según recuerdo, veré si no puedo desenterrar un enlace. –

1

Dado que pone la palabra "igualdad" entre comillas, supongo que le gustaría saber cómo son las listas iguales y cómo son diferentes. Salida difflib que tiene una clase SequenceMatcher:

sm = difflib.SequenceMatcher(None, a, b) 
    for opcode in sm.get_opcodes(): 
     print " (%s %d:%d %d:%d)" % opcode 

Va a volver a secuencias de descripciones de las diferencias. Es bastante simple convertir eso en diff -like salida.

0

Es probablemente no es tan eficiente, pero parece cobarde:

def cmpLists(a, b): 
    return len(a) == len(b) and (False not in [a[i] == b[i] for i in range(0,len(a)]) 

no sé el "todo" función que Ben mentioned, pero tal vez usted podría utilizar que en lugar de "falso no en"

+0

todo está documentado aquí: http://docs.python.org/3/library/functions.html#all –

2

Parece que la tupla (a) == tupla (b) es la mejor elección general. O tal vez una comparación tupla con un chequeo anterior de len si a menudo serán longitudes diferentes. Esto crea listas adicionales, pero con suerte no es un problema a excepción de listas realmente enormes. Aquí está mi comparación de las diversas alternativas sugeridas:

import timeit 

tests = (
''' 
a=b=[5]*100 
''', 

''' 
a=[5]*100 
b=[5]*3 
''', 

''' 
a=b=(5,)*100 
''', 

''' 
a=b="This on is a string" * 5 
''', 

''' 
import array 
a=b=array.array('B', "This on is a string" * 5) 
''' 
) 

common = '''import itertools 
def comp1(a, b): 
    if len(a) != len(b): 
     return False 
    for i, v in enumerate(a): 
     if v != b[i]: 
      return False 
    return True''' 

for i, setup in enumerate(tests): 
    t1 = timeit.Timer("comp1(a, b)", setup + common) 
    t2 = timeit.Timer("all(x == y for x, y in itertools.izip_longest(a, b))", setup + common) 
    t3 = timeit.Timer("all([x == y for x, y in itertools.izip_longest(a, b)])", setup + common) 
    t4 = timeit.Timer("list(a) == list(b)", setup + common) 
    t5 = timeit.Timer("tuple(a) == tuple(b)", setup + common) 

    print '==test %d==' % i 
    print ' comp1: %g' % t1.timeit() 
    print ' all gen: %g' % t2.timeit() 
    print 'all list: %g' % t3.timeit() 
    print ' list: %g' % t4.timeit() 
    print ' tuple: %g\n' % t5.timeit() 

Éstos son los resultados:

==test 0== 
    comp1: 27.8089 
all gen: 31.1406 
all list: 29.4887 
    list: 3.58438 
    tuple: 3.25859 

==test 1== 
    comp1: 0.833313 
all gen: 3.8026 
all list: 33.5288 
    list: 1.90453 
    tuple: 1.74985 

==test 2== 
    comp1: 30.606 
all gen: 31.4755 
all list: 29.5637 
    list: 3.56635 
    tuple: 1.60032 

==test 3== 
    comp1: 33.3725 
all gen: 35.3699 
all list: 34.2619 
    list: 10.2443 
    tuple: 10.1124 

==test 4== 
    comp1: 31.7014 
all gen: 32.0051 
all list: 31.0664 
    list: 8.35031 
    tuple: 8.16301 

Editar: añadido algunas pruebas más. Esto se ejecutó en un AMD 939 3800+ con 2 GB de memoria RAM. Linux 32 bits, Python 2.6.2

+0

Ahora ejecute las mismas pruebas con Psyco. – Brian

+0

Sus listas son triviales ... puede que no sea la mejor opción si cada elemento es una tarea de computación intensiva. – rafak

0

Este código "funcional" debe ser lo suficientemente rápido y genérico para todos los propósitos.

# python 2.6 ≤ x < 3.0 
import operator, itertools as it 

def seq_cmp(seqa, seqb): 
    return all(it.starmap(operator.eq, it.izip_longest(seqa, seqb))) 

Si en Python 2.5, utilizar la definición para izip_longest de there.

+0

'seq_cmp ((0,1), [0,1, None])' devolverá verdadero. Use fillvalue = object() para nunca hacer coincidir fillvalue con algo. –

5

Aparte de la memoria extra que se usa mediante la creación de listas/tuplas temporales, esas respuestas saldrán perdiendo a soluciones generador provocando un cortocircuito para las secuencias grandes cuando la desigualdad se produce al principio en las secuencias

from itertools import starmap, izip 
from operator import eq 
all(starmap(eq, izip(x, y))) 

o más concisa

from itertools import imap 
from operator import eq 
all(imap(eq, x, y)) 

algunos puntos de referencia de ipython

x=range(1000) 
y=range(1000); y[10]=0 

timeit tuple(x) == tuple(y) 
100000 loops, best of 3: 16.9 us per loop 

timeit all(imap(eq, x, y)) 
100000 loops, best of 3: 2.86 us per loop 
+0

Esta es la mejor respuesta para cuando las listas no son pequeñas, y cuando hay una posibilidad no trivial de que son diferentes en los primeros elementos. Pero el momento es muy extraño. Bajo Python 3.5 (usando 'map' y' zip' por supuesto), comenzando con 50 millones de listas largas 'x',' y': 'starmap' es 2.5-2.6 segundos; 'map' es 3.5 segundos; y la solución @Ben Blank (reemplazando 'izip_longest' con' zip' por consistencia) es de 5 segundos. (Sé que ignoramos las listas de longitudes desiguales pero lo que sea.) Esta diferencia en el rendimiento es tan extraña, ¿algún pensamiento? Por cierto, 'tuple()' tarda 2.0-2.1 segundos, apenas más rápido que 'starmap'. – max

+0

@max 'tuple' es rápido. Asumiendo que tiene mucha memoria y espera que las secuencias usualmente sean iguales a los elementos, la conversión a 'tuple' vencerá a las soluciones de generador (al menos para CPython en este momento) –

+0

oh por supuesto, pero me preguntaba por qué su' starmap' es TANTO más rápido que 'map' o @Ben Blank solution. (En realidad, su 'mapa estelar' es * casi * tan rápido como una tupla, lo cual es realmente inesperado para mí). – max

0

Creo que es una buena idea para casos especiales cuando ambas secuencias son del tipo list. Comparar dos listas es más rápido (y más eficiente en cuanto a la memoria) que convertir ambos en tuplas.

En el caso de que a o b no son listas, ambos se convierten a tuple. No hay sobrecarga si uno o ambos son ya tuplas, ya que tuple() simplemente devuelve una referencia al objeto original en ese caso.

def comp(a, b): 
    if len(a) != len(b): 
     return False 
    if type(a) == type(b) == list: 
     return a == b 
    a = tuple(a) 
    b = tuple(b) 
    return a == b 
Cuestiones relacionadas