python duplicates

python - ¿Hay una manera corta de verificar la unicidad de los valores sin usar ''if'' y multiple ''and?



duplicates (5)

Estoy escribiendo algún código y necesito comparar algunos valores. El punto es que ninguna de las variables debe tener el mismo valor que otra. Por ejemplo:

a=1 b=2 c=3 if a != b and b != c and a != c: #do something

Ahora, es fácil ver que en un caso de código con más variables, la instrucción if vuelve muy larga y llena de and s. ¿Hay una manera corta de decirle a Python que no hay valores de 2 variables que deban ser iguales?


Depende un poco del tipo de valores que tengas.

Si se comportan bien y tienen hashable, puede (como otros ya lo han señalado) simplemente usar un set para averiguar cuántos valores únicos tiene y si eso no es igual al número de valores totales, tiene al menos dos valores que son iguales.

def all_distinct(*values): return len(set(values)) == len(values) all_distinct(1, 2, 3) # True all_distinct(1, 2, 2) # False

Valores hashable y perezosos.

En caso de que realmente tenga muchos valores y desee abortar tan pronto como se encuentre una coincidencia, también podría crear el conjunto con pereza. Es más complicado y probablemente más lento si todos los valores son distintos pero proporciona cortocircuito en caso de que se encuentre un duplicado:

def all_distinct(*values): seen = set() seen_add = seen.add last_count = 0 for item in values: seen_add(item) new_count = len(seen) if new_count == last_count: return False last_count = new_count return True all_distinct(1, 2, 3) # True all_distinct(1, 2, 2) # False

Sin embargo, si los valores no son hashables, esto no funcionará porque el set requiere valores hashable.

Valores que no se pueden romper

En caso de que no tenga valores de hashable, podría usar una lista simple para almacenar los valores ya procesados ​​y simplemente verificar si cada nuevo elemento ya está en la lista:

def all_distinct(*values): seen = [] for item in values: if item in seen: return False seen.append(item) return True all_distinct(1, 2, 3) # True all_distinct(1, 2, 2) # False all_distinct([1, 2], [2, 3], [3, 4]) # True all_distinct([1, 2], [2, 3], [1, 2]) # False

Esto será más lento porque la verificación de si un valor está en una lista requiere compararlo con cada elemento de la lista.

Una solución de biblioteca (de terceros)

En caso de que no le importe una dependencia adicional, también puede usar una de mis bibliotecas (disponible en PyPi y conda-forge) para esta tarea iteration_utilities.all_distinct . Esta función puede manejar valores hashable e inestables (y una combinación de estos):

from iteration_utilities import all_distinct all_distinct([1, 2, 3]) # True all_distinct([1, 2, 2]) # False all_distinct([[1, 2], [2, 3], [3, 4]]) # True all_distinct([[1, 2], [2, 3], [1, 2]]) # False

Comentarios generales

Tenga en cuenta que todos los enfoques mencionados anteriormente se basan en el hecho de que la igualdad significa "no es no igual", que es el caso de (casi) todos los tipos integrados, ¡pero no necesariamente es el caso!

Sin embargo, quiero señalar las respuestas de los chepners que no requieren capacidad de hash de los valores y no se basan en "igualdad significa no igual" al verificar explícitamente != . También es un cortocircuito, por lo que se comporta como su original and enfoque.

Actuación

Para tener una idea aproximada del rendimiento, estoy usando otra de mis bibliotecas ( simple_benchmark )

Utilicé distintas entradas de hashable (izquierda) e insumos inestables (derecha). Para las entradas de hashable, los enfoques de conjunto se comportaron mejor, mientras que para las entradas no soplables, los enfoques de lista funcionaron mejor. El enfoque basado en combinations parecía más lento en ambos casos:

También probé el rendimiento en caso de que haya duplicados, por conveniencia consideré el caso cuando los dos primeros elementos eran iguales (de lo contrario, la configuración era idéntica a la del caso anterior):

from iteration_utilities import all_distinct from itertools import combinations from simple_benchmark import BenchmarkBuilder # First benchmark b1 = BenchmarkBuilder() @b1.add_function() def all_distinct_set(values): return len(set(values)) == len(values) @b1.add_function() def all_distinct_set_sc(values): seen = set() seen_add = seen.add last_count = 0 for item in values: seen_add(item) new_count = len(seen) if new_count == last_count: return False last_count = new_count return True @b1.add_function() def all_distinct_list(values): seen = [] for item in values: if item in seen: return False seen.append(item) return True b1.add_function(alias=''all_distinct_iu'')(all_distinct) @b1.add_function() def all_distinct_combinations(values): return all(x != y for x, y in combinations(values, 2)) @b1.add_arguments(''number of hashable inputs'') def argument_provider(): for exp in range(1, 12): size = 2**exp yield size, range(size) r1 = b1.run() r1.plot() # Second benchmark b2 = BenchmarkBuilder() b2.add_function(alias=''all_distinct_iu'')(all_distinct) b2.add_functions([all_distinct_combinations, all_distinct_list]) @b2.add_arguments(''number of unhashable inputs'') def argument_provider(): for exp in range(1, 12): size = 2**exp yield size, [[i] for i in range(size)] r2 = b2.run() r2.plot() # Third benchmark b3 = BenchmarkBuilder() b3.add_function(alias=''all_distinct_iu'')(all_distinct) b3.add_functions([all_distinct_set, all_distinct_set_sc, all_distinct_combinations, all_distinct_list]) @b3.add_arguments(''number of hashable inputs'') def argument_provider(): for exp in range(1, 12): size = 2**exp yield size, [0, *range(size)] r3 = b3.run() r3.plot() # Fourth benchmark b4 = BenchmarkBuilder() b4.add_function(alias=''all_distinct_iu'')(all_distinct) b4.add_functions([all_distinct_combinations, all_distinct_list]) @b4.add_arguments(''number of hashable inputs'') def argument_provider(): for exp in range(1, 12): size = 2**exp yield size, [[0], *[[i] for i in range(size)]] r4 = b4.run() r4.plot()


La forma ligeramente más ordenada es pegar todas las variables en una lista, luego crear un nuevo conjunto de la lista. Si la lista y el conjunto no tienen la misma longitud, algunas de las variables eran iguales, ya que los conjuntos no pueden contener duplicados:

vars = [a, b, c] no_dupes = set(vars) if len(vars) != len(no_dupes): # Some of them had the same value

Esto supone que los valores son hashable; cuales son en tu ejemplo


Puede usar all con list.count también, es razonable, puede que no sea el mejor, pero vale la pena responder:

>>> a, b, c = 1, 2, 3 >>> l = [a, b, c] >>> all(l.count(i) < 2 for i in l) True >>> a, b, c = 1, 2, 1 >>> l = [a, b, c] >>> all(l.count(i) < 2 for i in l) False >>>

También esta solución funciona con objetos inestables en la lista.

Una forma que solo funciona con objetos hashable en la lista:

>>> a, b, c = 1, 2, 3 >>> l = [a, b, c] >>> len({*l}) == len(l) True >>>

Actualmente:

>>> from timeit import timeit >>> timeit(lambda: {*l}, number=1000000) 0.5163292075532642 >>> timeit(lambda: set(l), number=1000000) 0.7005311807841572 >>>

{*l} es más rápido que set(l) , más información here .


Puedes intentar hacer sets.

a, b, c = 1, 2, 3 if len({a,b,c}) == 3: # Do something

Si sus variables se mantienen como una lista, se vuelve aún más simple:

a = [1,2,3,4,4] if len(set(a)) == len(a): # Do something

Here está la documentación oficial de los conjuntos de python.

Esto funciona solo para objetos hashable como los enteros, como se indica en la pregunta. Para objetos no hashables, vea la solución más general de @chepner.

Esta es definitivamente la forma en que debe ir para los objetos hashable, ya que toma O (n) tiempo para la cantidad de objetos n . El método combinatorio para objetos no hashable toma tiempo O (n ^ 2).


Suponiendo que el hashing no es una opción, use itertools.combinations y all .

from itertools import combinations if all(x != y for x, y in combinations([a,b,c], 2)): # All values are unique