Trò chơi mạo hiểm Python
Tôi đã giải quyết một câu hỏi trò chơi Rủi ro (http://www.pyschools.com/quiz/view_question/s3-q12) theo hai cách, nhưng vì tôi hiện đang học Python, tôi chỉ tự hỏi làm thế nào để đánh giá các phương pháp giải quyết câu hỏi này của tôi. Giống như một trong những cách này thực sự là một phương pháp tốt hơn, hiệu quả và hiệu quả để giải quyết câu hỏi này? Hoặc có thể có một cách tiếp cận tốt hơn những gì tôi đã làm?
Đây là hướng dẫn trò chơi: "Trong trò chơi Risk board, có tình huống người tấn công tung 3 viên xúc xắc trong khi người phòng thủ tung 2 viên xúc xắc. Để xác định kết quả, người chơi sẽ so sánh số chết cao nhất của mỗi người chơi, tiếp theo là số chết cao nhất tiếp theo . Đối với mỗi trường hợp, quân của bên tấn công phải cao hơn quân của bên phòng thủ để thắng. Bên thua sẽ mất 1 quân trong mỗi trường hợp. "
Đây là mẫu của hàm và câu lệnh trả về:
>>> 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.'
Phương pháp 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])
Phương pháp 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])
Trả lời
Triển khai tốt cho cả hai phương pháp, một vài gợi ý:
- Trường hợp lạc đà hoặc dấu gạch dưới. Hàm
RiskGamesử dụng ký hiệu dạng hoa camel nhưng các biến sử dụng ký hiệu gạch dưới (a_score). Tốt hơn là chỉ sử dụng một ký hiệu. Nói chung, dấu gạch dưới được ưu tiên trong Python. - Các biến không được sử dụng :
a_scorevàd_scoretrong Phương pháp 1. - Tên biến có thể được cải thiện :
a_scrcó thể đổi tên thànhattacker_score. Câu lệnh nàya=max(attacker)có thểattacker_max_number=max(attacker)giống hoặc tương tự. Ngay cả khi nó dài hơn, cũng làm cho mã dễ đọc hơn. - Trả về kết quả thay vì một chuỗi mà con người có thể đọc được, việc sử dụng lại và kiểm tra dễ dàng hơn. Ví dụ thay vì:
trả về kết quả trực tiếp: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) #...
Phương pháp nào hiệu quả hơn?
Không có vấn đề gì về các buổi biểu diễn với các yêu cầu của tối đa ba phần tử cho danh sách đầu vào. Trên thực tế, như @Jasmijn đã chỉ ra trong các nhận xét, với điều kiện 1 <= len(attacker) <= 3và 1 <= len(defender) <= 2độ phức tạp về thời gian của cả hai phương thức là \$O(1)\$.
Nếu danh sách đầu vào có nhiều phần tử và danh sách của kẻ tấn công lớn hơn danh sách của kẻ bảo vệ, tôi sẽ nói Phương pháp 2 nhanh hơn. Độ phức tạp về thời gian của Phương pháp 1 sẽ là \$O(d*a)\$ở đâu \$d\$là độ dài của danh sách hậu vệ và \$a\$là độ dài của danh sách kẻ tấn công. Ngay cả khi danh sách co lại ở mỗi lần lặp lại, chúng ta có thể nói rằng đối với các đầu vào lớn. Phương pháp 2 sẽ là \$O(a*log(a))\$, giả sử rằng zip()hàm chạy trong \$O(d)\$và sắp xếp danh sách của kẻ tấn công sẽ mất \$O(a*log(a))\$.
Bạn đã có một số câu trả lời hay rồi. Thay vì tập trung vào chi tiết mã của bạn, tôi sẽ đưa ra một số nhận xét về cách tiếp cận thiết kế của một chương trình đầy đủ. Tôi không biết tình hình chính xác của bạn, nhưng khi bạn đang học, các chương trình dòng lệnh là một tiêu điểm tốt vì nhiều lý do thực tế khác nhau, vì vậy tôi sẽ sử dụng điều đó làm minh họa.
Lõi chức năng và vỏ mệnh lệnh . Càng nhiều càng tốt, hãy tách chương trình của bạn thành hai loại hàm: (A) những hàm không làm gì khác ngoài việc lấy dữ liệu và trả về dữ liệu (cốt lõi); và (B) những chất có tác dụng phụ (vỏ). Điều gì được coi là một tác dụng phụ? Nhiều thứ, nhưng in ấn và ngoại lệ là hai trong số những điều phổ biến nhất. Ý tưởng cốt lõi ở đây là hầu hết chương trình của bạn (và đặc biệt là các chi tiết về thuật toán, tính toán của nó) nên nằm trong lõi chức năng. Lớp vỏ bên ngoài liên quan đến việc in ấn và tương tác phải càng mỏng và càng không có thuật toán càng tốt. Trong ví dụ dưới đây, main()là lớp vỏ bên ngoài và nó thật kỳ quặc từ góc độ thuật toán - không hơn gì một thứ tầm thường if-else. Nếu bạn muốn tìm hiểu thêm về những ý tưởng này, bài nói chuyện hay nhất mà tôi đã thấy về sự phân tách lõi-vỏ này là từ Gary Bernhardt .
Hãy phá vỡ nó . Khi bạn đã nghĩ đến sự tách biệt cơ bản đó, hãy bắt đầu phân chia các tính toán cần thiết thành các phần nhỏ hợp lý. Nỗ lực này là một phần nghệ thuật, một phần khoa học. Bạn nên theo đuổi phân rã bao xa tùy thuộc vào bối cảnh. Ví dụ dưới đây đưa nó đi khá xa. Chương trình càng phức tạp và tiền cược càng cao, bạn càng muốn thực hiện nỗ lực phân hủy nghiêm túc hơn. Trong các tình huống đơn giản hơn, các phím tắt khác nhau vẫn tốt. Nhưng khi chương trình quan trọng, bạn cần phải viết các bài kiểm tra cho nó và yêu cầu về khả năng kiểm tra thường sẽ khiến bạn phải phân rã nhiều hơn (có thể khó kiểm tra các chức năng thực hiện quá nhiều việc cùng một lúc và việc kiểm tra sẽ rất đau đầu. các chức năng có tác dụng phụ chính).
Chú ý đến sự đơn giản nổi lên . Các chức năng cuối cùng nhỏ gọn, dễ hiểu, mô tả nhanh trong phần bình luận cho người đọc. Trong hầu hết các tình huống, những lợi ích đó lớn hơn (thường là đáng kể) chi phí bổ sung của việc dành thêm thời gian để phá vỡ mọi thứ.
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:])
Cái đầu tiên thay đổi đầu vào, điều này không tốt, trừ khi nó được yêu cầu. Bạn có thể khắc phục điều đó bằng cách tạo bản sao của các danh sách đã cho và làm việc với chúng thay thế.
Định dạng chuỗi là không cần thiết, vì các giá trị luôn 2, 2hoặc (1, 1), tương ứng. Cũng cho chúng ta cơ hội để có phong cách viết tốt, viết các số dưới dạng chữ, không phải chữ số. Có, tôi nhận ra rằng các chữ số được yêu cầu bởi người đặt vấn đề nên đó là lỗi của họ, không phải của bạn. Chỉ nói. Bạn có thể lập luận rằng của bạn là tổng quát hơn, trong trường hợp nhiều xúc xắc được sử dụng hơn, nhưng bạn cũng nên sử dụng số ít / số nhiều một cách thích hợp, để tránh nói những điều như "Kẻ tấn công mất 5 quân".
Cuối cùng, tôi sẽ chọn một bên. Đó là, chỉ tính thống kê của một bên. Tôi sẽ đi với kẻ tấn công, vì đó là bên "chủ động" (trái ngược với phòng thủ là phản ứng ).
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.'
Nếu đây là một bài PR thực sự, tôi sẽ đánh dấu một trong các *loosebiến số và hỏi Có nên đọclose không?
Tôi thực sự thích cách tiếp cận thứ hai của bạn. Trong khi đọc nó, tôi cảm thấy như dòng mã tiếp theo đang thực hiện chính xác những gì tôi dự đoán là nó sẽ làm và tôi không phải sử dụng nhiều trí nhớ trong khi đọc nó như cách tôi làm cách tiếp cận đầu tiên của bạn, theo dõi tất cả những các biến đã được đặt thành 0. Một cách bạn có thể cân nhắc để cải thiện nó là xóa a_scrvà d_scrkhởi tạo và sử dụng a sum()để tính toán chúng, chẳng hạn như:
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])
Tôi có những dè dặt tương tự về cách viết hoa trong Pascal của các hàm và các chuỗi trả về thay vì các giá trị, nhưng chúng được đề cập kỹ trong các câu trả lời khác.
Chỉnh sửa: Trình bày cách sử dụng các bài kiểm tra
Phần này bao gồm một cách khác để đảm bảo các trận đánh ví dụ của bạn chạy chính xác theo cách có hệ thống bằng cách sử dụng pytest. Nó minh họa lý do tại sao một số gợi ý khác của các câu trả lời khác để lấy việc in ra khỏi hàm làm cho việc viết mã dễ dàng hơn.
Bước 1.
Có chức năng chính của bạn chỉ cần trả lại a_scrvà d_scrvà đặt logic in trong tập lệnh chính:
# 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))
Khi bạn thực thi tập lệnh, bạn sẽ nhận được hành vi giống như trước đây:
$ python riskgame.py
Attacker loses 2 armies.
Bước 2.
Trong cùng một thư mục, hãy tạo test_wins_and_losses.pyvà tạo một số thử nghiệm:
# 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
Lưu ý rằng tôi đã sử dụng các giá trị giống như bạn đặt trong bài viết gốc, nhưng bạn có thể có bất kỳ trò chơi mẫu nào bạn muốn trong đó. Lý tưởng nhất là bạn sẽ có rất nhiều, bao gồm nhiều trường hợp sử dụng nhất có thể.
Bước 3.
Cài đặt pytest nếu bạn chưa có.
$ pip install pytest
Bước 4.
Chạy nó!
$ 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 ===============================
Ý tưởng là bây giờ bạn có thể thay đổi mã của mình và bất cứ khi nào bạn làm vậy, bạn chỉ cần gõ pytestvào dòng lệnh để xác nhận mọi thứ vẫn hoạt động theo cách bạn mong đợi . Ví dụ: nếu chúng tôi mắc lỗi mà tôi đã mắc phải trước đó và thay đổi dòng thành
d_scr = sum([i < j for i, j in zip(a,b)])
và chạy các bài kiểm tra, chúng tôi nhận được:
$ 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 ================================================================
Chúc bạn thử nghiệm vui vẻ!