Как я могу найти пропущенное значение более кратко? - PullRequest
76 голосов
/ 09 января 2012

Следующий код проверяет, являются ли x и y различными значениями (переменные x, y, z могут иметь только значения a, b или c) и если это так, задает z третий символ:

if x == 'a' and y == 'b' or x == 'b' and y == 'a':
    z = 'c'
elif x == 'b' and y == 'c' or x == 'c' and y == 'b':
    z = 'a'
elif x == 'a' and y == 'c' or x == 'c' and y == 'a':
    z = 'b'

Можно ли сделать это более кратким, читабельным и эффективным способом?

Ответы [ 11 ]

62 голосов
/ 09 января 2012
z = (set(("a", "b", "c")) - set((x, y))).pop()

Я предполагаю, что один из трех случаев в вашем коде выполняется.Если это так, то набор set(("a", "b", "c")) - set((x, y)) будет состоять из одного элемента, который возвращается pop().

Редактировать: В соответствии с предложением Рэймонда Хеттингера в комментариях вытакже можно использовать распаковку кортежей для извлечения одного элемента из набора:

z, = set(("a", "b", "c")) - set((x, y))
47 голосов
/ 09 января 2012

Метод strip - это еще один вариант, который быстро работает для меня:

z = 'abc'.strip(x+y) if x!=y else None
27 голосов
/ 09 января 2012

Отличный код Свена проделал слишком много работы и мог бы использовать распаковку кортежей вместо pop () . Кроме того, он мог бы добавить охранника if x != y для проверки отличимости x и y . Вот как выглядит улучшенный ответ:

# create the set just once
choices = {'a', 'b', 'c'}

x = 'a'
y = 'b'

# the main code can be used in a loop
if x != y:
    z, = choices - {x, y}

Вот сравнительные тайминги с набором времени, чтобы показать относительную производительность:

import timeit, itertools

setup_template = '''
x = %r
y = %r
choices = {'a', 'b', 'c'}
'''

new_version = '''
if x != y:
    z, = choices - {x, y}
'''

original_version = '''
if x == 'a' and y == 'b' or x == 'b' and y == 'a':
    z = 'c'
elif x == 'b' and y == 'c' or x == 'c' and y == 'b':
    z = 'a'
elif x == 'a' and y == 'c' or x == 'c' and y == 'a':
    z = 'b'
'''

for x, y in itertools.product('abc', repeat=2):
    print '\nTesting with x=%r and y=%r' % (x, y)
    setup = setup_template % (x, y)
    for stmt, name in zip([original_version, new_version], ['if', 'set']):
        print min(timeit.Timer(stmt, setup).repeat(7, 100000)),
        print '\t%s_version' % name

Вот результаты таймингов:

Testing with x='a' and y='a'
0.0410830974579     original_version
0.00535297393799    new_version

Testing with x='a' and y='b'
0.0112571716309     original_version
0.0524711608887     new_version

Testing with x='a' and y='c'
0.0383319854736     original_version
0.048309803009      new_version

Testing with x='b' and y='a'
0.0175108909607     original_version
0.0508949756622     new_version

Testing with x='b' and y='b'
0.0386209487915     original_version
0.00529098510742    new_version

Testing with x='b' and y='c'
0.0259420871735     original_version
0.0472128391266     new_version

Testing with x='c' and y='a'
0.0423510074615     original_version
0.0481910705566     new_version

Testing with x='c' and y='b'
0.0295209884644     original_version
0.0478219985962     new_version

Testing with x='c' and y='c'
0.0383579730988     original_version
0.00530385971069    new_version

Эти временные интервалы показывают, что производительность original-version довольно сильно варьируется в зависимости от того, какие операторы if запускаются различными входными значениями.

18 голосов
/ 09 января 2012
z = (set('abc') - set(x + y)).pop()

Вот все сценарии, чтобы показать, что это работает:

>>> (set('abc') - set('ab')).pop()   # x is a/b and y is b/a
'c'
>>> (set('abc') - set('bc')).pop()   # x is b/c and y is c/b
'a'
>>> (set('abc') - set('ac')).pop()   # x is a/c and y is c/a
'b'
15 голосов
/ 09 января 2012

Если эти три предмета были не "a", "b" и "c", а скорее 1, 2 и 3, вы также можете использовать двоичный XOR:

z = x ^ y

В более общем смысле, если вы хотите установить z для оставшегося одного из трех чисел a, b и c, учитывая два числа x и y из этого набора, вы можете использовать

z = x ^ y ^ a ^ b ^ c

Конечно, вы можете предварительно вычислить a ^ b ^ c, если числа фиксированы.

Этот подход также может быть использован с оригинальными буквами:

z = chr(ord(x) ^ ord(y) ^ 96)

Пример: * * тысяча двадцать-пять

>>> chr(ord("a") ^ ord("c") ^ 96)
'b'

Не ожидайте, что кто-нибудь, читающий этот код, сразу поймет, что он означает:)

13 голосов
/ 09 января 2012

Я думаю, что решение Свена Марнаха и FJ прекрасно, но в моем маленьком тесте оно не быстрее.Это оптимизированная версия Раймонда с использованием предварительно вычисленного set:

$ python -m timeit -s "choices = set('abc')" \
                   -s "x = 'c'" \
                   -s "y = 'a'" \
                      "z, = choices - set(x + y)"
1000000 loops, best of 3: 0.689 usec per loop

Это оригинальное решение:

$ python -m timeit -s "x = 'c'" \
                   -s "y = 'a'" \
                      "if x == 'a' and y == 'b' or x == 'b' and y == 'a':" \
                      "    z = 'c'" \
                      "elif x == 'b' and y == 'c' or x == 'c' and y == 'b':" \
                      "    z = 'a'" \
                      "elif x == 'a' and y == 'c' or x == 'c' and y == 'a':" \
                      "    z = 'b'"
10000000 loops, best of 3: 0.310 usec per loop

Обратите внимание, что это худший возможный ввод для if -статемий, так как все шесть сравнений должны быть опробованы.Тестирование со всеми значениями x и y дает:

x = 'a', y = 'b': 0.084 usec per loop
x = 'a', y = 'c': 0.254 usec per loop
x = 'b', y = 'a': 0.133 usec per loop
x = 'b', y = 'c': 0.186 usec per loop
x = 'c', y = 'a': 0.310 usec per loop
x = 'c', y = 'b': 0.204 usec per loop

Вариант на основе set показывает одинаковую производительность для разных входов, но она постоянно между 2 и 8 размедленнее .Причина в том, что вариант на основе if выполняет гораздо более простой код: тесты на равенство по сравнению с хешированием.

Я думаю, что оба типа решений являются ценными: важно знать, что создание "сложных" структур данных, таких как наборычто-то стоит вам в производительности - в то время как они дают вам большую читабельность и скорость разработки .Сложные типы данных также значительно улучшаются при изменении кода: решение, основанное на множестве, легко расширить до четырех, пяти, ... переменных, тогда как операторы if быстро превращаются в кошмар обслуживания.

8 голосов
/ 09 января 2012

Попробуйте эту опцию, используя словари:

z = {'ab':'c', 'ba':'c', 'bc':'a', 'cb':'a', 'ac':'b', 'ca':'b'}[x+y]

Конечно, если на карте нет ключа x+y, он выдаст KeyError, с которым вам придется справиться.

Если словарь предварительно вычисляется один раз и сохраняется для будущего использования, доступ будет намного быстрее, поскольку для каждой оценки не нужно создавать новые структуры данных, требуются только конкатенация строк и поиск по словарю:

lookup_table = {'ab':'c', 'ba':'c', 'bc':'a', 'cb':'a', 'ac':'b', 'ca':'b'}
z = lookup_table[x+y]
8 голосов
/ 09 января 2012
z = 'a'*('a' not in x+y) or 'b'*('b' not in x+y) or 'c'

или менее хакерский и использующий условное присвоение

z = 'a' if ('a' not in x+y) else 'b' if ('b' not in x+y) else 'c'

, но, вероятно, решение для диктата быстрее ... вам придется рассчитать его.

2 голосов
/ 09 января 2012

Я думаю, это должно выглядеть так:

z = (set(("a", "b", "c")) - set((x, y))).pop() if x != y else None
1 голос
/ 09 января 2012

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

l = ['a', 'b', 'c']
z = [n for n in l if n not in [x,y]].pop()

Или, как в принятом ответе, воспользоваться кортежем для его распаковки,

z, = [n for n in l if n not in [x,y]]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...