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