Soru Eksik değeri nasıl daha özlü bir şekilde bulabilirim?


Aşağıdaki kod, olup olmadığını kontrol eder x ve y farklı değerler (değişkenler) x, y, z sadece değerler olabilir a, bveya c) ve eğer öyleyse, setleri z üçüncü karaktere:

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'

Bunu daha özlü, okunabilir ve verimli bir şekilde yapmak mümkün mü?


76
2018-01-09 17:21


Menşei


Kısa cevap "Evet!" Python'un setleri, farklılıkları kontrol etmek ve kullanılmayan öğeleri hesaplamak için mükemmeldir. - Raymond Hettinger
Cevapların hepsi için teşekkürler, sanırım çözümü kullanarak hızlı ve okunabilir bir çözüm kullanarak kullanacağım, Lópezscar López tarafından yapılan arama tablosuna dayalı cevap da ilgi çekicidir. - Bunny Rabbit


Cevaplar:


z = (set(("a", "b", "c")) - set((x, y))).pop()

Kodunuzdaki üç durumdan birinin olduğunu farz ediyorum. Bu durumda, set set(("a", "b", "c")) - set((x, y)) tarafından döndürülen tek bir öğeden oluşacaktır pop().

Düzenle: Raymond Hettinger'ın yorumlarda da önerdiği gibi, tek bir elemanı setten çıkarmak için tuple açmayı da kullanabilirsiniz:

z, = set(("a", "b", "c")) - set((x, y))

62
2018-01-09 17:23



Python 2.7 / 3.1 veya daha sonraki bir sürümü kullanıyorsanız, set editörlerini kullanarak daha net bir şekilde yazabilirsiniz. z = ({'a', 'b', 'c'} - {x, y}).pop() - Taymon
pop() gereksiz ve yavaştır. Bunun yerine tuple ambalajını kullanın. Ayrıca set(("a", "b", "c")) değişmezdir, bu yüzden bir kez önceden ayarlanmış olabilir, sadece bir döngüde kullanılmak üzere set farklılığı bırakır (eğer bir döngüde kullanılmazsa, o zaman hız hakkında fazla bir şey umursamaz). - Raymond Hettinger
@Ed: Biliyorum, ancak OP ne yapacağını belirtmedi x == yBu yüzden testi ihmal ettim. Eklemek için yeterince kolay if x != y: gerekirse. - Sven Marnach
Şahsen ben ilk rastgele bir virgül daha bariz olduğu için tercih ederim. - John
Değiştirmek isteyebilirsiniz set(("a", "b", "c")) tarafından set("abc"). - kasyc


strip Metod, benim için hızlı çalışan başka bir seçenektir:

z = 'abc'.strip(x+y) if x!=y else None

47
2018-01-09 19:15



+1 Ayrıca çok şeffaf ve cevapların çoğunun aksine x == y ile ilgileniyor. - Ed Staub
Güzel fikir, +1; Aslında bunu düşünüyorum "a", "b" ve "c" Orijinal gönderide gerçek değerler için sadece yer tutucuları vardır. Bu çözüm, uzunluk 1'in dizelerinden başka bir değer türüne genellenmez. - Sven Marnach
@chepner thats yaratıcı! Chepner'a cevap verdiğiniz için teşekkürler. - Bunny Rabbit


Sven'in mükemmel kodu, sadece biraz fazla iş yaptı ve tuple paketini açmak yerine pop(). Ayrıca, bir bekçi ekleyebilirdi if x != y kontrol etmek x ve y ayrı olmak. Geliştirilmiş cevap şöyle görünüyor:

# 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}

Göreceli performansı göstermek için bir zamanlama paketiyle karşılaştırmalı zamanlamalar:

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

İşte zamanlamaların sonuçları:

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

Bu zamanlamalar, Orijinal versiyon Performans, giriş değerlerinin çeşitli tarafından hangi if ifadelerinin tetiklendiğine bağlı olarak biraz değişir.


28
2018-01-09 19:06



Testin önyargılı görünüyor. "Set_version" denilen şey sadece bazen daha hızlıdır çünkü ek bir if Beyan. - ekhumoro
@ekhumoro Sorun belirtimi şu şekilde çağrıldı: " x ve y farklı değerler ve eğer öyleyse, ayarlar z üçüncü karaktere ". Değerlerin farklı olup olmadığını kontrol etmek için en hızlı (ve en düz ileri) yolu x != y. Sadece ayrı olduklarında, üçüncü karakteri belirlemek için set-fark yaparız :-) - Raymond Hettinger
Yaptığım nokta testlerinizin set_version daha iyi performans gösterir çünkü setlere dayanıyor; sadece koruma nedeniyle daha iyi performans gösterir if Beyan. - ekhumoro
@ekhumoro Bu test sonuçlarının garip bir okuması. Kod yapar OP ne istedi? Zamanlamalar, tüm olası girdi grupları ile karşılaştırmalı performansı gösterir. Bunları nasıl yorumlamak istediğinize bağlı. Kullanılacak sürümün zamanlamaları if x != y: z, = choices - {x, y} OP'nin orijinal koduna göre oldukça makul fiyatlar. Önyargı kavramının nereden geldiğini bilmiyorum - zamanlamaları oldukları gibi ve AFAICT, bu hala gönderilen cevapların en iyisi. Hem temiz hem de hızlı. - Raymond Hettinger
Sven'in "set-version" 'a birkaç optimizasyon eklendi, fakat aynı "if-version" için de aynı şey yapılmadı. Ekleme if x != y "if-version" unun muhafazası, muhtemelen şimdiye kadar sunulan diğer tüm çözümlerden daha tutarlı ve daha iyi performans göstermesini sağlayacaktır (açıkça okunabilir ve özlü olmamakla birlikte). "Set_version" ifadeniz çok iyi bir çözümdür. oldukça testler kadar iyi görünüyor ;-) - ekhumoro


z = (set('abc') - set(x + y)).pop()

İşte bunun işe yaradığını gösteren senaryoların tümü:

>>> (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'

18
2018-01-09 17:24





Söz konusu üç madde değilse "a", "b" ve "c", daha ziyade 1, 2 ve 3İkili bir XOR kullanabilirsiniz:

z = x ^ y

Daha genel olarak, eğer ayarlamak isterseniz z üç sayının kalan birine a, b ve c iki sayı verildi x ve y bu setten kullanabilirsiniz

z = x ^ y ^ a ^ b ^ c

Tabii ki önceden yapabilirsiniz a ^ b ^ c sayılar sabit ise.

Bu yaklaşım aynı zamanda orijinal harflerle de kullanılabilir:

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

Örnek:

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

Bu kodu okuyan kimsenin ne anlama geldiğini hemen anlamasını beklemeyin :)


15
2018-01-09 18:02



+1 Bu çözüm güzel ve zarif görünüyor; ve eğer kendi işlevini yaparsanız ve sihirli numarayı 96 bir isim verirseniz, mantığın takip edilmesi / bakımı oldukça kolaydır (xor_of_a_b_c = 96 # ord('a') ^ ord('b') ^ ord('c') == 96). Bununla birlikte, ham hız açısından bu uzun zincirin yaklaşık% 33 daha yavaştır. if / elifs; ama% 500 daha hızlı set yöntem. - dr jimbob
SENİ XOR operatörünü bana tanıttığınız için teşekkürler. Çözümünüz temiz ve zariftir, bence bu örnek beynime yapışacaktır, tekrar teşekkürler :) - Bunny Rabbit


Sven Marnach ve F.J'nin çözümünün güzel olduğunu düşünüyorum, ama benim küçük testimde daha hızlı değil. Bu, önceden hesaplanmış bir yöntemle Raymond'un optimize edilmiş versiyonu. 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

Bu orijinal çözüm:

$ 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

Bu olduğunu unutmayın en kötü olası girdi için if- tüm altı karşılaştırmanın denenmesi gerektiğinden İçin tüm değerlerle test etme x ve y verir:

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

settemelli varyant farklı girişler için aynı performansı gösterir, ancak sürekli olarak 2 ve 8 kat daha yavaş. Nedeni şu ki iftemelli değişken çok daha basit bir kod çalıştırır: eşitlik testleri karma ile karşılaştırılmıştır.

Her iki çözümün de değerli olduğunu düşünüyorum: setler gibi “karmaşık” veri yapıları oluşturmanın size performansta bir şey vereceğini bilmek çok önemli. gelişme hızı. Karmaşık veri türleri de kod değiştiğinde çok daha iyidir: set-tabanlı çözümü dört, beş, ... değişkenlere genişletmek kolay olsa da if-ifadeleri hızla bir bakım kabusuna dönüşür.


13
2018-01-09 17:39



@martinGeisler cevabınız için çok teşekkürler, python'da bu gibi şeylere zaman ayırabileceğimize dair hiçbir fikrim yoktu. Chessmasters çözümünün sadece iyi ve verimli çalışacağını hissettiğim bir his var, ben de yaptığınız gibi test etmeye çalışacağım diğer cevaplar ve bilmenizi sağlar. - Bunny Rabbit
Set tabanlı çözüm optimize özlülük ve okunabilirliği (ve zarafet). Fakat verim ayrıca belirtildi, ben de önerilen çözümlerin performansını araştırdım ve araştırdım. - Martin Geisler
@MartinGeisler: Evet, bunu fark ettiğimde, yorumumu kaldırdım. Ve genellikle en azından neyin daha hızlı olduğunu bilmek ilginç buluyorum. - Sven Marnach
@BunnyRabbit: timeit modülü, bunun gibi mikro kriterler için harikadır. Tabi ki yapmalısın önce genel programınızı profilleyin darboğazların nerede olduğunu belirlemek için, ancak tanımlandıklarında, zaman çizelgesi birbirinden farklı uygulamaları hızlı bir şekilde denemenin harika bir yolu olabilir. - Martin Geisler
+1 - basit bir dizi karşılaştırmayı kanıtlayan karşılaştırma testi mantıklı ve hızlıdır. - Jeff Ferland


Sözlükleri kullanarak bu seçeneği deneyin:

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

Tabii ki x+y anahtar haritada mevcut değil, bir KeyError hangi işlemek zorunda kalacaksın.

Sözlük önceden tek bir kez önceden ayarlanmışsa ve daha sonra kullanılmak üzere saklanırsa, erişim çok daha hızlı olacaktır, çünkü her değerlendirme için yeni veri yapılarının oluşturulması gerekmeyeceğinden, yalnızca bir dizgi birleştirme ve bir sözlük araması gereklidir:

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

8
2018-01-09 17:45



Sadece eğlence için, başka bir dict seçeneği: {1: 'c', 2: 'b', 3: 'a'}[ord(x)+ord(y)-ord('a')*2]Ekstra karmaşıklık, muhtemelen kurtarılan alana değmez. - Andrew Clark
@ F.J: z = {1: 'a', 2: 'b', 3: 'c'}[2*('a' in x+y)+('b' in x+y)]  bu eğlenceli... - ChessMaster
Yaratıcı olmak ve hızlı kod almak için +1. - Raymond Hettinger
Wohoo bu kadar hızlı! - Bunny Rabbit
OP orijinal kodundan daha mı hızlı? Öyleyse neden? Karma değerlerin hesaplanması basit karşılaştırmadan nasıl daha hızlı olabilir? - max


z = 'a'*('a' not in x+y) or 'b'*('b' not in x+y) or 'c'

veya daha az hackish ve Koşullu Atama kullanma

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

ama muhtemelen bu dict çözümü daha hızlıdır ... zamanlamanız gerekir.


8
2018-01-09 17:44





Sanırım öyle görünüyor:

z = (set(("a", "b", "c")) - set((x, y))).pop() if x != y else None

2
2018-01-09 17:38



len(set((x, y))) == 2 yazmanın en okunaksız yolu x != y Şimdiye kadar gördüğüm :) - Sven Marnach
Evet, Sven))) Yorumunuz için teşekkürler. Bu betiğin yazmaya başladığımda başka bir temel fikri vardı.) Sonunda bunu düzenlemeyi unuttum. - selfnamed