🚀 Day 2:打造「結構化帳務明細與分類統計分析器」

5 天全日營隊 (Day 2 / 總時數 6 Hours) | 從單筆紀錄邁向資料庫二維結構

📖 為什麼學這些?——「從單筆開銷到發票分析器」關卡冒險故事線

💡 故事情境:理財咖啡館升級!從單張收據到完整發票數據表

昨天我們的記帳程式只能記錄「單一筆花費」,算完一次就失憶了。但真實生活中你一天會有好幾筆消費!今天我們要幫理財咖啡館打造**二維資料結構與數據分析系統**:

單元名稱 記帳程式關卡角色 為什麼這個單元不可或缺?(技術關聯)
單元 1:List 基礎與操作 🛒 裝發票的購物車口袋 使用清單(List)有序儲存多筆消費,隨時用 append() 把剛買的物品塞進袋子裡。
單元 2:Dict 鍵值對 🏷️ 商品詳細成分標籤牌 單靠數字無法說明細節。字典(Dict)能用自訂 Key-Value 打包商品的「名稱、價格、分類」詳細屬性。
單元 3:List of Dicts 組合 📊 完整帳本數據表 (Rows/Table) 將「字典清單化」二維組合 [{...}, {...}],完美模擬現代資料庫表格(Database Table)的每一列記錄。
單元 4:for 迴圈走訪統計 🧮 自動總帳巡邏機器人 手動加總太慢了!for 迴圈能自動逐筆巡邏所有紀錄,精確計算出總消費金額與單筆平均開銷。
單元 5:擴充資料模型 Schema 🗓️ 帳本欄位升級 (Schema) 為資料模型導入 category 與系統自動日期 datetime,讓帳本轉變為專業軟體數據規格。
單元 6:黑客松:資料分類器 🔍 精明消費分析師 (ORM filter) 實作條件篩選:一鍵掏出所有「餐飲」或「娛樂」消費小計,銜接未來 Django ORM 的 .filter() 查詢!

📌 課程前導與簡介說明

🎓 教師心法與教學策略 (Teacher Only)
  • 營隊定位: Day 2 的任務是引導學生理解如何將真實世界的「一張帳單明細」透過 List 與 Dict 結構化表達。
  • 具象對映法:清單(List)等於購物車,字典(Dict)等於商品的標籤牌(名稱、金額、分類)。
  • Django 銜接點:字典的 Key 就是資料庫欄位(Column),包含多個字典的清單(List of Dicts)就是一張資料庫數據表(Table)或 QuerySet。
🌟 學生自主學習指引 (Student Self-Study)

歡迎來到 Day 2!今天我們要解鎖「資料結構化」的魔法!請閱讀每個單元的 **故事單元說明**,掌握 List 與 Dict 的特性。點擊單元內的 **延伸閱讀** 展開技術深度觀念與 Google 搜尋,遇到寫碼難題時使用專屬的 **AI 協作除錯提示詞**!

💻 Day 2 專案總成果:結構化帳務統計與分類分析器

【教師參考解答】學生於 Day 2 結束時將能獨立撰寫的完整程式:

"""
 Day 2 最終成果:結構化帳務明細與分類統計分析器
 核心概念:List of Dicts 資料結構 -> for 迴圈走訪加總 -> 條件篩選 (ORM .filter 雛形)
"""
import datetime

# 1. 初始化資料結構 (模擬 Database Table / QuerySet)
records = [
    {"name": "珍珠奶茶", "price": 60, "category": "餐飲", "date": "2026-08-03"},
    {"name": "Python 教科書", "price": 550, "category": "學習", "date": "2026-08-03"},
    {"name": "排骨便當", "price": 110, "category": "餐飲", "date": "2026-08-03"}
]

print("==========================================")
print("   📊 Day 2 結構化帳務統計與篩選器 📊   ")
print("==========================================")

# 2. 新增一筆動態紀錄
new_item = input("👉 請輸入新增消費名稱: ").strip()
new_price = int(input("👉 請輸入金額 (TWD): "))
new_cat = input("👉 請輸入分類 (餐飲/學習/娛樂): ").strip()
today_str = datetime.date.today().strftime("%Y-%m-%d")

new_record = {
    "name": new_item,
    "price": new_price,
    "category": new_cat if new_cat else "一般",
    "date": today_str
}
records.append(new_record)

# 3. 走訪清單並計算統計資訊
print("\n---------------- 🛒 完整消費明細表 ----------------")
total_sum = 0
for idx, item in enumerate(records, start=1):
    print(f"[{idx}] {item['date']} | {item['name']:<10} | ${item['price']:<5} | 分類: {item['category']}")
    total_sum += item["price"]

avg_price = total_sum / len(records)
print("----------------------------------------------------")
print(f"💰 總消費金額  :${total_sum} 元")
print(f"📈 平均單筆花費:${avg_price:.1f} 元")

# 4. 條件篩選 (模擬 Django ORM .filter())
search_cat = input("\n🔍 請輸入要篩選的分類名稱 (例如: 餐飲): ").strip()
print(f"\n--- 🔎 分類「{search_cat}」篩選結果 ---")
filtered_count = 0
filtered_sum = 0

for item in records:
    if item["category"] == search_cat:
        print(f" • {item['name']}: ${item['price']} 元 ({item['date']})")
        filtered_count += 1
        filtered_sum += item["price"]

if filtered_count > 0:
    print(f"\n🎯 符合「{search_cat}」共 {filtered_count} 筆,小計:${filtered_sum} 元")
else:
    print(f"\n⚠️ 找不到分類為「{search_cat}」的消費紀錄。")

【學生自主實作框架】請根據今日各單元所學,將空缺的 `____` 填入正確程式碼:

"""
 🎯 Day 2 自主挑戰:請將下方的 ____ 填入正確的 List、Dict 或 for 迴圈語法!
"""
import datetime

# 1. 初始化資料庫結構 (List of Dicts)
records = [
    {"name": "珍珠奶茶", "price": 60, "category": "餐飲"},
    {"name": "Python 教科書", "price": 550, "category": "學習"}
]

# 2. 用字典建立新資料,並用 append 加進清單
new_item = input("請輸入品項: ")
new_price = int(input("請輸入金額: "))
new_cat = input("請輸入分類: ")

new_data = {
    "name": ____,
    "price": ____,
    "category": ____
}
records.____(new_data) # 提示:加進清單的方法

# 3. 用 for 迴圈走訪累加總金額
total_sum = 0
for item in ____: # 提示:要走訪的清單名稱
    print(f"• {item['name']}: ${item['price']} 元 [{item['category']}]")
    total_sum += item[____] # 提示:要累加金額的字典 Key

print(f"💰 總花費金額:${total_sum} 元")

# 4. 條件過濾
target = input("請輸入欲過濾的分類: ")
for item in records:
    if item["category"] ____ target: # 提示:比較相等的符號
        print(f"🎯 找到符合項目:{item['name']}")

⏰ 單元 1:List 基礎與操作(09:00 - 10:00)

🛒 故事單元說明:裝發票的購物車口袋

想像一下,如果每次買東西都要開一個新變數(price1, price2, price3...),帳本很快就會亂七八糟。清單(List)就像是一個「有順序的購物車口袋」,用語法 [] 建立。你可以使用 append() 把剛買的品項塞進口袋最後面,用 len() 算口袋裡有幾張發票,並用索引編號(如 items[0])拿取第一個商品。記得:**電腦數數是從 0 開始的喔!**

💡 深度觀念:清單索引 (Index) 與動態陣列

Python 的 List 是一種 **可變 (Mutable)** 且有序的序列。電腦在記憶體中為清單配置連續的指標空間:

  • 0-Based Indexing:第一個元素的偏移量 (Offset) 為 0,因此 list[0] 存取首筆資料。
  • 負數索引 list[-1]:方便從清單尾端倒數存取最新加入的元素。
  • append() 時間複雜度:平均為 $O(1)$,能高效率地在末端動態擴充資料。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請向初學者解釋 Python 的 List(清單)如何在記憶體中儲存資料,並說明 0-based index(從 0 開始的索引)與負數索引 (如 -1) 的運作原理。

🎓 教師備課手冊 (Teacher Only)
  • 單一變數像獨棟套房,清單像「多房間公寓」。
  • append() 把資料推入末端;len() 算長度;[0] 存取首筆。
  • Django 銜接點:Django ORM 查詢產生的 QuerySet 集合。
💻 程式碼段落
# 1-1: 建立清單與存取索引
items = ["便當", "珍奶", "筆記本"]
print(f"第一個品項:{items[0]}")  # 便當
print(f"清單總數量:{len(items)}") # 3

# 1-2: 動態加入新資料
items.append("運動飲料")
print("更新後的購物車:", items)
⚠️ 常見陷阱與問題案例
  • 索引越界 (Out of Range):長度為 3 的清單存取 items[3] $\rightarrow$ 報錯 IndexError: list index out of range
🤖 單元 1 常見問題 AI 協作除錯提示詞

我在用 Python 清單存資料時跳出了 IndexError: list index out of range 錯誤。請幫我分析原因,並說明 index 為什麼是從 0 到 len-1。

⏰ 單元 2:Dict 鍵值對(10:00 - 11:00)

🏷️ 故事單元說明:商品成分標籤牌

如果只有清單,我們只知道買了 3 個東西,但不知道每樣東西的名字、價格與分類。字典(Dict)用語法 {} 建立,用 **「自訂標籤 (Key) : 內容 (Value)」** 來打包單一商品的完整細節。例如:{"name": "珍奶", "price": 60}。這樣只要查 ["price"],就能精準抓出這筆商品的金額!

💡 深度觀念:字典 (Dictionary) 與哈希雜湊 (Hash Table)

Python 的 Dict 是透過 **Hash Table (雜湊表)** 實現的,具備極高的資料檢索效率:

  • Key 的唯一性與不可變性:Key 必須是唯一且不可變的型別(如字串或數字)。
  • 安全存取 dict.get('key', default):若 Key 不存在,使用 [] 會拋出 KeyError,而 .get() 會優雅回傳 None 或預設值。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請向初學者說明什麼是 Python 字典(Dict)的 Key-Value 鍵值對,並示範使用 dict['key'] 與 dict.get('key') 存取資料時的差異與安全性。

🎓 教師備課手冊 (Teacher Only)
  • Dict 用自訂標籤 (Key) 找內容 (Value)。
  • 語法:{"key": "value"}
  • Django 銜接點:字典的 Key 等同於資料庫欄位(Database Field/Column)。
💻 程式碼段落
# 2-1: 宣告單筆消費字典
expense = {
    "name": "珍珠奶茶",
    "price": 60,
    "category": "餐飲"
}

# 2-2: 存取與修改欄位
print(f"品項:{expense['name']}")
print(f"價格:${expense['price']} 元")

# 漲價更新
expense["price"] = 65
print(f"最新價格:${expense['price']} 元")
⚠️ 常見陷阱與問題案例
  • 存取不存在的 Key:拼錯或存取未定義的 Key expense["amount"] $\rightarrow$ 報錯 KeyError: 'amount'
🤖 單元 2 常見問題 AI 協作除錯提示詞

我在存取 Python 字典時跳出了 KeyError 錯誤。請協助我檢查 Key 名稱是否拼錯,並教我如何使用 .get() 語法避免當機。

⏰ 單元 3:List + Dict 組合構建(11:00 - 12:00)

📊 故事單元說明:完整帳本數據表

現在我們要結合前兩個單元的神技!把每一個商品的「字典標籤」塞進「清單購物車」裡,就變成了 **List of Dicts 二維資料結構**:records = [{...}, {...}]。這正是所有現代軟體與資料庫最核心的結構!清單代表「整張數據表 (Table)」,而裡面的每一個字典代表「一列消費紀錄 (Row)」。

💡 深度觀念:二維資料集與雙重存取語法

組合 List 與 Dict 能建立強大的二維資料模型(Two-dimensional Data Structure):

  • 雙重存取語法 records[0]["price"]:第一層 [0] 透過索引取出現第 1 筆字典,第二層 ["price"] 取出金額。
  • 對應 Excel / DB Table:Index 對應橫向列 (Row),Dict Key 對應縱向欄 (Column/Field)。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請用 Excel 表格的列(Row)與欄(Column)的概念,說明 Python List of Dicts 是如何對應真實資料庫表格結構的。

🎓 教師備課手冊 (Teacher Only)
  • 二維結構:records = [{...}, {...}]
  • 雙重存取:records[0]["price"]
  • Django 銜接點:資料庫表格(Database Table)。
💻 程式碼段落
# 3-1: 建立記帳數據庫雛形 (List of Dicts)
records = [
    {"name": "珍珠奶茶", "price": 60},
    {"name": "排骨便當", "price": 110}
]

# 3-2: 動態新增一筆紀錄
item_input = input("請輸入品項: ")
price_input = int(input("請輸入金額: "))

new_data = {"name": item_input, "price": price_input}
records.append(new_data)

print(f"目前第一筆名稱:{records[0]['name']}")
print(f"目前最後一筆金額:{records[-1]['price']}")
⚠️ 常見陷阱與問題案例
  • 型別存取搞混:把 List 當作 Dict 直接寫 records["price"] $\rightarrow$ 報錯 TypeError: list indices must be integers or slices, not str
🤖 單元 3 常見問題 AI 協作除錯提示詞

我在存取 List of Dicts 時遇到了 TypeError: list indices must be integers or slices, not str 錯誤。請教我如何用雙重存取語法 (如 list[0]['key']) 正確取出資料。

⏰ 單元 4:for 迴圈走訪與統計(13:00 - 14:00)

🧮 故事單元說明:自動總帳巡邏機器人

帳本裡如果有 100 筆資料,總不可能手動去加 `[0] + [1] + [2]` 吧?for 迴圈就像是一個「巡邏機器人」,語法 for item in records: 會自動掏出清單裡的每一個字典,幫我們印出細節,並透過累加變數 total += item["price"] 精確算出總花費與平均值!

💡 深度觀念:累加器模式 (Accumulator Pattern)

走訪容器並計算統計數據是演算法的核心基礎:

  • 初始化累加器 total = 0:必須放在迴圈外部,否則每次迭代都會被重置歸零。
  • enumerate(sequence, start=1):能同時取出當前項目的索引編號與內容物,非常適合用來印出排版明細。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請說明什麼是累加器模式(Accumulator Pattern),並示範如何用 for 迴圈搭配 enumerate 走訪 List of Dicts 算出總金額與平均數。

🎓 教師備課手冊 (Teacher Only)
  • for item in records: 逐筆取出字典。
  • 累加器變數 total_cost += item["price"] 需宣告在迴圈外。
  • Django 銜接點:Django HTML 模板中的 {% for item in items %} 網頁清單渲染。
💻 程式碼段落
records = [
    {"name": "珍珠奶茶", "price": 60},
    {"name": "排骨便當", "price": 110},
    {"name": "筆記本", "price": 45}
]

# 4-1: for 迴圈逐筆列印與累加
total_cost = 0
print("=== 消費明細 ===")
for item in records:
    print(f"• {item['name']}: ${item['price']} 元")
    total_cost += item["price"]

avg_cost = total_cost / len(records)
print(f"總計消費:${total_cost} 元")
print(f"平均花費:${avg_cost:.1f} 元")
⚠️ 常見陷阱與問題案例
  • 累加器位置寫錯:把 total = 0 寫在 for 迴圈內部,導致每次循環都被歸零。
🤖 單元 4 常見問題 AI 協作除錯提示詞

我寫了 for 迴圈想要計算清單總金額,但算出來的總和永遠只有最後一筆金額。請幫我檢查累加器 (total) 宣告的位置是否放錯了。

⏰ 單元 5:專案實作:擴充資料模型(14:00 - 15:00)

🗓️ 故事單元說明:帳本欄位升級 (Schema)

專業軟體不會只紀錄名稱跟金額。我們現在要為資料模型擴充「分類(餐飲/學習/娛樂)」以及「自動日期時間戳記」。引入 Python 內建的 datetime.date.today(),讓系統在你新增消費時,自動幫你蓋上今天的日期印章!

💡 深度觀念:資料規格 (Schema) 與 datetime 時間處理

在軟體工程中,統一資料欄位格式稱為定義 **Schema**:

  • datetime.date.today():獲取系統當前日期物件。
  • .strftime("%Y-%m-%d"):將日期物件轉化為標準的字串格式(如 2026-08-03)。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請說明如何使用 Python datetime 模組取得今天的日期,並解說 .strftime('%Y-%m-%d') 中各字母格式代表的意義。

🎓 教師備課手冊 (Teacher Only)
  • 模型擴充:categorydate 欄位。
  • 使用 datetime.date.today().strftime("%Y-%m-%d")
  • Django 銜接點:Django Model 欄位擴充與 Migration 資料庫遷移。
💻 程式碼段落
import datetime

# 取得今天日期字串
today_str = datetime.date.today().strftime("%Y-%m-%d")

# 建立具備完整 Schema 的紀錄
record = {
    "name": "Python 教科書",
    "price": 550,
    "category": "學習",
    "date": today_str
}

print("擴充後的資料模型:", record)
⚠️ 常見陷阱與問題案例
  • 舊資料缺欄位:舊的字典沒有 category 欄位,讀取時噴出 KeyError $\rightarrow$ 建立資料需統一 Schema。
🤖 單元 5 常見問題 AI 協作除錯提示詞

我在擴充字典欄位後,存取舊資料時跳出了 KeyError。請教我如何在讀取欄位時提供預設值 (例如 .get('category', '一般'))。

⏰ 單元 6:小組黑客松:資料分類器(15:00 - 16:00)

🔍 故事單元說明:精明消費分析師 (ORM filter)

記帳最終目的是要「檢討開銷」。如果我想知道「這個月到底喝了多少錢的珍奶?」,我們能在 for 迴圈裡面加上 if item["category"] == target: 判斷式。只有符合分類的項目才會被掏出來加總,並透過 .strip() 自動消除輸入時不小心多打的空格!這也是未來 Django 網頁後台一鍵搜尋資料庫的核心原理!

💡 深度觀念:資料過濾 (Filtering) 與字串正規化

資料分析的核心步驟包括過濾與清洗:

  • .strip() 字串清洗:能自動清除使用者輸入時頭尾多打的空白,避免 "餐飲 " == "餐飲" 比對失敗。
  • 對應 Django ORM:此邏輯等於 Django 資料庫查詢 Expenses.objects.filter(category='餐飲')

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請說明如何在 for 迴圈中用 if 條件式對 List of Dicts 進行欄位篩選,並解釋 .strip() 如何防止字串比較時因為多打空白而失敗。

🎓 教師備課手冊 (Teacher Only)
  • for 迴圈結合 if item["category"] == target: 比對。
  • 使用 .strip() 消除頭尾白空白。
  • Django 銜接點:Django ORM 語法 Expenses.objects.filter(category='餐飲')
💻 程式碼段落
records = [
    {"name": "珍珠奶茶", "price": 60, "category": "餐飲"},
    {"name": "排骨便當", "price": 110, "category": "餐飲"},
    {"name": "電影票", "price": 300, "category": "娛樂"}
]

target_cat = input("請輸入要搜尋的分類 (例如: 餐飲): ").strip()

cat_total = 0
print(f"\n--- 篩選分類:{target_cat} ---")
for item in records:
    if item["category"] == target_cat:
        print(f" • {item['name']}: ${item['price']} 元")
        cat_total += item["price"]

print(f"分類小計:${cat_total} 元")
⚠️ 常見陷阱與問題案例
  • 隱形空格比對失敗:輸入 "餐飲 "(帶有空白),導致 "餐飲 " == "餐飲" 結果永遠為 False
🤖 單元 6 常見問題 AI 協作除錯提示詞

我在比對使用者輸入的分類名稱時,明明輸入了一模一樣的中文文字卻沒有觸發 if 條件。請幫我檢查字串是否有包含多餘的空白字元,並示範使用 .strip()。