import random
import io
import re
# ==========================================
# 1. 題目標準解答 (Solver)
# ==========================================
def solve(input_text):
lines = input_text.strip('\n').split('\n')
if not lines:
return ""
# 讀取匯率
n = int(lines[0].strip())
exchange_rates = {'USD': 1.0}
for i in range(1, n + 1):
currency, rate = lines[i].strip().split()
exchange_rates[currency] = float(rate)
csv_start_idx = n + 1
# 略過空行找到 CSV 起點
while csv_start_idx < len(lines) and lines[csv_start_idx].strip() == "":
csv_start_idx += 1
# 略過 Header
csv_start_idx += 1
category_revenue = {}
# 手刻 CSV 橫列解析邏輯 (處理引號內的逗號)
def parse_csv_line(line):
result = []
current = ""
in_quotes = False
for char in line:
if char == '"':
in_quotes = not in_quotes
elif char == ',' and not in_quotes:
result.append(current)
current = ""
else:
current += char
result.append(current)
return result
for i in range(csv_start_idx, len(lines)):
line = lines[i].strip()
if not line:
continue
fields = parse_csv_line(line)
# 條件 1: 缺少欄位 (固定 6 個欄位)
if len(fields) != 6:
continue
tid, product, category, price_str, currency, qty_str = [f.strip() for f in fields]
# 條件 1.5: 必填欄位不可為空
if not all([tid, product, category, price_str, currency, qty_str]):
continue
# 條件 2 & 3: 價格數量需為正數值
try:
price = float(price_str)
qty = int(qty_str)
if price <= 0 or qty <= 0:
continue
except ValueError:
continue
# 條件 4: 貨幣必須存在
if currency not in exchange_rates:
continue
# 計算營收
revenue = price * qty * exchange_rates[currency]
if category not in category_revenue:
category_revenue[category] = 0.0
category_revenue[category] += revenue
# 排序:營收降序,類別升序
sorted_categories = sorted(category_revenue.items(), key=lambda x: (-x[1], x[0]))
output = []
for cat, rev in sorted_categories:
output.append(f"{cat},{rev:.2f}")
return "\n".join(output)
# ==========================================
# 2. 測資產生器 (Test Data Generator)
# ==========================================
def generate_test_data(num_records=1000):
currencies = {"TWD": 0.03, "EUR": 1.1, "JPY": 0.007, "GBP": 1.25}
categories = ["Electronics", "Accessories", "Furniture", "Clothing", "Toys"]
products_clean = ["Laptop", "Monitor", "Desk", "T-Shirt", "Bear"]
products_comma = ["\"Mouse, Wireless\"", "\"Keyboard, RGB\"", "\"Cable, USB-C\"", "\"Chair, Ergonomic\""]
input_lines = []
# 1. 產生匯率表 (只放部分,讓一些貨幣變成無效)
input_lines.append(str(len(currencies)))
for cur, rate in currencies.items():
input_lines.append(f"{cur} {rate}")
input_lines.append("")
input_lines.append("TransactionID,ProductName,Category,Price,Currency,Quantity")
# 2. 產生 CSV 數據
for i in range(1, num_records + 1):
# 決定正常資料還是髒資料 (80% 正常, 20% 髒資料)
is_clean = random.random() < 0.8
tid = str(i)
product = random.choice(products_clean + products_comma)
category = random.choice(categories)
price = round(random.uniform(10, 1000), 2)
currency = random.choice(list(currencies.keys()) + ["USD", "CAD"]) # CAD 會被過濾
qty = random.randint(1, 10)
if not is_clean:
error_type = random.randint(1, 4)
if error_type == 1:
category = "" # 缺漏欄位
elif error_type == 2:
price = "abc" # 非數值
elif error_type == 3:
qty = -5 # 負數
elif error_type == 4:
currency = "KRW" # 未知貨幣
line = f"{tid},{product},{category},{price},{currency},{qty}"
input_lines.append(line)
return "\n".join(input_lines)
# ==========================================
# 3. 執行與輸出範例
# ==========================================
if __name__ == "__main__":
print("Generating test data...")
test_input = generate_test_data(num_records=10) # 產生 50 筆做為範例
print("\n--- [Input.txt] ---")
print(test_input)
print("\n--- [Output.txt] ---")
test_output = solve(test_input)
print(test_output)
# 若需存檔,可解除下方註解
# with open("input.txt", "w", encoding="utf-8") as f:
# f.write(test_input)
# with open("output.txt", "w", encoding="utf-8") as f:
# f.write(test_output)
aW1wb3J0IHJhbmRvbQppbXBvcnQgaW8KaW1wb3J0IHJlCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIDEuIOmhjOebruaomea6luino+etlCAoU29sdmVyKQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQpkZWYgc29sdmUoaW5wdXRfdGV4dCk6CiAgICBsaW5lcyA9IGlucHV0X3RleHQuc3RyaXAoJ1xuJykuc3BsaXQoJ1xuJykKICAgIGlmIG5vdCBsaW5lczoKICAgICAgICByZXR1cm4gIiIKICAgIAogICAgIyDoroDlj5bljK/njocKICAgIG4gPSBpbnQobGluZXNbMF0uc3RyaXAoKSkKICAgIGV4Y2hhbmdlX3JhdGVzID0geydVU0QnOiAxLjB9CiAgICBmb3IgaSBpbiByYW5nZSgxLCBuICsgMSk6CiAgICAgICAgY3VycmVuY3ksIHJhdGUgPSBsaW5lc1tpXS5zdHJpcCgpLnNwbGl0KCkKICAgICAgICBleGNoYW5nZV9yYXRlc1tjdXJyZW5jeV0gPSBmbG9hdChyYXRlKQogICAgICAgIAogICAgY3N2X3N0YXJ0X2lkeCA9IG4gKyAxCiAgICAjIOeVpemBjuepuuihjOaJvuWIsCBDU1Yg6LW36bueCiAgICB3aGlsZSBjc3Zfc3RhcnRfaWR4IDwgbGVuKGxpbmVzKSBhbmQgbGluZXNbY3N2X3N0YXJ0X2lkeF0uc3RyaXAoKSA9PSAiIjoKICAgICAgICBjc3Zfc3RhcnRfaWR4ICs9IDEKICAgICAgICAKICAgICMg55Wl6YGOIEhlYWRlcgogICAgY3N2X3N0YXJ0X2lkeCArPSAxIAogICAgCiAgICBjYXRlZ29yeV9yZXZlbnVlID0ge30KCiAgICAjIOaJi+WIuyBDU1Yg5qmr5YiX6Kej5p6Q6YKP6LyvICjomZXnkIblvJXomZ/lhafnmoTpgJfomZ8pCiAgICBkZWYgcGFyc2VfY3N2X2xpbmUobGluZSk6CiAgICAgICAgcmVzdWx0ID0gW10KICAgICAgICBjdXJyZW50ID0gIiIKICAgICAgICBpbl9xdW90ZXMgPSBGYWxzZQogICAgICAgIGZvciBjaGFyIGluIGxpbmU6CiAgICAgICAgICAgIGlmIGNoYXIgPT0gJyInOgogICAgICAgICAgICAgICAgaW5fcXVvdGVzID0gbm90IGluX3F1b3RlcwogICAgICAgICAgICBlbGlmIGNoYXIgPT0gJywnIGFuZCBub3QgaW5fcXVvdGVzOgogICAgICAgICAgICAgICAgcmVzdWx0LmFwcGVuZChjdXJyZW50KQogICAgICAgICAgICAgICAgY3VycmVudCA9ICIiCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBjdXJyZW50ICs9IGNoYXIKICAgICAgICByZXN1bHQuYXBwZW5kKGN1cnJlbnQpCiAgICAgICAgcmV0dXJuIHJlc3VsdAoKICAgIGZvciBpIGluIHJhbmdlKGNzdl9zdGFydF9pZHgsIGxlbihsaW5lcykpOgogICAgICAgIGxpbmUgPSBsaW5lc1tpXS5zdHJpcCgpCiAgICAgICAgaWYgbm90IGxpbmU6CiAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgIAogICAgICAgIGZpZWxkcyA9IHBhcnNlX2Nzdl9saW5lKGxpbmUpCiAgICAgICAgCiAgICAgICAgIyDmop3ku7YgMTog57y65bCR5qyE5L2NICjlm7rlrpogNiDlgIvmrITkvY0pCiAgICAgICAgaWYgbGVuKGZpZWxkcykgIT0gNjoKICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgCiAgICAgICAgdGlkLCBwcm9kdWN0LCBjYXRlZ29yeSwgcHJpY2Vfc3RyLCBjdXJyZW5jeSwgcXR5X3N0ciA9IFtmLnN0cmlwKCkgZm9yIGYgaW4gZmllbGRzXQogICAgICAgIAogICAgICAgICMg5qKd5Lu2IDEuNTog5b+F5aGr5qyE5L2N5LiN5Y+v54K656m6CiAgICAgICAgaWYgbm90IGFsbChbdGlkLCBwcm9kdWN0LCBjYXRlZ29yeSwgcHJpY2Vfc3RyLCBjdXJyZW5jeSwgcXR5X3N0cl0pOgogICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAKICAgICAgICAjIOaineS7tiAyICYgMzog5YO55qC85pW46YeP6ZyA54K65q2j5pW45YC8CiAgICAgICAgdHJ5OgogICAgICAgICAgICBwcmljZSA9IGZsb2F0KHByaWNlX3N0cikKICAgICAgICAgICAgcXR5ID0gaW50KHF0eV9zdHIpCiAgICAgICAgICAgIGlmIHByaWNlIDw9IDAgb3IgcXR5IDw9IDA6CiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgIGV4Y2VwdCBWYWx1ZUVycm9yOgogICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAKICAgICAgICAjIOaineS7tiA0OiDosqjluaPlv4XpoIjlrZjlnKgKICAgICAgICBpZiBjdXJyZW5jeSBub3QgaW4gZXhjaGFuZ2VfcmF0ZXM6CiAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgIAogICAgICAgICMg6KiI566X54ef5pS2CiAgICAgICAgcmV2ZW51ZSA9IHByaWNlICogcXR5ICogZXhjaGFuZ2VfcmF0ZXNbY3VycmVuY3ldCiAgICAgICAgCiAgICAgICAgaWYgY2F0ZWdvcnkgbm90IGluIGNhdGVnb3J5X3JldmVudWU6CiAgICAgICAgICAgIGNhdGVnb3J5X3JldmVudWVbY2F0ZWdvcnldID0gMC4wCiAgICAgICAgY2F0ZWdvcnlfcmV2ZW51ZVtjYXRlZ29yeV0gKz0gcmV2ZW51ZQoKICAgICMg5o6S5bqP77ya54ef5pS26ZmN5bqP77yM6aGe5Yil5Y2H5bqPCiAgICBzb3J0ZWRfY2F0ZWdvcmllcyA9IHNvcnRlZChjYXRlZ29yeV9yZXZlbnVlLml0ZW1zKCksIGtleT1sYW1iZGEgeDogKC14WzFdLCB4WzBdKSkKICAgIAogICAgb3V0cHV0ID0gW10KICAgIGZvciBjYXQsIHJldiBpbiBzb3J0ZWRfY2F0ZWdvcmllczoKICAgICAgICBvdXRwdXQuYXBwZW5kKGYie2NhdH0se3JldjouMmZ9IikKICAgICAgICAKICAgIHJldHVybiAiXG4iLmpvaW4ob3V0cHV0KQoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyAyLiDmuKzos4fnlKLnlJ/lmaggKFRlc3QgRGF0YSBHZW5lcmF0b3IpCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CmRlZiBnZW5lcmF0ZV90ZXN0X2RhdGEobnVtX3JlY29yZHM9MTAwMCk6CiAgICBjdXJyZW5jaWVzID0geyJUV0QiOiAwLjAzLCAiRVVSIjogMS4xLCAiSlBZIjogMC4wMDcsICJHQlAiOiAxLjI1fQogICAgY2F0ZWdvcmllcyA9IFsiRWxlY3Ryb25pY3MiLCAiQWNjZXNzb3JpZXMiLCAiRnVybml0dXJlIiwgIkNsb3RoaW5nIiwgIlRveXMiXQogICAgcHJvZHVjdHNfY2xlYW4gPSBbIkxhcHRvcCIsICJNb25pdG9yIiwgIkRlc2siLCAiVC1TaGlydCIsICJCZWFyIl0KICAgIHByb2R1Y3RzX2NvbW1hID0gWyJcIk1vdXNlLCBXaXJlbGVzc1wiIiwgIlwiS2V5Ym9hcmQsIFJHQlwiIiwgIlwiQ2FibGUsIFVTQi1DXCIiLCAiXCJDaGFpciwgRXJnb25vbWljXCIiXQogICAgCiAgICBpbnB1dF9saW5lcyA9IFtdCiAgICAKICAgICMgMS4g55Si55Sf5Yyv546H6KGoICjlj6rmlL7pg6jliIbvvIzorpPkuIDkupvosqjluaPorormiJDnhKHmlYgpCiAgICBpbnB1dF9saW5lcy5hcHBlbmQoc3RyKGxlbihjdXJyZW5jaWVzKSkpCiAgICBmb3IgY3VyLCByYXRlIGluIGN1cnJlbmNpZXMuaXRlbXMoKToKICAgICAgICBpbnB1dF9saW5lcy5hcHBlbmQoZiJ7Y3VyfSB7cmF0ZX0iKQogICAgCiAgICBpbnB1dF9saW5lcy5hcHBlbmQoIiIpCiAgICBpbnB1dF9saW5lcy5hcHBlbmQoIlRyYW5zYWN0aW9uSUQsUHJvZHVjdE5hbWUsQ2F0ZWdvcnksUHJpY2UsQ3VycmVuY3ksUXVhbnRpdHkiKQogICAgCiAgICAjIDIuIOeUoueUnyBDU1Yg5pW45pOaCiAgICBmb3IgaSBpbiByYW5nZSgxLCBudW1fcmVjb3JkcyArIDEpOgogICAgICAgICMg5rG65a6a5q2j5bi46LOH5paZ6YKE5piv6auS6LOH5paZICg4MCUg5q2j5bi4LCAyMCUg6auS6LOH5paZKQogICAgICAgIGlzX2NsZWFuID0gcmFuZG9tLnJhbmRvbSgpIDwgMC44CiAgICAgICAgCiAgICAgICAgdGlkID0gc3RyKGkpCiAgICAgICAgcHJvZHVjdCA9IHJhbmRvbS5jaG9pY2UocHJvZHVjdHNfY2xlYW4gKyBwcm9kdWN0c19jb21tYSkKICAgICAgICBjYXRlZ29yeSA9IHJhbmRvbS5jaG9pY2UoY2F0ZWdvcmllcykKICAgICAgICBwcmljZSA9IHJvdW5kKHJhbmRvbS51bmlmb3JtKDEwLCAxMDAwKSwgMikKICAgICAgICBjdXJyZW5jeSA9IHJhbmRvbS5jaG9pY2UobGlzdChjdXJyZW5jaWVzLmtleXMoKSkgKyBbIlVTRCIsICJDQUQiXSkgIyBDQUQg5pyD6KKr6YGO5r++CiAgICAgICAgcXR5ID0gcmFuZG9tLnJhbmRpbnQoMSwgMTApCiAgICAgICAgCiAgICAgICAgaWYgbm90IGlzX2NsZWFuOgogICAgICAgICAgICBlcnJvcl90eXBlID0gcmFuZG9tLnJhbmRpbnQoMSwgNCkKICAgICAgICAgICAgaWYgZXJyb3JfdHlwZSA9PSAxOgogICAgICAgICAgICAgICAgY2F0ZWdvcnkgPSAiIiAjIOe8uua8j+ashOS9jQogICAgICAgICAgICBlbGlmIGVycm9yX3R5cGUgPT0gMjoKICAgICAgICAgICAgICAgIHByaWNlID0gImFiYyIgIyDpnZ7mlbjlgLwKICAgICAgICAgICAgZWxpZiBlcnJvcl90eXBlID09IDM6CiAgICAgICAgICAgICAgICBxdHkgPSAtNSAjIOiyoOaVuAogICAgICAgICAgICBlbGlmIGVycm9yX3R5cGUgPT0gNDoKICAgICAgICAgICAgICAgIGN1cnJlbmN5ID0gIktSVyIgIyDmnKrnn6XosqjluaMKICAgICAgICAgICAgICAgIAogICAgICAgIGxpbmUgPSBmInt0aWR9LHtwcm9kdWN0fSx7Y2F0ZWdvcnl9LHtwcmljZX0se2N1cnJlbmN5fSx7cXR5fSIKICAgICAgICBpbnB1dF9saW5lcy5hcHBlbmQobGluZSkKICAgICAgICAKICAgIHJldHVybiAiXG4iLmpvaW4oaW5wdXRfbGluZXMpCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIDMuIOWft+ihjOiIh+i8uOWHuuevhOS+iwojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgcHJpbnQoIkdlbmVyYXRpbmcgdGVzdCBkYXRhLi4uIikKICAgIHRlc3RfaW5wdXQgPSBnZW5lcmF0ZV90ZXN0X2RhdGEobnVtX3JlY29yZHM9MTApICMg55Si55SfIDUwIOethuWBmueCuuevhOS+iwogICAgCiAgICBwcmludCgiXG4tLS0gW0lucHV0LnR4dF0gLS0tIikKICAgIHByaW50KHRlc3RfaW5wdXQpCiAgICAKICAgIHByaW50KCJcbi0tLSBbT3V0cHV0LnR4dF0gLS0tIikKICAgIHRlc3Rfb3V0cHV0ID0gc29sdmUodGVzdF9pbnB1dCkKICAgIHByaW50KHRlc3Rfb3V0cHV0KQogICAgCiAgICAjIOiLpemcgOWtmOaqlO+8jOWPr+ino+mZpOS4i+aWueiou+inowogICAgIyB3aXRoIG9wZW4oImlucHV0LnR4dCIsICJ3IiwgZW5jb2Rpbmc9InV0Zi04IikgYXMgZjoKICAgICMgICAgIGYud3JpdGUodGVzdF9pbnB1dCkKICAgICMgd2l0aCBvcGVuKCJvdXRwdXQudHh0IiwgInciLCBlbmNvZGluZz0idXRmLTgiKSBhcyBmOgogICAgIyAgICAgZi53cml0ZSh0ZXN0X291dHB1dCk=
Generating test data...
--- [Input.txt] ---
4
TWD 0.03
EUR 1.1
JPY 0.007
GBP 1.25
TransactionID,ProductName,Category,Price,Currency,Quantity
1,T-Shirt,,493.19,USD,9
2,Bear,,851.81,USD,8
3,"Cable, USB-C",Clothing,493.25,CAD,10
4,Laptop,Electronics,621.96,EUR,5
5,Monitor,Furniture,591.2,KRW,8
6,Bear,Furniture,974.27,EUR,3
7,Bear,Furniture,519.86,GBP,6
8,T-Shirt,Electronics,575.16,CAD,8
9,Bear,Electronics,989.65,KRW,8
10,Bear,Toys,796.71,TWD,5
--- [Output.txt] ---
Furniture,7114.04
Electronics,3420.78
Toys,119.51