เกม Python Risk
ฉันแก้ไขคำถามเกี่ยวกับเกมความเสี่ยง (http://www.pyschools.com/quiz/view_question/s3-q12) ได้สองวิธี แต่ในขณะที่ฉันกำลังเรียนรู้ Python ฉันแค่สงสัยว่าจะให้คะแนนวิธีการแก้คำถามนี้อย่างไร เช่นเดียวกับวิธีใดต่อไปนี้เป็นวิธีที่ดีกว่ามีประสิทธิภาพและประสิทธิผลในการแก้ปัญหานี้ หรืออาจจะมีแนวทางที่ดีกว่าที่เคยทำ?
นี่คือคำแนะนำของเกม: "ในเกมกระดาน Risk มีสถานการณ์ที่ผู้โจมตีทอยลูกเต๋า 3 ลูกในขณะที่ฝ่ายรับทอยลูกเต๋า 2 ลูกในการตัดสินผลจะมีการเปรียบเทียบการตายสูงสุดของผู้เล่นแต่ละคนตามด้วยการตายสูงสุดถัดไป . ในแต่ละกรณีการตายของผู้โจมตีจะต้องสูงกว่าฝ่ายรับจึงจะชนะผู้แพ้จะสูญเสียกองทัพ 1 ฝ่ายในแต่ละกรณี "
นี่คือตัวอย่างของฟังก์ชันและคำสั่ง return:
>>> 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.'
วิธีที่ 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])
วิธีที่ 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])
คำตอบ
การใช้งานที่ดีสำหรับทั้งสองวิธีคำแนะนำเล็กน้อย:
- ซองอูฐหรือขีดล่าง ฟังก์ชัน
RiskGameนี้ใช้สัญกรณ์กรณีอูฐ แต่ตัวแปรใช้เครื่องหมายขีดล่าง (a_score) ดีกว่าที่จะใช้สัญกรณ์เดียว โดยทั่วไปควรใช้ขีดล่างใน Python - ตัวแปรที่ไม่ได้ใช้ :
a_scoreและd_scoreในวิธีที่ 1 - ชื่อตัวแปรสามารถปรับปรุงได้ :
a_scrสามารถเปลี่ยนชื่อเป็นattacker_score. ข้อความนี้a=max(attacker)อาจattacker_max_number=max(attacker)คล้ายหรือคล้ายกัน แม้ว่าจะยาวกว่า แต่ก็ทำให้อ่านโค้ดได้ง่ายขึ้น - ส่งคืนผลลัพธ์แทนที่จะเป็นสตริงที่มนุษย์อ่านได้ง่ายต่อการใช้ซ้ำและทดสอบ ตัวอย่างเช่นแทนที่จะเป็น:
ส่งคืนผลลัพธ์โดยตรง: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) #...
วิธีใดมีประสิทธิภาพมากกว่ากัน?
ไม่มีปัญหาเกี่ยวกับการแสดงตามข้อกำหนดขององค์ประกอบสูงสุดสามรายการสำหรับรายการอินพุต ในความเป็นจริงดังที่ @Jasmijn ชี้ให้เห็นในความคิดเห็นเกี่ยวกับเงื่อนไขที่ว่า1 <= len(attacker) <= 3และ1 <= len(defender) <= 2ความซับซ้อนของเวลาของทั้งสองวิธีคือ\$O(1)\$.
หากรายการอินพุตมีหลายองค์ประกอบและรายชื่อผู้โจมตีใหญ่กว่ารายชื่อผู้พิทักษ์ฉันจะบอกว่าวิธีที่ 2 นั้นเร็วกว่า ความซับซ้อนของเวลาของวิธีที่ 1 จะเป็น\$O(d*a)\$ที่ไหน\$d\$คือความยาวของรายชื่อกองหลังและ\$a\$คือความยาวของรายชื่อผู้โจมตี แม้ว่ารายการจะหดตัวลงในการวนซ้ำแต่ละครั้ง แต่เราสามารถพูดได้ว่าสำหรับอินพุตขนาดใหญ่ วิธีที่ 2 คือ\$O(a*log(a))\$สมมติว่าzip()ฟังก์ชันทำงานใน\$O(d)\$และการจัดเรียงรายชื่อผู้โจมตีจะใช้\$O(a*log(a))\$.
คุณมีคำตอบที่ดีอยู่แล้ว แทนที่จะเน้นที่รายละเอียดของโค้ดของคุณฉันจะเสนอความคิดเห็นเกี่ยวกับวิธีการออกแบบโปรแกรมเต็มรูปแบบ ฉันไม่ทราบสถานการณ์ที่แน่นอนของคุณ แต่เมื่อคุณกำลังเรียนรู้โปรแกรมบรรทัดคำสั่งเป็นจุดโฟกัสที่ดีด้วยเหตุผลเชิงปฏิบัติหลายประการดังนั้นฉันจะใช้เป็นภาพประกอบ
หลักในการทำงานและเปลือกความจำเป็น ให้มากที่สุดเท่าที่จะเป็นไปได้ให้แยกโปรแกรมของคุณออกเป็นฟังก์ชันสองประเภทอย่างเคร่งครัด: (A) สิ่งที่ไม่ทำอะไรเลยนอกจากรับข้อมูลและส่งคืนข้อมูล (แกนกลาง); และ (B) สิ่งที่มีผลข้างเคียง (เปลือก) สิ่งที่นับเป็นผลข้างเคียง? หลายสิ่งหลายอย่าง แต่การพิมพ์และข้อยกเว้นเป็นสองสิ่งที่พบบ่อยที่สุด แนวคิดหลักในที่นี้คือโปรแกรมส่วนใหญ่ของคุณ (และโดยเฉพาะอย่างยิ่งอัลกอริทึมรายละเอียดการคำนวณ) ควรอยู่ในแกนหลักที่ใช้งานได้ เปลือกนอกที่เกี่ยวข้องกับการพิมพ์และการโต้ตอบควรมีความบางและไม่ใช้อัลกอริทึมให้มากที่สุด ในตัวอย่างด้านล่างที่main()เป็นเปลือกนอกและมันก็เป็นอ่อนแอจากมุมมองของอัลกอริทึม - if-elseไม่มีอะไรมากไปกว่าเล็กน้อย หากคุณต้องการที่จะเรียนรู้เพิ่มเติมเกี่ยวกับความคิดเหล่านี้พูดคุยที่ดีที่สุดที่ผมเคยเห็นการแยกแกนเปลือกนี้มาจากแกรี่แบร์นฮาร์ด
ทำลายมันลง เมื่อคุณคำนึงถึงการแบ่งแยกพื้นฐานดังกล่าวแล้วให้เริ่มแยกย่อยการคำนวณที่จำเป็นออกเป็นส่วนเล็ก ๆ ที่สมเหตุสมผล ความพยายามนี้เป็นศิลปะส่วนหนึ่งวิทยาศาสตร์ส่วนหนึ่ง คุณควรติดตามการสลายตัวไปไกลแค่ไหนขึ้นอยู่กับบริบท ตัวอย่างด้านล่างใช้เวลาค่อนข้างไกล ยิ่งโปรแกรมมีความซับซ้อนมากขึ้นและเงินเดิมพันสูงคุณก็ยิ่งต้องการใช้ความพยายามในการย่อยสลายมากขึ้นเท่านั้น ในสถานการณ์ที่ง่ายกว่านั้นทางลัดต่างๆก็ใช้ได้ แต่เมื่อโปรแกรมมีความสำคัญคุณต้องเขียนการทดสอบและความต้องการในการทดสอบมักจะผลักดันให้คุณแยกย่อยมากขึ้น (อาจเป็นเรื่องยากที่จะทดสอบฟังก์ชันที่ทำหลายอย่างพร้อมกันมากเกินไปและเป็นเรื่องยากที่จะทดสอบ ฟังก์ชันที่มีผลข้างเคียงที่สำคัญ)
ขอให้สังเกตความเรียบง่ายที่โผล่ออกมา ฟังก์ชั่นจบลงด้วยขนาดเล็กเข้าใจง่ายอธิบายอย่างรวดเร็วในความคิดเห็นสำหรับผู้อ่าน ในสถานการณ์ส่วนใหญ่ผลประโยชน์เหล่านี้จะมีมากกว่าค่าใช้จ่ายเพิ่มเติมในการสละเวลาเพิ่มเติมเพื่อแยกสิ่งต่างๆออกจากกัน
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:])
อันแรกเปลี่ยนอินพุตซึ่งไม่ดีเว้นแต่ว่าควรจะเป็น คุณสามารถแก้ไขได้โดยการทำสำเนาของรายการที่กำหนดและดำเนินการแทน
การจัดรูปแบบสตริงไม่จำเป็นเนื่องจากค่าอยู่เสมอ2, 2หรือ(1, 1)ตามลำดับ ยังช่วยให้เรามีโอกาสสำหรับรูปแบบการเขียนที่ดีการเขียนตัวเลขเป็นคำไม่ใช่ตัวเลข ใช่ฉันรู้ว่าตัวกำหนดปัญหาต้องการตัวเลขดังนั้นจึงเป็นความผิดของพวกเขาไม่ใช่ของคุณ แค่พูด. คุณสามารถโต้แย้งได้ว่าของคุณนั้นกว้างกว่าในกรณีที่มีการใช้ลูกเต๋ามากกว่า แต่คุณควรใช้เอกพจน์ / พหูพจน์อย่างเหมาะสมเพื่อหลีกเลี่ยงการพูดว่า "ผู้โจมตีสูญเสีย 5 กองทัพ"
สุดท้ายฉันจะเลือกข้าง นั่นคือนับสถิติด้านเดียวเท่านั้น ฉันจะไปกับผู้โจมตีเพราะนั่นคือฝ่ายที่ "ใช้งานอยู่" (ตรงข้ามกับการป้องกันเป็นปฏิกิริยา )
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.'
ถ้านี่เป็น PR จริงฉันจะเน้น*looseตัวแปรตัวใดตัวหนึ่งและถามว่าควรอ่านloseหรือไม่
ฉันชอบแนวทางที่สองของคุณมาก ในขณะที่อ่านมันฉันรู้สึกว่าโค้ดบรรทัดถัดไปกำลังทำในสิ่งที่ฉันคาดหวังว่ามันจะเป็นจริงและฉันไม่จำเป็นต้องใช้ความจำทางจิตมากนักในขณะที่อ่านมันเหมือนกับที่ฉันทำตามแนวทางแรกของคุณคอยติดตามสิ่งเหล่านั้นทั้งหมด ตัวแปรที่ตั้งค่าเป็นศูนย์ วิธีหนึ่งที่คุณอาจพิจารณาปรับปรุงได้คือการลบa_scrและการd_scrเริ่มต้นและใช้ a sum()ในการคำนวณเช่น:
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 ของฟังก์ชันและการส่งคืนสตริงแทนที่จะเป็นค่า แต่สิ่งเหล่านี้ครอบคลุมอย่างดีในคำตอบอื่น ๆ
แก้ไข: สาธิตวิธีใช้การทดสอบ
ส่วนนี้ครอบคลุมอีกวิธีหนึ่งในการตรวจสอบให้แน่ใจว่าการต่อสู้ตัวอย่างของคุณดำเนินไปอย่างถูกต้องอย่างเป็นระบบโดยใช้ pytest มันแสดงให้เห็นว่าทำไมคำแนะนำอื่น ๆ ของคำตอบอื่น ๆ เพื่อให้การพิมพ์ออกจากฟังก์ชันช่วยให้เขียนโค้ดได้ง่ายขึ้น
ขั้นตอนที่ 1.
ให้ฟังก์ชันหลักของคุณเพียงแค่ส่งคืนa_scrและd_scrและใส่ตรรกะการพิมพ์ในสคริปต์หลัก:
# 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))
เมื่อคุณรันสคริปต์คุณจะได้รับลักษณะการทำงานเหมือนเดิม:
$ python riskgame.py
Attacker loses 2 armies.
ขั้นตอนที่ 2.
ในไดเร็กทอรีเดียวกันให้สร้างtest_wins_and_losses.pyและสร้างการทดสอบบางอย่าง:
# 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
สังเกตว่าฉันใช้ค่าเดียวกับที่คุณใส่ไว้ในโพสต์ต้นฉบับ แต่คุณสามารถมีเกมตัวอย่างที่คุณต้องการได้ ตามหลักการแล้วคุณจะมีจำนวนมากครอบคลุมกรณีการใช้งานให้มากที่สุดเท่าที่จะทำได้
ขั้นตอนที่ 3.
ติดตั้ง pytest หากคุณยังไม่ได้ทำ
$ pip install pytest
ขั้นตอนที่ 4.
เรียกใช้!
$ 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 ===============================
ความคิดคือตอนนี้คุณสามารถเปลี่ยนรหัสของคุณและเวลาที่คุณทำคุณก็สามารถพิมพ์ทุกpytestที่บรรทัดคำสั่งทุกอย่างที่ยืนยันจะยังคงทำงานในแบบที่คุณคาดหวัง ตัวอย่างเช่นหากเราทำผิดพลาดก่อนหน้านี้และเปลี่ยนบรรทัดเป็น
d_scr = sum([i < j for i, j in zip(a,b)])
และทำการทดสอบเราจะได้รับ:
$ 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 ================================================================
ขอให้สนุกกับการทดสอบ!