Есть ли короткий способ проверить уникальность значений, не используя 'если' и несколько 'и?
Я пишу код, и мне нужно сравнить некоторые значения. Дело в том, что ни одна из переменных не должна иметь то же значение, что и другая. Например:
a=1
b=2
c=3
if a != b and b != c and a != c:
#do something
Теперь легко увидеть, что в случае кода с большим количеством переменных оператор if
становится очень длинным и полным and
s. Есть короткий способ сказать Python, что никакие 2 значения переменных не должны быть одинаковыми.
Ответы
Ответ 1
Вы можете попробовать сделать наборы.
a, b, c = 1, 2, 3
if len({a,b,c}) == 3:
# Do something
Если ваши переменные хранятся в виде списка, это становится еще проще:
a = [1,2,3,4,4]
if len(set(a)) == len(a):
# Do something
Вот официальная документация наборов питонов.
Это работает только для хэшируемых объектов, таких как целые числа, как указано в вопросе. Для объектов без хэша см. Более общее решение @chepner.
Это определенно способ, которым вы должны использовать хешируемые объекты, поскольку для количества объектов n требуется время O (n). Комбинаторный метод для объектов без хэша занимает время O (n ^ 2).
Ответ 2
Предполагая, что хеширование не вариант, используйте itertools.combinations
и all
.
from itertools import combinations
if all(x != y for x, y in combinations([a,b,c], 2)):
# All values are unique
Ответ 3
Это немного зависит от того, какие ценности у вас есть.
Если они хорошо себя ведут и могут быть хэшируемыми, то вы можете (как уже отмечали другие) просто использовать set
чтобы узнать, сколько у вас уникальных значений, и если это не равно количеству общих значений, у вас есть как минимум два значения, которые равны.
def all_distinct(*values):
return len(set(values)) == len(values)
all_distinct(1, 2, 3) # True
all_distinct(1, 2, 2) # False
Хасимые значения и ленивый
Если у вас действительно много значений и вы хотите прервать их, как только будет найдено одно совпадение, вы также можете лениво создать набор. Это сложнее и, вероятно, медленнее, если все значения различны, но обеспечивает короткое замыкание в случае обнаружения дубликата:
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
Однако, если значения не могут быть хешируемыми, это не сработает, потому что для set
требуются хэшируемые значения.
Неиссякаемые значения
Если у вас нет хешируемых значений, вы можете использовать простой список для хранения уже обработанных значений и просто проверить, есть ли каждый новый элемент в списке:
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
Это будет медленнее, потому что проверка, находится ли значение в списке, требует его сравнения с каждым элементом в списке.
(Стороннее) решение для библиотеки
Если вы не возражаете против дополнительной зависимости, вы также можете использовать одну из моих библиотек (доступных в PyPi и conda-forge) для этой задачи iteration_utilities.all_distinct
. Эта функция может обрабатывать как хешируемые, так и не хешаемые значения (и их комбинацию):
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
Общие комментарии
Обратите внимание, что все вышеупомянутые подходы основаны на том факте, что равенство означает "не неравный", что имеет место для (почти) всех встроенных типов, но не обязательно имеет место!
Однако я хочу указать на ответы Чепнера, которые не требуют хеш-значения значений и не полагаются на "равенство означает не равное", явно проверяя !=
. Это также короткое замыкание, поэтому он ведет себя как ваш оригинал and
подход.
Спектакль
Чтобы получить общее представление о производительности, я использую другую из моих библиотек ( simple_benchmark
)
Я использовал разные вводимые значения (слева) и нечитаемые (справа). Для хэшируемых входных данных подходы с набором выполняются лучше всего, в то время как для нерасширяемых входных данных подходы с использованием списка работают лучше В combinations
-based подход казался медленным в обоих случаях:
![enter image description here]()
Я также проверил производительность в случае наличия дубликатов, для удобства я рассмотрел случай, когда первые два элемента были равны (в противном случае установка была идентична предыдущему случаю):
![enter image description here]()
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()
Ответ 4
Немного более аккуратный способ - вставить все переменные в список, а затем создать новый набор из списка. Если список и набор не имеют одинаковую длину, некоторые переменные были равны, поскольку наборы не могут содержать дубликаты:
vars = [a, b, c]
no_dupes = set(vars)
if len(vars) != len(no_dupes):
# Some of them had the same value
Это предполагает, что значения могут быть хэшируемыми; которые они в вашем примере.
Ответ 5
Вы также можете использовать all
с list.count
, это разумно, может быть не лучшим, но стоит ответить:
>>> 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
>>>
Также это решение работает с нежелательными объектами в списке.
Способ, который работает только с хешируемыми объектами в списке:
>>> a, b, c = 1, 2, 3
>>> l = [a, b, c]
>>> len({*l}) == len(l)
True
>>>
На самом деле:
>>> from timeit import timeit
>>> timeit(lambda: {*l}, number=1000000)
0.5163292075532642
>>> timeit(lambda: set(l), number=1000000)
0.7005311807841572
>>>
{*l}
быстрее, чем set(l)
, больше информации здесь.
Ответ 6
Не знаю, какую версию Python вы используете, но в Python 3, if a != b != c:
работает просто отлично.