ทบทวน Rock Paper Scissors ใน Python

ทบทวน Rock Paper Scissors ใน Python

เมื่อคุณเรียนรู้การเขียนโปรแกรมเป็นครั้งแรก คุณมองหา (หรืออาจได้รับมอบหมาย) โครงการที่ส่งเสริมแนวคิดพื้นฐาน แต่บ่อยแค่ไหนที่คุณเมื่อคุณได้รับความรู้และประสบการณ์มากขึ้น ทบทวนโครงการเริ่มต้นเหล่านั้นจากมุมมองของโปรแกรมเมอร์ขั้นสูง?

ในบทความนี้ฉันต้องการทำอย่างนั้น ฉันต้องการทบทวนโครงการสำหรับผู้เริ่มต้นทั่วไป การนำเกม “Rock Paper Scissors” ไปใช้ใน Python ด้วยความรู้ที่ฉันได้รับจากประสบการณ์การเขียนโปรแกรม Python เกือบแปดปี

🎓
คุณเป็นผู้เริ่มต้นหรือไม่? อย่าคลิกไป! คุณยังสามารถเรียนรู้อะไรมากมายจากบทความนี้

สารบัญ

กฎของ “กรรไกรกระดาษหิน”

ก่อนดำดิ่งลงไปในโค้ด เรามาตั้งฉากโดยสรุปวิธีการเล่น “Rock Paper Scissors” กันก่อน ผู้เล่นสองคนแต่ละคนเลือกหนึ่งในสามรายการ: หิน กระดาษ หรือกรรไกร ผู้เล่นเปิดเผยการเลือกของพวกเขาให้กันและกันและผู้ชนะจะถูกกำหนดโดยกฎต่อไปนี้:

  1. ร็อคเต้นกรรไกร
  2. กรรไกรตีกระดาษ
  3. กระดาษเต้นร็อค

เมื่อโตขึ้น ฉันกับเพื่อนใช้ “กรรไกรตัดกระดาษ” เพื่อแก้ปัญหาทุกประเภท ใครจะได้เล่นเป็นคนแรกในวิดีโอเกมเล่นคนเดียว? ใครได้รับโซดากระป๋องสุดท้าย? ใครต้องไปรับเรื่องที่เราเพิ่งทำไป? ของสำคัญ.

ความต้องการ

มาร่างข้อกำหนดบางประการสำหรับการนำไปปฏิบัติกัน แทนที่จะสร้างเกมที่เต็มรูปแบบ ให้เน้นที่การเขียนฟังก์ชันที่เรียกว่า play() ที่ยอมรับสองอาร์กิวเมนต์สตริง — ตัวเลือกของ "rock" , "paper" หรือ "scissors" ที่ผู้เล่นแต่ละคนเลือก – และส่งคืนสตริงที่ระบุ ผู้ชนะ (เช่น "paper wins" ) หรือหากผลการแข่งขันเท่ากัน (เช่น "tie" )

ต่อไปนี้คือตัวอย่างวิธีการเรียก play() และสิ่งที่ส่งคืน:

 >>> play("rock", "paper") 'rock wins' >>> play("scissors", "paper") 'scissors wins' >>> play("paper", "paper") 'tie'

หากอาร์กิวเมนต์หนึ่งหรือทั้งสองอาร์กิวเมนต์ไม่ถูกต้อง ซึ่งหมายความว่าไม่ใช่หนึ่งใน "rock" , "paper" หรือ "scissors" ดังนั้น play() ควรมีข้อยกเว้นบางประการ

play() ควรจะ สับเปลี่ยน ด้วย นั่นคือ play("rock", "paper") ควรคืนค่าเหมือนกับ play("paper", "rock")

โซลูชัน “สำหรับผู้เริ่มต้น”

ในการกำหนดพื้นฐานสำหรับการเปรียบเทียบ ให้พิจารณาว่าผู้เริ่มต้นใช้งานฟังก์ชัน play() ได้อย่างไร หากผู้เริ่มต้นคนนี้เป็นเหมือนฉันเมื่อตอนที่ฉันเรียนการเขียนโปรแกรมครั้งแรก พวกเขาอาจจะเริ่มเขียนประโยค if จำนวนมาก:

 def play(player1_choice, player2_choice): if player1_choice == "rock": if player2_choice == "rock": return "tie" elif player2_choice == "paper": return "paper wins" elif player2_choice == "scissors": return "rock wins" else: raise ValueError(f"Invalid choice: {player2_choice}") elif player1_choice == "paper": if player2_choice == "rock": return "paper wins" elif player2_choice == "paper": return "tie" elif player2_choice == "scissors": return "rock wins" else: raise ValueError(f"Invalid choice: {player2_choice}") elif player1_choice == "scissors": if player2_choice == "rock": return "rock wins" elif player2_choice == "paper": return "scissors wins" elif player2_choice == "scissors": return "tie" else: raise ValueError(f"Invalid choice: {player2_choice}") else: raise ValueError(f"Invalid choice: {player1_choice}")

พูดอย่างเคร่งครัดไม่มีอะไร ผิดปกติ กับรหัสนี้ มันทำงานโดยไม่มีข้อผิดพลาดและตรงตามข้อกำหนดทั้งหมด นอกจากนี้ยังคล้ายกับการใช้งานระดับสูงจำนวนมากสำหรับการค้นหาของ Google “rock paper scissors python”

โปรแกรมเมอร์ที่มีประสบการณ์จะรับรู้ถึงกลิ่นโค้ดจำนวนหนึ่งได้อย่างรวดเร็ว โดยเฉพาะอย่างยิ่ง โค้ดซ้ำกันและมีเส้นทางการดำเนินการที่เป็นไปได้มากมาย

โซลูชันขั้นสูง #1

วิธีหนึ่งในการใช้ “Rock Paper Scissors” จากมุมมองขั้นสูงคือการใช้ประโยชน์จากประเภทพจนานุกรมของ Python พจนานุกรมสามารถแมปไอเท็มกับไอเท็มที่พวกเขาเอาชนะได้ตามกฎของเกม

เรียกพจนานุกรมนี้ว่า loses_to (การตั้งชื่อยากนะ):

 loses_to = { "rock": "scissors", "paper": "rock", "scissors": "paper", }

loses_to จัดเตรียม API อย่างง่ายสำหรับกำหนดว่ารายการใดจะสูญเสียไปยังรายการอื่น:

 >>> loses_to["rock"] 'scissors' >>> loses_to["scissors"] 'paper'

พจนานุกรมมีประโยชน์สองสามประการ คุณสามารถใช้เพื่อ:

  1. ตรวจสอบรายการที่เลือกโดยตรวจสอบการเป็นสมาชิกหรือเพิ่ม KeyError
  2. กำหนดผู้ชนะโดยตรวจสอบว่าค่าสูญเสียไปยังคีย์ที่เกี่ยวข้องหรือไม่

เมื่อคำนึงถึงสิ่งนี้ ฟังก์ชัน play() สามารถเขียนได้ดังนี้:

 def play(player1_choice, player2_choice): if player2_choice == loses_to[player1_choice]: return f"{player1_choice} wins" if player1_choice == loses_to[player2_choice]: return f"{player2_choice} wins" if player1_choice == player2_choice: return "tie"

ในเวอร์ชันนี้ play() ใช้ประโยชน์จาก KeyError ในตัวที่สร้างโดยพจนานุกรม loses_to เมื่อพยายามเข้าถึงคีย์ที่ไม่ถูกต้อง สิ่งนี้จะตรวจสอบตัวเลือกของผู้เล่นได้อย่างมีประสิทธิภาพ ดังนั้นหากผู้เล่นคนใดคนหนึ่งเลือกไอเท็มที่ไม่ถูกต้อง เช่น "lizard" หรือ 1234play() ทำให้เกิด KeyError :

 >>> play("lizard", "paper") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in play KeyError: 'lizard'

แม้ว่า KeyError จะไม่มีประโยชน์เท่ากับ ValueError ที่มีข้อความอธิบาย แต่ก็ยังสามารถทำงานให้เสร็จได้

ฟังก์ชั่น play() ใหม่นั้นง่ายกว่าฟังก์ชั่นดั้งเดิมมาก แทนที่จะจัดการกับกรณีที่ชัดเจนจำนวนมาก มีเพียงสามกรณีที่ต้องตรวจสอบ:

  1. player2_choice แพ้ player1_choice
  2. player1_choice แพ้ player2_choice
  3. player1_choice และ player2_choice เหมือนกัน

มีคดีที่สี่ซ่อนอยู่ อย่างไรก็ตาม ที่คุณเกือบจะต้องเหล่เพื่อดู กรณีนั้นเกิดขึ้นเมื่อไม่มีกรณีอื่นใดอีกสามกรณีเป็นจริง ในกรณีนี้ play() จะส่งคืนค่า None

แต่… กรณีนี้จะเกิดขึ้นได้จริงหรือ? จริงๆแล้วไม่ มันไม่สามารถ ตามกฎของเกม ถ้าผู้เล่นที่ 1 ไม่แพ้ผู้เล่นที่ 2 และ ผู้เล่นที่ 2 ไม่แพ้ผู้เล่นที่ 1 แสดงว่าผู้เล่นทั้งสองต้องเลือกรายการเดียวกัน

กล่าวอีกนัยหนึ่ง เราสามารถลบส่วนสุดท้าย if บล็อกจาก play() และเพียงแค่ return "tie" หากไม่มีอีกสองอัน if บล็อกดำเนินการ:

 def play(player1_choice, player2_choice): if player2_choice == loses_to[player1_choice]: return f"{player1_choice} wins" if player1_choice == loses_to[player2_choice]: return f"{player2_choice} wins" return "tie"

เราได้ทำการแลกเปลี่ยน เราได้เสียสละความชัดเจน — ฉันขอยืนยันว่าจำเป็นต้องมีภาระด้านความรู้ความเข้าใจมากขึ้นเพื่อทำความเข้าใจว่าฟังก์ชัน play() ด้านบนทำงานอย่างไรเมื่อเปรียบเทียบกับเวอร์ชัน “เริ่มต้น” เพื่อลดฟังก์ชันและหลีกเลี่ยงสถานะที่ไม่สามารถเข้าถึงได้

การแลกเปลี่ยนนี้คุ้มค่าหรือไม่? ฉันไม่รู้. ความบริสุทธิ์เอาชนะการปฏิบัติจริงหรือไม่?

โซลูชันขั้นสูง #2

โซลูชันก่อนหน้านี้ใช้งานได้ดี สามารถอ่านได้ และ สั้นกว่าโซลูชัน “สำหรับผู้เริ่มต้น” มาก แต่ก็ไม่ค่อยยืดหยุ่นเท่าไหร่ กล่าวคือ ไม่สามารถจัดการกับรูปแบบต่างๆ ของ “Rock Paper Scissors” ได้โดยไม่ต้องเขียนตรรกะบางอย่างใหม่

ตัวอย่างเช่น มีรูปแบบที่เรียกว่า “Rock Paper Scissors Lizard Spock” ที่มีชุดกฎที่ซับซ้อนมากขึ้น:

  1. ร็อคทุบกรรไกรและจิ้งจก
  2. กระดาษเต้นร็อคและสป็อค
  3. กรรไกรตีกระดาษและจิ้งจก
  4. จิ้งจกตีสป็อคและกระดาษ
  5. สป็อคตีกรรไกรและร็อค

คุณจะปรับโค้ดเพื่อจัดการกับรูปแบบนี้ได้อย่างไร

ขั้นแรก แทนที่ค่าสตริงในพจนานุกรม loses_to ด้วยชุด Python แต่ละชุดประกอบด้วยรายการทั้งหมดที่สูญเสียไปยังคีย์ที่เกี่ยวข้อง นี่คือลักษณะของ loses_to เวอร์ชันนี้เมื่อใช้กฎ “Rock Paper Scissors” ดั้งเดิม:

 loses_to = { "rock": {"scissors"}, "paper": {"rock"}, "scissors": {"paper"}, }

ทำไมต้องชุด? เพราะเราสนใจแต่ของ ที่ เสียให้กับคีย์ที่ให้มาเท่านั้น เราไม่สนใจเกี่ยวกับ ลำดับ ของรายการเหล่านั้น

ในการปรับ play() เพื่อจัดการกับพจนานุกรม loses_to ใหม่ สิ่งที่คุณต้องทำคือแทนที่ == ด้วย in เพื่อใช้การตรวจสอบสมาชิกแทนการตรวจสอบความเท่าเทียมกัน:

 def play(player1_choice, player2_choice): # vv--- replace == with in if player2_choice in loses_to[player1_choice]: return f"{player1_choice} wins" # vv--- replace == with in if player1_choice in loses_to[player2_choice]: return f"{player2_choice} wins" return "tie"

ใช้เวลาสักครู่เพื่อเรียกใช้รหัสนี้และตรวจสอบว่าทุกอย่างยังใช้งานได้

ตอนนี้แทนที่ loses_to ด้วยพจนานุกรมที่ใช้กฎสำหรับ “Rock Paper Scissors Lizard Spock” นี่คือสิ่งที่ดูเหมือน:

 loses_to = { "rock": {"scissors", "lizard"}, "paper": {"rock", "spock"}, "scissors": {"paper", "lizard"}, "lizard": {"spock", "paper"}, "spock": {"scissors", "rock"}, }

ฟังก์ชันใหม่ play() ทำงานร่วมกับกฎใหม่เหล่านี้ได้อย่างไม่มีที่ติ:

 >>> play("rock", "paper") 'paper wins' >>> play("spock", "lizard") 'lizard wins' >>> play("spock", "spock") 'tie'

ในความคิดของฉัน นี่เป็นตัวอย่างที่ดีของพลังของการเลือกโครงสร้างข้อมูลที่ถูกต้อง ด้วยการใช้ชุดเพื่อแสดงรายการทั้งหมดที่สูญเสียไปยังคีย์ในพจนานุกรม loses_to และแทนที่ == ด้วย in คุณได้สร้างวิธีแก้ปัญหาทั่วไปมากขึ้นโดยไม่ต้องเพิ่มโค้ดแม้แต่บรรทัดเดียว

โซลูชันขั้นสูง #3

ลองย้อนกลับไปและใช้แนวทางที่แตกต่างออกไปเล็กน้อย แทนที่จะค้นหารายการในพจนานุกรมเพื่อตัดสินผู้ชนะ เราจะสร้างตารางข้อมูลที่เป็นไปได้ทั้งหมดและผลลัพธ์

คุณยังคงต้องการบางสิ่งเพื่อแสดงถึงกฎของเกม ดังนั้นเรามาเริ่มด้วยคำ loses_to จากวิธีแก้ปัญหาก่อนหน้านี้:

 loses_to = { "rock": {"scissors"}, "paper": {"rock"}, "scissors": {"paper"}, }

ถัดไป เขียนฟังก์ชัน build_results_table() ที่ใช้พจนานุกรมกฎ เช่น loses_to และส่งคืนพจนานุกรมใหม่ที่จับคู่สถานะกับผลลัพธ์ ตัวอย่างเช่น นี่คือสิ่งที่ build_results_table() ควรส่งคืนเมื่อถูกเรียกด้วย loses_to เป็นอาร์กิวเมนต์:

 >>> build_results_table(loses_to) { {"rock", "scissors"}: "rock wins", {"paper", "rock"}: "paper wins", {"scissors", "paper"}: "scissors wins", {"rock", "rock"}: "tie", {"paper", "paper"}: "tie", {"scissors", "scissors"}: "tie", }

ถ้าคุณคิดว่ามีบางอย่างดูเหมือนอยู่ที่นั่น คุณคิดถูก มีสองสิ่งผิดปกติในพจนานุกรมนี้:

  1. ชุดเช่น {"rock", "rock"} ไม่มีอยู่จริง ชุดต้องไม่มีองค์ประกอบซ้ำ ในสถานการณ์จริง ชุดนี้จะดูเหมือน {"rock"} คุณไม่จำเป็นต้องกังวลเกี่ยวกับเรื่องนี้มากเกินไป ฉันเขียนเซตเหล่านั้นด้วยสององค์ประกอบเพื่อให้ชัดเจนว่าสถานะเหล่านั้นเป็นตัวแทนของอะไร
  2. คุณไม่สามารถใช้ชุดเป็นคีย์พจนานุกรม แต่เรา ต้องการ ใช้เซตเพราะมันดูแลการสลับสับเปลี่ยนให้เราโดยอัตโนมัติ นั่นคือ {"rock", "paper"} และ {"paper", "rock"} ประเมินว่าเท่ากันและควรส่งคืนผลลัพธ์เดียวกันเมื่อค้นหา

วิธีแก้ไขคือใช้ ประเภท frozenset ในตัวของ Python เช่นเดียวกับชุด frozensets การตรวจสอบสมาชิกภาพ และเปรียบเทียบเท่ากับ set อื่นหรือชุด frozenset เฉพาะในกรณีที่ทั้งสองชุดมีสมาชิกเหมือนกัน อย่างไรก็ตาม ต่างจากชุดมาตรฐาน อินสแตนซ์ frozenset จะไม่เปลี่ยนรูป เป็นผลให้สามารถใช้เป็นคีย์พจนานุกรมได้

ในการใช้งาน build_results_table() คุณสามารถวนซ้ำแต่ละคีย์ในพจนานุกรม loses_to และสร้างอินสแตนซ์ frozenset สำหรับแต่ละค่าสตริงในชุดที่สอดคล้องกับคีย์:

 def build_results_table(rules): results = {} for key, values in rules.items(): for value in values: state = frozenset((key, value)) result = f"{key} wins" results[state] = result return results

สิ่งนี้ทำให้คุณได้ประมาณครึ่งทาง:

 >>> build_results_table(loses_to) {frozenset({'rock', 'scissors'}): 'rock wins', frozenset({'paper', 'rock'}): 'paper wins', frozenset({'paper', 'scissors'}): 'scissors wins'}

แม้ว่ารัฐที่ส่งผลให้เกิดการเสมอกันจะไม่ครอบคลุม ในการเพิ่มสิ่งเหล่านี้ คุณต้องสร้างอินสแตนซ์ frozenset สำหรับแต่ละคีย์ในพจนานุกรม rules ที่แมปกับสตริง "tie" :

 def build_results_table(rules): results = {} for key, values in rules.items(): # Add the tie states results[frozenset((key,))] = "tie" # <-- New # Add the winning states for value in values: state = frozenset((key, value)) result = f"{key} wins" results[state] = result return results

ตอนนี้ค่าที่ส่งคืนโดย build_results_table() ต้อง:

 >>> build_results_table(loses_to) {frozenset({'rock'}): 'tie', frozenset({'rock', 'scissors'}): 'rock wins', frozenset({'paper'}): 'tie', frozenset({'paper', 'rock'}): 'paper wins', frozenset({'scissors'}): 'tie', frozenset({'paper', 'scissors'}): 'scissors wins'}

ทำไมต้องผ่านปัญหาทั้งหมดนี้? build_results_table() ดูซับซ้อนกว่าฟังก์ชัน play() จากโซลูชันก่อนหน้านี้

คุณไม่ผิด แต่ฉันต้องการชี้ให้เห็นว่ารูปแบบนี้มีประโยชน์มาก หากมีสถานะในโปรแกรมจำนวนจำกัด บางครั้งคุณอาจเห็นความเร็วที่เพิ่มขึ้นอย่างมากโดยการคำนวณผลลัพธ์ล่วงหน้าสำหรับสถานะเหล่านั้นทั้งหมด นี่อาจเกินความสามารถสำหรับบางสิ่งที่เรียบง่ายเช่น “Rock Paper Scissors” แต่อาจสร้างความแตกต่างอย่างมากในสถานการณ์ที่มีรัฐหลายแสนหรือหลายล้านรัฐ

สถานการณ์หนึ่งในชีวิตจริงที่แนวทางประเภทนี้สมเหตุสมผลคือ อัลกอริธึม Q-learning ที่ ใช้ในแอปพลิเคชันการเรียนรู้แบบเสริมกำลัง ในอัลกอริธึมนั้น ตารางสถานะ — ตาราง Q — จะถูกรักษาไว้ซึ่งแมปแต่ละสถานะกับชุดของความน่าจะเป็นสำหรับการกระทำที่กำหนดไว้ล่วงหน้าบางอย่าง เมื่อตัวแทนได้รับการฝึกอบรมแล้ว จะสามารถเลือกและดำเนินการตามความน่าจะเป็นสำหรับสถานะที่สังเกตได้ จากนั้นจึงดำเนินการตามนั้น

บ่อยครั้ง ตารางแบบเดียวกับที่สร้างโดย build_results_table() จะถูกคำนวณและจัดเก็บไว้ในไฟล์ เมื่อโปรแกรมทำงาน ตารางที่คำนวณไว้ล่วงหน้าจะถูกโหลดลงในหน่วยความจำแล้วใช้โดยแอปพลิเคชัน

ดังนั้น เมื่อคุณมีฟังก์ชันที่สามารถสร้างตารางผลลัพธ์ได้แล้ว ให้กำหนดตารางสำหรับ loses_to ให้กับตัวแปร outcomes :

 outcomes = build_results_table(loses_to)

ตอนนี้คุณสามารถเขียนฟังก์ชัน play() ที่ค้นหาสถานะในตาราง outcomes ตามอาร์กิวเมนต์ที่ส่งไปเล่นแล้วส่งคืนผลลัพธ์:

 def play(player1_choice, player2_choice): state = frozenset((player1_choice, player2_choice)) return outcomes[state]

play() เวอร์ชันนี้เรียบง่ายอย่างเหลือเชื่อ โค้ดแค่สองบรรทัด! คุณสามารถเขียนเป็นบรรทัดเดียวได้หากต้องการ:

 def play(player1_choice, player2_choice): return outcomes[frozenset((player1_choice, player2_choice))]

โดยส่วนตัวแล้ว ฉันชอบเวอร์ชันสองบรรทัดมากกว่าเวอร์ชันบรรทัดเดียว

ฟังก์ชัน play() ใหม่ของคุณเป็นไปตามกฎของเกมและมีการสับเปลี่ยน:

 >>> play("rock", "paper") 'paper wins' >>> play("paper", "rock") 'paper wins'

play() ยังทำให้เกิด KeyError หากถูกเรียกด้วยตัวเลือกที่ไม่ถูกต้อง แต่ตอนนี้ข้อผิดพลาดมีประโยชน์น้อยกว่าเมื่อตั้งค่าคีย์ของพจนานุกรม outcomes :

 >>> play("lizard", "paper") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 21, in play return outcomes[state] KeyError: frozenset({'lizard', 'paper'})

อย่างไรก็ตาม ข้อผิดพลาดที่คลุมเครือไม่น่าจะเป็นปัญหา ในบทความนี้ คุณกำลังใช้งานฟังก์ชัน play() เท่านั้น ในการใช้งาน “Rock Paper Scissors” อย่างแท้จริง คุณน่าจะจับข้อมูลที่ผู้ใช้ป้อนและตรวจสอบว่าก่อนที่จะผ่านตัวเลือกที่ผู้ใช้เลือก play()

ดังนั้นการใช้งานนี้เร็วกว่าการติดตั้งก่อนหน้านี้มากน้อยเพียงใด นี่คือผลการจับเวลาบางส่วนเพื่อเปรียบเทียบประสิทธิภาพของการใช้งานต่างๆ โดยใช้ฟังก์ชันเวทย์มนตร์ %timeit ของ IPython play1() เป็นเวอร์ชันของ play() จากส่วน Advanced Solution #2 และ play2() เป็นเวอร์ชันปัจจุบัน:

 In [1]: %timeit play1("rock", "paper") 141 ns ± 0.0828 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each) In [2]: %timeit play2("rock", "paper") 188 ns ± 0.0944 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

ในกรณีนี้ โซลูชันที่ใช้ตารางผลลัพธ์จริง ๆ แล้ว ช้า กว่าการใช้งานครั้งก่อน ผู้ร้ายที่นี่คือบรรทัดที่แปลงอาร์กิวเมนต์ของฟังก์ชันเป็น frozenset ดังนั้น แม้ว่าการค้นหาพจนานุกรมจะรวดเร็ว และการสร้างตารางที่แมปสถานะกับผลลัพธ์สามารถปรับปรุง ประสิทธิภาพ ได้ แต่คุณต้องระวังเพื่อหลีกเลี่ยงการดำเนินการที่มีราคาแพงซึ่งอาจจบลงด้วยการปฏิเสธสิ่งที่คุณคาดหวังว่าจะได้รับ

บทสรุป

ฉันเขียนบทความนี้เป็นแบบฝึกหัด ฉันอยากรู้ว่าฉันจะเข้าถึงโครงการเริ่มต้นเช่น “Rock Paper Scissors” ใน Python ได้อย่างไร เพราะตอนนี้ฉันมีประสบการณ์มากมาย ฉันหวังว่าคุณจะพบว่ามันน่าสนใจ หากคุณมีแรงบันดาลใจในการทบทวนโครงการเริ่มต้นของคุณเอง ฉันคิดว่าฉันทำงานเสร็จแล้ว!

หากคุณทบทวนโครงการเริ่มต้นของคุณเองหรือหากคุณเคยทำมาแล้วในอดีต โปรดแจ้งให้เราทราบว่าผลเป็นอย่างไรในความคิดเห็น คุณได้เรียนรู้อะไรใหม่หรือไม่? โซลูชันใหม่ของคุณแตกต่างจากวิธีที่คุณเขียนเมื่อเป็นมือใหม่มากน้อยเพียงใด

อะไรเป็นแรงบันดาลใจให้บทความนี้?

Miguel Raz Guzmán Macedo เพาะเลี้ยงสัตว์น้ำจากโลกของ Julia ทำให้ฉันเข้าสู่ บล็อกโพสต์ โดย Mosè Giordano Mosè ใช้ประโยชน์จากกระบวนทัศน์การ จัดส่ง ที่หลากหลายของ Julia ในการเขียน “Rock Paper Scissors” ด้วยโค้ดที่น้อยกว่าสิบบรรทัด:

ทบทวน Rock Paper Scissors ใน Python

ฉันจะไม่ลงรายละเอียดว่าโค้ดของ Mosè ทำงานอย่างไร Python ไม่สนับสนุนแม้กระทั่งการจัดส่งหลายรายการตั้งแต่แกะกล่อง (แม้ว่าคุณสามารถใช้มันได้ด้วยความช่วยเหลือจาก แพ็คเกจ plum )

บทความของ Mosè ทำให้จิตใจของฉันหมุนวน และสนับสนุนให้ฉันทบทวน “Rock Paper Scissors” ใน Python เพื่อคิดว่าฉันจะเข้าถึงโครงการในรูปแบบที่ต่างไปจากเดิมได้อย่างไร

ขณะที่ฉันกำลังแก้ปัญหานี้ ฉันได้รับการเตือนถึงบทความที่ฉันตรวจสอบสำหรับ Real Python เมื่อนานมาแล้ว:

ทบทวน Rock Paper Scissors ใน Python

ปรากฎว่าสองวิธีแก้ปัญหาแรกที่ฉัน “คิดค้น” ในที่นี้คล้ายกับวิธีแก้ปัญหาที่ Chris Wilkerson ผู้เขียนบทความของ Real Python คิดขึ้นมา

โซลูชันของ Chris มีคุณสมบัติครบถ้วนมากขึ้น มันมีกลไกการเล่นเกมแบบโต้ตอบและแม้กระทั่งใช้ประเภท Enum ของ Python เพื่อแสดงรายการเกม นั่นต้องเป็นที่ที่ฉันได้ยินชื่อ “Rock Paper Scissors Lizard Spock” เป็นครั้งแรก


คุณสนุกกับบทความนี้หรือไม่? ติดตามข่าวสารล่าสุดเกี่ยวกับเนื้อหาทั้งหมดของฉัน เข้าใช้หลักสูตรของฉันก่อนใคร และรับเนื้อหาที่คัดสรรจากชุมชน Python และ Julia ส่งตรงไปยังกล่องจดหมายของคุณทุกวันศุกร์โดยสมัคร รับจดหมายข่าว Curious About Code รายสัปดาห์ของฉัน

วิธีที่ถูกต้องในการเปรียบเทียบ Floats ใน Python

วิธีที่ถูกต้องในการเปรียบเทียบ Floats ใน Python

ตัวเลขทศนิยมเป็นวิธีที่รวดเร็วและมีประสิทธิภาพในการจัดเก็บและทำงานกับตัวเลข แต่มีข้อผิดพลาดหลายอย่างที่ทำให้โปรแกรมเมอร์มือใหม่หลายคนต้องหยุดชะงัก บางทีอาจเป็นโปรแกรมเมอร์ที่มีประสบการณ์ด้วยเช่นกัน! ตัวอย่างคลาสสิกที่แสดงให้เห็นถึงข้อผิดพลาดของการลอยตัวมีลักษณะดังนี้:

 >>> 0.1 + 0.2 == 0.3 False

เห็นสิ่งนี้เป็นครั้งแรกอาจทำให้สับสนได้ แต่อย่าโยนคอมพิวเตอร์ของคุณลงในถังขยะ พฤติกรรมนี้ถูกต้อง!

บทความนี้จะแสดงให้คุณเห็นว่าเหตุใดข้อผิดพลาดทศนิยมแบบเดียวกับข้างต้นจึงเป็นเรื่องปกติ เหตุใดจึงสมเหตุสมผล และสิ่งที่คุณสามารถทำได้เพื่อจัดการกับข้อผิดพลาดเหล่านี้ใน Python

คอมพิวเตอร์ของคุณเป็นคนโกหก (เรียงลำดับ)

คุณเห็นแล้วว่า 0.1 + 0.2 ไม่เท่ากับ 0.3 แต่ความบ้าคลั่งไม่ได้หยุดอยู่แค่นั้น ต่อไปนี้คือตัวอย่างที่ทำให้สับสนมากขึ้น:

 >>> 0.2 + 0.2 + 0.2 == 0.6 False >>> 1.3 + 2.0 == 3.3 False >>> 1.2 + 2.4 + 3.6 == 7.2 False

ปัญหานี้ไม่ได้จำกัดอยู่เพียงการเปรียบเทียบความเท่าเทียมกัน:

 >>> 0.1 + 0.2 <= 0.3 False >>> 10.4 + 20.8 > 31.2 True >>> 0.8 - 0.1 > 0.7 True

เกิดอะไรขึ้น? คอมพิวเตอร์ของคุณโกหกคุณหรือไม่? ดูเหมือนว่าจะเป็นเช่นนั้น แต่มีอะไรมากกว่านั้นเกิดขึ้นภายใต้พื้นผิว

เมื่อคุณพิมพ์ตัวเลข 0.1 ลงในล่าม Python จะถูกเก็บไว้ในหน่วยความจำเป็นตัวเลขทศนิยม มีการแปลงที่เกิดขึ้นเมื่อสิ่งนี้เกิดขึ้น 0.1 เป็นทศนิยมในฐาน 10 แต่ตัวเลขทศนิยมถูกเก็บไว้ในไบนารี กล่าวอีกนัยหนึ่ง 0.1 จะถูกแปลงจากฐาน 10 เป็นฐาน 2

เลขฐานสองที่ได้อาจไม่ได้แสดงถึงตัวเลขฐาน 10 เดิมอย่างถูกต้อง 0.1 เป็นตัวอย่างหนึ่ง การแทนค่าไบนารีคือ \(0.0\overline{0011}\) นั่นคือ 0.1 เป็นทศนิยมซ้ำอนันต์เมื่อเขียนในฐาน 2 สิ่งเดียวกันนี้เกิดขึ้นเมื่อคุณเขียนเศษส่วน ⅓ เป็นทศนิยมในฐาน 10 คุณลงเอยด้วยทศนิยมซ้ำอนันต์ \(0.\overline{33}\ ).

หน่วยความจำคอมพิวเตอร์มีจำกัด ดังนั้นการแสดงเศษส่วนไบนารีซ้ำอนันต์ของ 0.1 จะถูกปัดเศษเป็นเศษส่วนจำกัด ค่าของตัวเลขนี้ขึ้นอยู่กับสถาปัตยกรรมของคอมพิวเตอร์ของคุณ (32 บิต กับ 64 บิต) วิธีหนึ่งในการดูค่าทศนิยมที่เก็บไว้เป็น 0.1 คือการใช้ .as_integer_ratio() สำหรับการทศนิยมเพื่อรับตัวเศษและตัวส่วนของการแสดงจุดทศนิยม:

 >>> numerator, denominator = (0.1).as_integer_ratio() >>> f"0.1 ≈ {numerator} / {denominator}" '0.1 ≈ 3602879701896397 / 36028797018963968'

ตอนนี้ใช้ format() เพื่อแสดงเศษส่วนที่ถูกต้องเป็นทศนิยม 55 ตำแหน่ง:

 >>> format(numerator / denominator, ".55f") '0.1000000000000000055511151231257827021181583404541015625'

ดังนั้น 0.1 จะถูกปัดเศษเป็นจำนวนที่มากกว่าค่าจริงเล็กน้อย

🐍
เรียนรู้เพิ่มเติมเกี่ยวกับวิธีการเกี่ยวกับตัวเลข เช่น .as_integer_ratio() ในบทความของฉัน 3 สิ่งที่คุณอาจไม่รู้เกี่ยวกับตัวเลขใน Python

ข้อผิดพลาดนี้เรียกว่า ข้อผิดพลาดในการแสดง จุดทศนิยม เกิดขึ้นบ่อยกว่าที่คุณคิด

ข้อผิดพลาดในการเป็นตัวแทนเป็นเรื่องธรรมดา จริงๆ

มีเหตุผลสามประการที่ทำให้ตัวเลขถูกปัดเศษเมื่อแสดงเป็นตัวเลขทศนิยม:

  1. ตัวเลขมีเลขนัยสำคัญมากกว่าจุดทศนิยมที่อนุญาต
  2. ตัวเลขไม่ลงตัว
  3. ตัวเลขเป็นจำนวนตรรกยะแต่มีการแทนค่าไบนารีที่ไม่สิ้นสุด

ตัวเลขทศนิยม 64 บิตดีสำหรับตัวเลขนัยสำคัญประมาณ 16 หรือ 17 หลัก ตัวเลขใดๆ ที่มีเลขนัยสำคัญมากกว่าจะถูกปัดเศษ จำนวนอตรรกยะ เช่น π และ e ไม่สามารถแทนด้วยเศษส่วนที่สิ้นสุดในฐานจำนวนเต็มใดๆ ดังนั้น ไม่ว่าจะเกิดอะไรขึ้น ตัวเลขอตรรกยะจะถูกปัดเศษเมื่อเก็บเป็นทศนิยม

สองสถานการณ์นี้สร้างชุดตัวเลขอนันต์ที่ไม่สามารถแสดงเป็นตัวเลขทศนิยมได้อย่างแน่นอน แต่ถ้าคุณไม่ใช่นักเคมีที่เกี่ยวกับตัวเลขเล็กๆ หรือนักฟิสิกส์ที่จัดการกับจำนวนมหาศาลทางดาราศาสตร์ คุณก็ไม่น่าจะประสบปัญหาเหล่านี้

แล้วจำนวนตรรกยะที่ไม่สิ้นสุด เช่น 0.1 ในฐาน 2 ล่ะ นี่คือจุดที่คุณจะพบปัญหาจุดลอยตัวส่วนใหญ่ และด้วยคณิตศาสตร์ที่กำหนดว่าเศษส่วนสิ้นสุดลงหรือไม่ คุณจะปัดเป่าข้อผิดพลาดในการแทนค่าได้บ่อยกว่าที่คุณคิด

ในฐาน 10 เศษส่วนสามารถแสดงเป็นเศษส่วนที่สิ้นสุดได้หากตัวส่วนเป็นผลคูณของ ตัวประกอบเฉพาะ ของ 10 ตัวประกอบเฉพาะของ 10 สองตัวคือ 2 และ 5 ดังนั้นเศษส่วนเช่น ½, ¼, ⅕, ⅛ และ ⅒ ยุติทั้งหมด แต่ ⅓, ⅐ และ ⅑ ไม่ยุติ อย่างไรก็ตาม ในฐาน 2 มีตัวประกอบเฉพาะเพียงตัวเดียว: 2. เศษส่วนที่มีตัวส่วนเป็นยกกำลัง 2 เท่านั้นจึงสิ้นสุด ด้วยเหตุนี้ เศษส่วนเช่น ⅓, ⅕, ⅙, ⅐, ⅑ และ ⅒ จึงไม่สิ้นสุดเมื่อแสดงเป็นเลขฐานสอง

ตอนนี้คุณสามารถเข้าใจตัวอย่างดั้งเดิมในบทความนี้ 0.1 , 0.2 และ 0.3 ทั้งหมดจะถูกปัดเศษเมื่อแปลงเป็นตัวเลขทศนิยม:

 >>> # -----------vvvv Display with 17 significant digits >>> format(0.1, ".17g") '0.10000000000000001' >>> format(0.2, ".17g") '0.20000000000000001' >>> format(0.3, ".17g") '0.29999999999999999'

เมื่อเพิ่ม 0.1 และ 0.2 ผลลัพธ์จะเป็นตัวเลขที่มากกว่า 0.3 เล็กน้อย:

 >>> 0.1 + 0.2 0.30000000000000004

เนื่องจาก 0.1 + 0.2 มีขนาดใหญ่กว่า 0.3 เล็กน้อย และ 0.3 จะแสดงด้วยตัวเลขที่เล็กกว่าตัวมันเองเล็กน้อย นิพจน์ 0.1 + 0.2 == 0.3 จะถูกประเมินเป็น False

ข้อผิดพลาดในการแสดงจุดทศนิยมเป็นสิ่งที่โปรแกรมเมอร์ทุกคนในทุกภาษาจำเป็นต้องรับรู้และรู้วิธีจัดการ ไม่เฉพาะเจาะจงกับ Python คุณสามารถดูผลลัพธ์ของการพิมพ์ 0.1 + 0.2 ในภาษาต่างๆ ได้ที่เว็บไซต์ที่มีชื่อเหมาะเจาะของ Erik Wiffin 0.300000000000004.com

วิธีเปรียบเทียบ Floats ใน Python

ดังนั้นคุณจะจัดการกับข้อผิดพลาดในการแสดงจุดทศนิยมได้อย่างไรเมื่อเปรียบเทียบทศนิยมใน Python เคล็ดลับคือการหลีกเลี่ยงการตรวจสอบความเท่าเทียมกัน ห้ามใช้ == , >= หรือ <= กับ floats ใช้ math.isclose() แทน:

 >>> import math >>> math.isclose(0.1 + 0.2, 0.3) True

math.isclose() ตรวจสอบว่าอาร์กิวเมนต์แรกใกล้เคียงกับอาร์กิวเมนต์ที่สองหรือไม่ แต่นั่นหมายความว่าอย่างไร? เคล็ดลับคือการตรวจสอบระยะห่างระหว่างอาร์กิวเมนต์แรกกับอาร์กิวเมนต์ที่สอง ซึ่งเทียบเท่ากับค่าสัมบูรณ์ของผลต่างของทั้งสองค่า:

 >>> a = 0.1 + 0.2 >>> b = 0.3 >>> abs(a - b) 5.551115123125783e-17

ถ้า abs(a - b) น้อยกว่าเปอร์เซ็นต์ของ a หรือ b ที่ใหญ่กว่า ถือว่า a ใกล้เคียงกับ b มากพอที่จะ “เท่ากับ” กับ b เปอร์เซ็นต์นี้เรียกว่าความ อดทนสัมพัทธ์ คุณสามารถระบุได้ด้วยอาร์กิวเมนต์คีย์เวิร์ด rel_tol ของ math.isclose() ซึ่งมีค่าเริ่มต้นเป็น 1e-9 กล่าวอีกนัยหนึ่งถ้า abs(a - b) น้อยกว่า 0.00000001 * max(abs(a), abs(b)) a และ b จะถือว่า “ใกล้” กัน สิ่งนี้รับประกันว่า a และ b มีค่าเท่ากับทศนิยมเก้าตำแหน่ง

คุณสามารถเปลี่ยนค่าเผื่อสัมพัทธ์ได้หากต้องการ:

 >>> math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-20) False

แน่นอน ความอดทนสัมพัทธ์นั้นขึ้นอยู่กับข้อจำกัดที่กำหนดโดยปัญหาที่คุณกำลังแก้ไข อย่างไรก็ตาม สำหรับการใช้งานประจำวันส่วนใหญ่ ค่าเผื่อสัมพัทธ์ที่เป็นค่าเริ่มต้นก็เพียงพอแล้ว

มีปัญหาหาก a หรือ b เป็นศูนย์และ rel_tol มีค่าน้อยกว่าหนึ่ง ในกรณีนั้น ไม่ว่าค่าที่ไม่ใช่ศูนย์จะเป็นศูนย์มากแค่ไหน ความคลาดเคลื่อนสัมพัทธ์รับประกันว่าการตรวจสอบความใกล้ชิดจะล้มเหลวเสมอ ในกรณีนี้ การใช้ความคลาดเคลื่อนสัมบูรณ์ทำงานเป็นทางเลือก:

 >>> # Relative check fails! >>> # ---------------vvvv Relative tolerance >>> # ----------------------vvvvv max(0, 1e-10) >>> abs(0 - 1e-10) < 1e-9 * 1e-10 False >>> # Absolute check works! >>> # ---------------vvvv Absolute tolerance >>> abs(0 - 1e-10) < 1e-9 True

math.isclose() จะทำการตรวจสอบให้คุณโดยอัตโนมัติ อาร์กิวเมนต์คำหลัก abs_tol กำหนดความอดทนสัมบูรณ์ อย่างไรก็ตาม abs_tol ค่าเริ่มต้นเป็น 0.0 ดังนั้น คุณจะต้องตั้งค่านี้ด้วยตนเอง หากคุณต้องการตรวจสอบว่าค่าใกล้ศูนย์แค่ไหน

สรุปแล้ว math.isclose() ส่งคืนผลลัพธ์ของการเปรียบเทียบต่อไปนี้ ซึ่งรวมการทดสอบแบบสัมพัทธ์และแบบสัมบูรณ์เป็นนิพจน์เดียว:

 abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

math.isclose() ถูกนำมาใช้ใน PEP 485 และพร้อมใช้งานตั้งแต่ Python 3.5

คุณควรใช้ math.isclose() เมื่อใด

โดยทั่วไป คุณควรใช้ math.isclose() เมื่อใดก็ตามที่คุณต้องการเปรียบเทียบค่าทศนิยม แทนที่ == ด้วย math.isclose() :

 >>> # Don't do this: >>> 0.1 + 0.2 == 0.3 False >>> # Do this instead: >>> math.isclose(0.1 + 0.2, 0.3) True

คุณต้องระวังด้วย >= และ <= การเปรียบเทียบ จัดการความเท่าเทียมกันแยกกันโดยใช้ math.isclose() จากนั้นตรวจสอบการเปรียบเทียบที่เข้มงวด:

 >>> a, b, c = 0.1, 0.2, 0.3 >>> # Don't do this: >>> a + b <= c False >>> # Do this instead: >>> math.isclose(a + b, c) or (a + b < c) True

มีทางเลือกอื่นสำหรับ math.isclose() หากคุณใช้ NumPy คุณสามารถใช้ประโยชน์จาก numpy.allclose() และ numpy.isclose() :

 >>> import numpy as np >>> # Use numpy.allclose() to check if two arrays are equal >>> # to each other within a tolerance. >>> np.allclose([1e10, 1e-7], [1.00001e10, 1e-8]) False >>> np.allclose([1e10, 1e-8], [1.00001e10, 1e-9]) True >>> # Use numpy.isclose() to check if the elements of two arrays >>> # are equal to each other within a tolerance >>> np.isclose([1e10, 1e-7], [1.00001e10, 1e-8]) array([ True, False]) >>> np.isclose([1e10, 1e-8], [1.00001e10, 1e-9]) array([ True, True])

โปรดทราบว่าค่าความคลาดเคลื่อนที่สัมพันธ์เริ่มต้นและความคลาดเคลื่อนสัมบูรณ์ไม่เหมือนกับ math.isclose() ค่าเผื่อสัมพัทธ์เริ่มต้นสำหรับทั้ง numpy.allclose() และ numpy.isclose() คือ 1e-05 และค่าเผื่อเริ่มต้นที่แน่นอนสำหรับทั้งคู่คือ 1e-08

math.isclose() มีประโยชน์อย่างยิ่งสำหรับการทดสอบหน่วย แม้ว่าจะมีทางเลือกอื่นอยู่บ้าง โมดูล unittest ในตัวของ Python มีเมธอดunittest.TestCase.assertAlmostEqual() อย่างไรก็ตาม วิธีการนั้นใช้การทดสอบความแตกต่างแบบสัมบูรณ์เท่านั้น นอกจากนี้ยังเป็นการยืนยันด้วย ซึ่งหมายความว่าความล้มเหลวทำให้เกิด AssertionError ซึ่งทำให้ไม่เหมาะสำหรับการเปรียบเทียบในตรรกะทางธุรกิจของคุณ

ทางเลือกที่ดีสำหรับ math.isclose() สำหรับการทดสอบหน่วยคือ pytest.approx() จาก แพ็คเกจ pytest เช่นเดียวกับ math.isclose() , pytest.approx() รับสองอาร์กิวเมนต์และคืนค่าว่าเท่ากันหรือไม่ภายในเกณฑ์ความคลาดเคลื่อนบางประการ:

 >>> import pytest >>> pytest.approx(0.1 + 0.2, 0.3) True

เช่นเดียวกับ math.isclose() pytest.approx() มีอาร์กิวเมนต์คีย์เวิร์ด rel_tol และ abs_tol สำหรับการตั้งค่าความคลาดเคลื่อนสัมพัทธ์และสัมบูรณ์ อย่างไรก็ตาม ค่าเริ่มต้นจะแตกต่างกัน rel_tol มีค่าเริ่มต้น 1e-6 และ abs_tol มีค่าเริ่มต้น 1e-12

หากอาร์กิวเมนต์สองตัวแรกส่งผ่านไปยัง pytest.approx() มีลักษณะเหมือนอาร์เรย์ หมายความว่าเป็น Python ที่ iterable เช่น list หรือ tuple หรือแม้แต่อาร์เรย์ NumPy pytest.approx() จะทำงานเหมือน numpy.allclose() และส่งคืนว่าอาร์เรย์ทั้งสองมีค่าเท่ากันภายในค่าความคลาดเคลื่อนหรือไม่:

 >>> import numpy as np >>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == pytest.approx(np.array([0.3, 0.6])) True

pytest.approx() จะทำงานร่วมกับค่าพจนานุกรม:

 >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == pytest.approx({'a': 0.3, 'b': 0.6}) True

ตัวเลขทศนิยมเหมาะสำหรับการทำงานกับตัวเลขเมื่อใดก็ตามที่ไม่ต้องการความแม่นยำอย่างแท้จริง มีความรวดเร็วและหน่วยความจำมีประสิทธิภาพ แต่ถ้าคุณต้องการความแม่นยำ มีทางเลือกอื่นที่คุณควรพิจารณา

ทางเลือกทศนิยมที่แม่นยำ

มีตัวเลขในตัวสองประเภทใน Python ที่ให้ความแม่นยำเต็มที่สำหรับสถานการณ์ที่ floats ไม่เพียงพอ: Decimal และ Fraction

ประเภท Decimal

ประเภท Decimal สามารถจัดเก็บค่าทศนิยมได้อย่างแม่นยำเท่าที่คุณต้องการ โดยค่าเริ่มต้น Decimal จะรักษาตัวเลขสำคัญ 28 ตัว แต่คุณสามารถเปลี่ยนสิ่งนี้เป็นสิ่งที่คุณต้องการเพื่อให้เหมาะกับปัญหาเฉพาะที่คุณกำลังแก้ไข:

 >>> # Import the Decimal type from the decimal module >>> from decimal import Decimal >>> # Values are represented exactly so no rounding error occurs >>> Decimal("0.1") + Decimal("0.2") == Decimal("0.3") True >>> # By default 28 significant figures are preserved >>> Decimal(1) / Decimal(7) Decimal('0.1428571428571428571428571429') >>> # You can change the significant figures if needed >>> from decimal import getcontext >>> getcontext().prec = 6 # Use 6 significant figures >>> Decimal(1) / Decimal(7) Decimal('0.142857')

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับประเภท Decimal ได้ใน เอกสาร Python

ประเภท Fraction

อีกทางเลือกหนึ่งสำหรับตัวเลขทศนิยมคือ ประเภท Fraction Fraction สามารถเก็บจำนวนตรรกยะได้อย่างแม่นยำและเอาชนะปัญหาข้อผิดพลาดในการแสดงที่พบกับตัวเลขทศนิยม:

 >>> # import the Fraction type from the fractions module >>> from fractions import Fraction >>> # Instantiate a Fraction with a numerator and denominator >>> Fraction(1, 10) Fraction(1, 10) >>> # Values are represented exactly so no rounding error occurs >>> Fraction(1, 10) + Fraction(2, 10) == Fraction(3, 10) True

ทั้ง Fraction และ Decimal ให้ประโยชน์มากมายเหนือค่าทศนิยมมาตรฐาน อย่างไรก็ตาม ประโยชน์เหล่านี้มาพร้อมกับราคา: ความเร็วที่ลดลงและการใช้หน่วยความจำที่สูงขึ้น หากคุณไม่ต้องการความแม่นยำอย่างแท้จริง คุณก็ควรใช้ลูกลอย แต่สำหรับสิ่งต่าง ๆ เช่น แอปพลิเคชันทางการเงินและที่สำคัญต่อภารกิจ การประนีประนอมที่เกิดจาก Fraction และ Decimal อาจคุ้มค่า

บทสรุป

ค่าทศนิยมเป็นทั้งพรและคำสาป พวกเขาเสนอการดำเนินการทางคณิตศาสตร์ที่รวดเร็วและการใช้หน่วยความจำอย่างมีประสิทธิภาพโดยเสียค่าใช้จ่ายในการแสดงที่ไม่ถูกต้อง ในบทความนี้ คุณได้เรียนรู้:

  • ทำไมตัวเลขทศนิยมไม่แม่นยำ
  • เหตุใดข้อผิดพลาดในการแสดงจุดทศนิยมจึงเป็นเรื่องปกติ
  • วิธีเปรียบเทียบค่าทศนิยมใน Python . อย่างถูกต้อง
  • วิธีแสดงตัวเลขอย่างแม่นยำโดยใช้ประเภท Fraction และ Decimal ของ Python

หากคุณได้เรียนรู้สิ่งใหม่ๆ อาจมีมากกว่านั้นที่คุณไม่รู้เกี่ยวกับตัวเลขใน Python ตัวอย่างเช่น คุณทราบหรือไม่ว่าประเภท int ไม่ใช่ประเภทจำนวนเต็มเพียงชนิดเดียวใน Python ค้นหาว่าจำนวนเต็มประเภทอื่นคืออะไรและข้อเท็จจริงอื่นๆ ที่ไม่ค่อยมีใครรู้จักเกี่ยวกับตัวเลขในบทความของฉัน 3 สิ่งที่คุณอาจไม่ทราบเกี่ยวกับตัวเลขใน Python

วิธีที่ถูกต้องในการเปรียบเทียบ Floats ใน Python

แหล่งข้อมูลเพิ่มเติม


ต้องการยกระดับทักษะ Python ของคุณไปอีกระดับหรือไม่? ฉันเสนอการฝึกสอนแบบตัวต่อตัวแบบตัวต่อตัวสำหรับการเขียนโปรแกรม Python และการเขียนเชิงเทคนิค คลิกที่นี่ เพื่อเรียนรู้เพิ่มเติม