Python Risk oyunu
Bir Risk oyunu sorusunu çözdüm (http://www.pyschools.com/quiz/view_question/s3-q12) iki şekilde, ancak şu anda Python öğreniyorum, sadece bu soruyu çözme yöntemlerimi nasıl derecelendireceğimi merak ediyorum. Mesela bu yollardan hangisi bu soruyu çözmek için gerçekten daha iyi, etkili ve verimli bir yöntem? Ya da belki benim yaptığımdan daha iyi bir yaklaşım var?
Bu oyun talimatı şöyledir: "Risk masa oyununda, savunmacı 2 zar atarken saldırganın 3 zar attığı bir durum vardır. Sonucu belirlemek için her oyuncunun en yüksek zarları karşılaştırılır, ardından bir sonraki en yüksek zar . Her durumda, saldırganın ölmesi, savunmanın kazanmasından daha yüksek olmalıdır. Kaybeden her durumda 1 ordu kaybedecektir. "
Bu, işlevin ve dönüş ifadesinin örneğidir:
>>> RiskGame([6,2,6], [6, 6])
'Attacker loses 2 armies.'
>>> RiskGame([1,4,1], [1, 2])
'Attacker loses 1 army and defender loses 1 army.'
Yöntem 1
def RiskGame(attacker, defender):
a_score = 0
a_loose = 0
d_score = 0
d_loose = 0
for e in range(len(defender)):
a= max(attacker)
d= max(defender)
if a>d:
a_score +=1
d_loose +=1
else:
d_score +=1
a_loose +=1
attacker.remove(a)
defender.remove(d)
if a_loose == 0:
return 'Defender Loses %i armies.' %d_loose
elif d_loose == 0:
return 'Attacker loses %i armies.' %a_loose
else:
return 'Attacker loses %i army and defender loses %i army.' %(a_loose, d_loose)
RiskGame([1,2,6], [1, 5])
RiskGame([1,4,1], [1, 2])
RiskGame([6,2,6], [6, 6])
Yöntem 2
def RiskGame(attacker, defender):
a = sorted(attacker, reverse=True)
b = sorted(defender, reverse=True)
a_scr =0
d_scr =0
pairs = zip(a,b)
for i,j in pairs:
if i>j:
a_scr +=1
else:
d_scr +=1
if d_scr == 0:
return 'Defender loses %i armies.' %a_scr
elif a_scr == 0:
return 'Attacker loses %i armies.' %d_scr
else:
return 'Attacker loses %i army and defender loses %i army.' %(a_scr, d_scr)
RiskGame([1,2,6], [1, 5])
RiskGame([1,4,1], [1, 2])
RiskGame([6,2,6], [6, 6])
Yanıtlar
Her iki yöntem için de güzel uygulama, birkaç öneri:
- Deve kılıfı veya alt çizgi. İşlev
RiskGamedeve durum gösterimini kullanır ancak değişkenler alt çizgi gösterimini (a_score) kullanır. Yalnızca tek bir gösterim kullanmak daha iyidir. Python'da genellikle alt çizgiler tercih edilir. - Kullanılmayan değişkenler :
a_scoreved_scoreYöntem 1'de. - Değişken isimleri geliştirilebilir :
a_scrolarak yeniden adlandırılabilirattacker_score. Bu ifade veya benzeria=max(attacker)olabilirattacker_max_number=max(attacker). Daha uzun olsa bile kodun okunmasını kolaylaştırır. - İnsan tarafından okunabilir bir dize yerine sonucu döndür, yeniden kullanmak ve test etmek daha kolaydır. Örneğin bunun yerine:
sonucu doğrudan döndür:def RiskGame(attacker, defender): #... if d_scr == 0: return 'Defender loses %i armies.' %a_scr elif a_scr == 0: return 'Attacker loses %i armies.' %d_scr else: return 'Attacker loses %i army and defender loses %i army.' %(a_scr, d_scr)def RiskGame(attacker, defender): #... return attacker_score, defender_score attacker_score, defender_score = RiskGame([1,2,6], [1, 5]) if defender_score == 0: print('Defender Loses %i armies.' %attacker_score) #...
Hangi yöntem daha etkilidir?
Girdi listesi için en fazla üç öğenin gereksinimleri göz önüne alındığında performanslarla ilgili hiçbir sorun yoktur. Aslında, @Jasmijn şartıyla, yorumlarda işaret ettiği gibi 1 <= len(attacker) <= 3ve 1 <= len(defender) <= 2her iki yöntemin zaman karmaşıklığı olduğunu \$O(1)\$.
Giriş listelerinde çok sayıda öğe varsa ve saldırganın listesi savunanın listesinden büyükse, Yöntem 2'nin daha hızlı olduğunu söyleyebilirim. Yöntem 1'in zaman karmaşıklığı \$O(d*a)\$nerede \$d\$savunmacı listesinin uzunluğu ve \$a\$saldırgan listesinin uzunluğudur. Listeler her yinelemede küçülse bile bunu büyük girdiler için söyleyebiliriz. 2. yöntem \$O(a*log(a))\$, zip()işlevin \ konumunda çalıştığını varsayarak$O(d)\$ve saldırgan listesinin sıralanması \$O(a*log(a))\$.
Şimdiden bazı iyi cevaplarınız var. Kodunuzun ayrıntılarına odaklanmak yerine, tam bir programın tasarımına nasıl yaklaşılacağı hakkında bazı yorumlar sunacağım. Durumunuzu tam olarak bilmiyorum, ancak öğrenirken, komut satırı programları çeşitli pratik nedenlerden dolayı iyi bir odak noktasıdır, bu yüzden bunu bir örnek olarak kullanacağım.
İşlevsel çekirdek ve zorunlu kabuk . Mümkün olduğunca, programınızı iki tür işleve kesin olarak ayırın: (A) veri alıp veri döndürmekten başka hiçbir şey yapmayanlar (çekirdek); ve (B) yan etkilere sahip olanlar (kabuk). Ne yan etki olarak sayılır? Pek çok şey, ancak baskı ve istisnalar en yaygın olanıdır. Buradaki temel fikir, programınızın çoğunun (ve özellikle algoritmik, hesaplama ayrıntılarının) işlevsel çekirdekte bulunması gerektiğidir. Baskı ve etkileşimle ilgili dış kabuk mümkün olduğunca ince ve algoritmik olmayan olmalıdır. Aşağıdaki örnekte main(), dış kabuktur ve algoritmik bir bakış açısından cılızdır - önemsiz bir şeyden başka bir şey değildir if-else. Bu fikirler hakkında daha fazla bilgi edinmek istiyorsanız, bu çekirdek-kabuk ayrımı üzerine gördüğüm en iyi konuşma Gary Bernhardt'dan .
Parçala . Bu temel ayrımı aklınızda bulundurduğunuzda, gerekli hesaplamaları makul ölçüde küçük parçalara ayırmaya başlayın. Bu çaba, kısmen sanat, kısmen bilimdir. Ayrıştırmayı ne kadar takip etmeniz gerektiği bağlama bağlıdır. Aşağıdaki örnek oldukça uzağa götürüyor. Program ne kadar karmaşık ve risk ne kadar yüksekse, ayrıştırma çabasını o kadar ciddiye almak istersiniz. Daha basit durumlarda çeşitli kısayollar uygundur. Ancak program önemli olduğunda, bunun için testler yazmanız gerekir ve test edilebilirliğin talepleri sizi genellikle daha fazla ayrıştırmaya yönlendirir (aynı anda çok fazla şey yapan işlevleri test etmek zor olabilir ve test edilmesi büyük bir baş ağrısıdır) önemli yan etkileri olan işlevler).
Ortaya çıkan basitliğe dikkat edin . İşlevler küçük, anlaşılması kolay, okuyucu için bir yorumda açıklanması hızlı hale geliyor. Çoğu durumda, bu faydalar, işleri parçalamak için fazladan zaman ayırmanın ekstra maliyetlerinden (genellikle önemli ölçüde) ağır basar.
import sys
def main(args):
# Command-line usage example: `python risk_game.py 3,4,6 3,5`
attacker, defender, error = parse_entries(args)
if error:
print(error)
sys.exit(1)
else:
message = risk_game(attacker, defender)
print(message)
def parse_entries(entries):
# Takes attacker and defender entries. Returns a 3-tuple: (ATTACKER-ROLLS,
# DEFENDER-ROLLS, ERROR-MESSAGE). There are more featureful and robust ways
# to handle this; adjust as needed.
try:
return (parse_entry(entries[0]), parse_entry(entries[1]), None)
except Exception as e:
return (None, None, 'Invalid entry')
def parse_entry(entry):
# Parses a single entry and returns a list of dice rolls.
return [int(val) for val in entry.split(',')]
def risk_game(attacker, defender):
# Takes two lists of dice rolls. Returns a message describing the outcome.
score = compute_battle_score(attacker, defender)
return generate_message(attacker, defender, score)
def compute_battle_score(attacker, defender):
# Takes two lists of dice rolls. Returns a battle score.
atts = sorted(attacker, reverse = True)
defs = sorted(defender, reverse = True)
return sum(1 if a > d else -1 for a, d in zip(atts, defs))
# Or if you need to know N of victories for each combatant.
return collections.Counter(a > d for a, d in zip(atts, defs))
def generate_message(attacker, defender, score):
# Make it as fancy as you want.
return f'Attacker score: {score}'
if __name__ == '__main__':
main(sys.argv[1:])
İlki girişi değiştirir, öyle olması gerekmedikçe kötüdür. Verilen listelerin kopyalarını oluşturup bunun yerine üzerinde çalışarak bunu düzeltebilirsiniz.
Dize biçimlendirme değerleri her zaman olduğundan, gereksiz 2, 2ya (1, 1)sırasıyla. Aynı zamanda bize sayıları rakam değil sözcük olarak yazarak iyi bir yazma stili yapma fırsatı verir. Evet, problem çözücü için rakamların gerekli olduğunu biliyorum, bu yüzden bu sizin değil, onların hatası. Sadece söylüyorum. Daha fazla zar kullanılması durumunda, sizinkinin daha genel olduğunu iddia edebilirsiniz, ancak o zaman "Saldırgan 5 ordu kaybediyor" gibi bir şey söylemekten kaçınmak için tekil / çoğulu da uygun şekilde kullanmalısınız.
Sonunda bir taraf seçerdim. Yani, sadece bir tarafın istatistiğini sayın. Saldırganla gideceğim, çünkü bu "aktif" taraftır (savunmanın bir tepki olmasının aksine ).
def RiskGame(attacker, defender):
_, a1, a2 = sorted(attacker)
d1, d2 = sorted(defender)
wins = (a2 > d2) + (a1 > d1)
if wins == 2:
return 'Defender loses two armies.'
elif wins == 0:
return 'Attacker loses two armies.'
else:
return 'Attacker loses one army and defender loses one army.'
Bu gerçek bir PR olsaydı, *loosedeğişkenlerden birini vurgular ve " Bu okunmalılose mı?" Diye sorardım.
İkinci yaklaşımınızı gerçekten beğendim. Onu okurken, bir sonraki kod satırının tam olarak yapacağını tahmin ettiğim şeyi yaptığını hissettim ve okurken ilk yaklaşımınızı yaptığım gibi çok fazla zihinsel hafıza kullanmak zorunda değildim, tüm bunları takip ederek sıfıra ayarlanmış değişkenler. Geliştirmeyi düşünebileceğiniz bir yol, a_scrve d_scrilklendirmeleri kaldırmak ve sum()bunları hesaplamak için a kullanmaktır , örneğin:
def RiskGame(attacker, defender):
a = sorted(attacker, reverse=True)
b = sorted(defender, reverse=True)
a_scr = sum([i > j for i, j in zip(a,b)])
d_scr = sum([i < j for i, j in zip(a,b)])
if d_scr == 0:
return 'Defender loses %i armies.' %a_scr
elif a_scr == 0:
return 'Attacker loses %i armies.' %d_scr
else:
return 'Attacker loses %i army and defender loses %i army.' %(a_scr, d_scr)
RiskGame([1,2,6], [1, 5])
Pascal'ın fonksiyonların durumu ve değerler yerine dizgeleri döndürme konusunda benzer çekincelerim var, ancak bunlar diğer cevaplarda güzelce anlatılıyor.
Düzenleme: Testlerin nasıl kullanılacağını gösterin
Bu bölüm, pytest kullanarak örnek savaşlarınızın sistematik bir şekilde doğru şekilde yürütülmesini sağlamanın başka bir yolunu kapsar. Diğer bazı yanıtların işlevden çıktısını almak için önerilerinin kodlamayı neden kolaylaştırdığını gösterir.
Aşama 1.
Ana işlevi sadece dönmek var a_scrve d_scrve ana senaryoda baskı mantığı koyun:
# riskgame.py
def risk_game(attacker, defender):
a = sorted(attacker, reverse=True)
b = sorted(defender, reverse=True)
a_scr = sum([i > j for i, j in zip(a,b)])
d_scr = sum([i <= j for i, j in zip(a,b)])
return a_scr, d_scr
if __name__ == "__main__":
a_scr, d_scr = risk_game([6,2,6], [6, 6])
if d_scr == 0:
print('Defender loses %i armies.' %a_scr)
elif a_scr == 0:
print('Attacker loses %i armies.' %d_scr)
else:
print('Attacker loses %i army and defender loses %i army.' %(a_scr, d_scr))
Komut dosyasını çalıştırdığınızda, öncekiyle aynı davranışı elde edersiniz:
$ python riskgame.py
Attacker loses 2 armies.
Adım 2.
Aynı dizinde test_wins_and_losses.pybazı testler oluşturun ve oluşturun:
# test_wins_and_losses.py
from riskgame import risk_game
def test_d_wins():
a_scr, d_scr = risk_game([1,2,6], [1, 5])
assert a_scr == 2
assert d_scr == 0
def test_a_wins():
a_scr, d_scr = risk_game([6,2,6], [6, 6])
assert a_scr == 0
assert d_scr == 2
def test_equal():
a_scr, d_scr = risk_game([1,4,1], [1, 2])
assert a_scr == 1
assert d_scr == 1
Dikkat edin, orijinal gönderiye koyduğunuz aynı değerleri kullandım, ancak orada istediğiniz herhangi bir örnek oyun olabilir. İdeal olarak, olabildiğince çok kullanım durumunu kapsayan çok şeyiniz olur.
Aşama 3.
Henüz yapmadıysanız, pytest yükleyin.
$ pip install pytest
4. adım.
Çalıştır şunu!
$ pytest
============================= test session starts ==============================
platform linux -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1
rootdir: /tmp/risk_game
collected 3 items
test_wins_and_losses.py ... [100%]
============================== 3 passed in 0.02s ===============================
Buradaki fikir, şimdi kodunuzu değiştirebilmenizdir ve her yaptığınızda pytestkomut satırına yazarak her şeyin hala beklediğiniz gibi çalıştığını onaylayabilirsiniz . Örneğin, daha önce yaptığım hatayı yaparsak ve satırı şu şekilde değiştirirsek
d_scr = sum([i < j for i, j in zip(a,b)])
ve testleri çalıştırırsak:
$ pytest
==================================================================== test session starts =====================================================================
platform linux -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1
rootdir: /tmp/risk_game
collected 3 items
test_wins_and_losses.py .FF [100%]
========================================================================== FAILURES ==========================================================================
________________________________________________________________________ test_a_wins _________________________________________________________________________
def test_a_wins():
a_scr, d_scr = risk_game([6,2,6], [6, 6])
assert a_scr == 0
> assert d_scr == 2
E assert 0 == 2
test_wins_and_losses.py:11: AssertionError
_________________________________________________________________________ test_equal _________________________________________________________________________
def test_equal():
a_scr, d_scr = risk_game([1,4,1], [1, 2])
assert a_scr == 1
> assert d_scr == 1
E assert 0 == 1
test_wins_and_losses.py:16: AssertionError
================================================================== short test summary info ===================================================================
FAILED test_wins_and_losses.py::test_a_wins - assert 0 == 2
FAILED test_wins_and_losses.py::test_equal - assert 0 == 1
================================================================ 2 failed, 1 passed in 0.09s ================================================================
Mutlu testler!