"""Day 1 · Block 3a + 3b · 列表与元组
学习目标:掌握列表的增删改查、切片、嵌套,分清 sort() 原地改动返回 None
与 sorted() 返回新列表;会用 for / enumerate / zip / range 遍历;
真正理解"别名不是拷贝"、元组解包,以及 [[0] * 3] * 3 为什么是同一行。
用法:uv run python exercises/day1/03_lists_tuples.py
做法:把每个 TODO 换成你的实现,反复运行直到全部 [OK]。
3a 先做前 5 题,3b 再做后 4 题。
卡住 8 分钟以上再按 ai-guide.md 的模板提问;拿到提示后自己重写。
陷阱反例:b = a 不是复制;lst.sort() 返回 None;
for x in lst: lst.remove(x) 会漏删;[[0] * 3] * 3 三行是同一个列表。
预测练习(先在纸上写出输出,再到 REPL 验证;前三段贴到 Python Tutor 看动画):
>>> a = [1, 2, 3]
>>> b = a
>>> b.append(4)
>>> print(a) # ?
>>> c = a[:]
>>> c.append(5)
>>> print(a, c) # ?
>>> x = [3, 1, 2].sort()
>>> print(x) # ? ← sort() 返回 None
>>> t = (1,)
>>> print(type(t), type((1)))
>>> for i in range(1, 10, 3):
... print(i, end=" ")
>>> grid = [[0] * 3] * 2
>>> grid[0][0] = 9
>>> print(grid) # ? ← 两行是同一个列表
"""
# ---- 任务 1 ----
def running_total(nums: list[int]) -> list[int]:
"""返回前缀和列表。
例:running_total([1, 2, 3, 4]) -> [1, 3, 6, 10]
"""
# TODO: 在这里实现
raise NotImplementedError
# ---- 任务 2 ----
def remove_duplicates_keep_order(items: list) -> list:
"""去重,并保持每个元素首次出现的顺序。
例:remove_duplicates_keep_order([3, 1, 3, 2, 1]) -> [3, 1, 2]
"""
# TODO: 在这里实现
raise NotImplementedError
# ---- 任务 3 ----
def rotate(lst: list, k: int) -> list:
"""返回右旋 k 位的新列表;k 可以大于长度(用 %);空列表返回 []。
例:rotate([1, 2, 3, 4, 5], 2) -> [4, 5, 1, 2, 3]
"""
# TODO: 在这里实现
raise NotImplementedError
# ---- 任务 4 ----
def top_n(scores: list[int], n: int) -> list[int]:
"""返回降序排列的前 n 个分数,不能修改入参(用 sorted 而不是 sort)。
例:top_n([70, 95, 88, 60], 2) -> [95, 88]
"""
# TODO: 在这里实现
raise NotImplementedError
# ---- 任务 5 ----
def pair_up(names: list[str], scores: list[int]) -> list[tuple[str, int]]:
"""把姓名和分数用 zip 配成元组列表;两个列表长度不同时抛 ValueError。
例:pair_up(["张三"], [90]) -> [("张三", 90)]
"""
# TODO: 在这里实现
raise NotImplementedError
# ---- 任务 6 ----
def min_max(nums: list[int]) -> tuple[int, int]:
"""返回 (最小值, 最大值) 元组;空列表抛 ValueError。
例:lo, hi = min_max([3, 1, 2]) # lo 是 1,hi 是 3
"""
# TODO: 在这里实现
raise NotImplementedError
# ---- 任务 7 ----
def safe_copy_and_append(lst: list, item) -> list:
"""返回"追加了 item 的新列表",原列表保持不变。
例:orig = [1, 2]; safe_copy_and_append(orig, 3) -> [1, 2, 3],orig 仍是 [1, 2]
"""
# TODO: 在这里实现
raise NotImplementedError
# ---- 任务 8 ----
def transpose(matrix: list[list[int]]) -> list[list[int]]:
"""行列互换。
例:transpose([[1, 2, 3], [4, 5, 6]]) -> [[1, 4], [2, 5], [3, 6]]
"""
# TODO: 在这里实现
raise NotImplementedError
# ---- 任务 9 ----
def make_grid(rows: int, cols: int, fill: int = 0) -> list[list[int]]:
"""生成 rows × cols 的二维列表,每一行必须是彼此独立的对象。
例:g = make_grid(2, 2); g[0][0] = 1 之后 g[1][0] 仍是 0
"""
# TODO: 在这里实现
raise NotImplementedError
# ====================== 自测:不要改动下面的代码 ======================
def _check_running_total() -> None:
assert running_total([1, 2, 3, 4]) == [1, 3, 6, 10], "应为 [1, 3, 6, 10]"
assert running_total([]) == [], "空列表应返回空列表"
def _check_remove_duplicates_keep_order() -> None:
assert remove_duplicates_keep_order([3, 1, 3, 2, 1]) == [3, 1, 2], (
"去重要保持首次出现顺序,应为 [3, 1, 2]"
)
assert remove_duplicates_keep_order([]) == [], "空列表应返回空列表"
def _check_rotate() -> None:
original = [1, 2, 3, 4, 5]
assert rotate(original, 2) == [4, 5, 1, 2, 3], "右旋 2 位应为 [4, 5, 1, 2, 3]"
assert original == [1, 2, 3, 4, 5], "rotate 不能修改入参"
assert rotate([1, 2, 3], 4) == [3, 1, 2], "k 大于长度要先取模,应为 [3, 1, 2]"
assert rotate([], 3) == [], "空列表应返回 [](注意不要除以 0)"
def _check_top_n() -> None:
scores = [70, 95, 88, 60]
assert top_n(scores, 2) == [95, 88], "应为 [95, 88]"
assert scores == [70, 95, 88, 60], "top_n 不能修改入参(用 sorted 而不是 sort)"
def _check_pair_up() -> None:
assert pair_up(["张三", "李四"], [90, 85]) == [("张三", 90), ("李四", 85)], (
'应为 [("张三", 90), ("李四", 85)]'
)
assert pair_up([], []) == [], "两个空列表应返回空列表"
try:
pair_up(["张三"], [90, 85])
except ValueError:
pass
else:
raise AssertionError("两个列表长度不同时应抛出 ValueError")
def _check_min_max() -> None:
lo, hi = min_max([3, 1, 2])
assert (lo, hi) == (1, 3), "min_max([3, 1, 2]) 应为 (1, 3)"
assert min_max([5]) == (5, 5), "只有一个元素时最小值等于最大值"
try:
min_max([])
except ValueError:
pass
else:
raise AssertionError("空列表应抛出 ValueError")
def _check_safe_copy_and_append() -> None:
orig = [1, 2]
new = safe_copy_and_append(orig, 3)
assert new == [1, 2, 3], "新列表应为 [1, 2, 3]"
assert orig == [1, 2], "原列表必须保持 [1, 2] 不变"
assert new is not orig, "必须返回新列表,不能返回同一个对象"
def _check_transpose() -> None:
assert transpose([[1, 2, 3], [4, 5, 6]]) == [[1, 4], [2, 5], [3, 6]], (
"应为 [[1, 4], [2, 5], [3, 6]]"
)
assert transpose([[1]]) == [[1]], "1×1 矩阵转置还是自己"
def _check_make_grid() -> None:
g = make_grid(2, 2)
assert g == [[0, 0], [0, 0]], "make_grid(2, 2) 应为 [[0, 0], [0, 0]]"
g[0][0] = 1
assert g[1][0] == 0, "每行必须是独立对象(别用 [[0] * cols] * rows)"
assert make_grid(1, 3, 7) == [[7, 7, 7]], "fill 参数应能改默认填充值"
CHECKS = [
_check_running_total,
_check_remove_duplicates_keep_order,
_check_rotate,
_check_top_n,
_check_pair_up,
_check_min_max,
_check_safe_copy_and_append,
_check_transpose,
_check_make_grid,
]
def _run_checks() -> None:
passed = 0
for check in CHECKS:
name = check.__name__.removeprefix("_check_")
try:
check()
except NotImplementedError:
print(f"[TODO] {name}: 还没实现")
except AssertionError as e:
print(f"[FAIL] {name}: {e}")
except Exception as e:
print(f"[ERROR] {name}: {type(e).__name__}: {e}")
else:
passed += 1
print(f"[OK] {name}")
total = len(CHECKS)
tail = ",本文件全部完成!" if passed == total else ""
print(f"\n{passed}/{total} 通过{tail}")
if __name__ == "__main__":
_run_checks()