Как я могу найти недостающее значение более кратко?
следующий код проверяет, если 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 ответов:
z = (set(("a", "b", "c")) - set((x, y))).pop()
Я предполагаю, что один из трех случаев в коде держит. Если это так, то набор
set(("a", "b", "c")) - set((x, y))
будет состоять из одного элемента, который возвращенpop()
.Edit: как предложил Раймонд Хеттингер в комментариях, вы также можете использовать кортеж распаковки для извлечения одного элемента из набора:
z, = set(("a", "b", "c")) - set((x, y))
The
strip
метод-это еще один вариант, который работает быстро для меня:z = 'abc'.strip(x+y) if x!=y else None
отличный код Свена сделал просто немного слишком много работы и chould использовать кортеж распаковки вместо 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
эти тайминги показывают, что оригинальные версии производительность варьируется совсем немного в зависимости от того, какие if-операторы запускаются различными входными значениями.
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'
если бы эти три пункта не были
"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'
не ожидайте, что кто-то, читая этот код, сразу поймет, что это значит:)
Я думаю, что решение Sven Marnach и F. J красиво, но это не быстрее в моем маленьком тесте. Это оптимизированная версия Raymond с использованием предварительно вычисленного
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-операторы быстро превращаются в кошмар обслуживания.
попробуйте эту опцию, используя словари:
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]
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'
но, вероятно, решение dict быстрее... нужно время.
Я думаю, что это должно выглядит так:
z = (set(("a", "b", "c")) - set((x, y))).pop() if x != y else None