Есть ли короткий способ проверить уникальность значений, не используя 'если' и несколько 'и?

Я пишу код, и мне нужно сравнить некоторые значения. Дело в том, что ни одна из переменных не должна иметь то же значение, что и другая. Например:

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: работает просто отлично.