งูต้นฉบับจัดเมล็ดถั่วหลายสีลงถาดนับจำนวนที่มีช่องบวกและลบ

Python Counter.total: นับยอดรวม multiset โดยไม่เขียน sum(values) ซ้ำ

พี่โชว์Engineering

Counter คือ dict ที่ออกแบบมาสำหรับ multiset: key คือสิ่งของ และ value คือจำนวน Counter.total() ซึ่งมีตั้งแต่ Python 3.10 รวม count ทุกตัวให้โดยตรง จึงสื่อเจตนาดีกว่า sum(counter.values())

ส่วนที่คนพลาดบ่อยคือ Counter ยอมให้ count เป็นศูนย์หรือติดลบได้ และการอ่าน key ที่ไม่มีคืน 0 การบวก/ลบแบบ multiset จะตัดผลลัพธ์ที่ไม่เป็นบวกออก แต่ method subtract() เก็บค่าติดลบไว้ นี่เป็น semantics ที่ต้องเลือกให้ตรงกับโดเมน

from __future__ import annotations

import unittest
from collections import Counter


def remaining(stock: Counter[str], sold: Counter[str]) -> Counter[str]:
    return stock - sold


class CounterTests(unittest.TestCase):
    def test_total_and_multiset_subtraction(self):
        stock = Counter(apple=4, pear=2)
        sold = Counter(apple=1, pear=3)

        self.assertEqual(stock.total(), 6)
        self.assertEqual(remaining(stock, sold), Counter(apple=3))

    def test_subtract_keeps_negative_counts(self):
        balance = Counter(apple=1)
        balance.subtract(Counter(apple=3))
        self.assertEqual(balance["apple"], -2)
        self.assertEqual(balance.total(), -2)
        self.assertEqual(balance["missing"], 0)


if __name__ == "__main__":
    unittest.main()

เลือก semantics ก่อนเลือกคำสั่ง

ถ้า “คงเหลือ” ห้ามติดลบ ให้ใช้ operator - แล้วตรวจเหตุการณ์ oversell แยกต่างหาก ถ้าเป็น ledger ที่ต้องเห็นหนี้ subtract() เหมาะกว่า และ total() จะรวมค่าติดลบตามจริง ส่วน unary +counter ใช้สร้าง Counter ใหม่ที่เหลือเฉพาะ count บวกได้

อย่าลืมว่า total() บอกจำนวนหน่วยรวม ไม่ใช่จำนวนชนิดสินค้า จำนวนชนิดยังเป็น len(counter) และอาจรวม key ที่ count เป็นศูนย์หากเราไม่ลบออก

Recap

  • ใช้ total() เมื่อต้องการผลรวมของ count
  • เลือก - เมื่อต้องตัดค่าศูนย์/ติดลบ
  • เลือก subtract() เมื่อค่าติดลบมีความหมาย
  • แยก “จำนวนหน่วย” ออกจาก “จำนวนชนิด” ให้ชัด