题目要求:

No. Standard input Standard output
1 5
Ivanov 5
Petrov 4
Ivanov 3
Sidorov 5
Petrov 5
Ivanov 4.00
Petrov 4.50
Sidorov 5.00
2 3
Ivanov 5
Ivanov 5
Petrov 3
Ivanov 5.00
Petrov 3.00

这是我写的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
n = int(input())
name_list = []
child_list = {}
child_frequency = {}
for _ in range(n):
name , grade = input().split()
grade = int(grade)
if name not in child_list:
name_list.append(name)
child_list[name] = grade
child_frequency[name] = 1
else:
child_list[name] += grade
child_frequency[name] += 1
name_list.sort()
child = 0
for _ in child_frequency.keys():
child += 1
for i in range(child):
print(name_list[i],"%.2f"%(child_list[name_list[i]]/child_frequency[name_list[i]]))

最后通过下标访问列表很C,引入的新变量

可以直接遍历排序好的列表进行替代:

1
2
3
4
5
# 直接遍历排序后的名字列表
for name in name_list:
# 这里的 name 就等同于你原本的 name_list[i]
avg = child_list[name] / child_frequency[name]
print(name, "%.2f" % avg)

另外,获取列表元素的个数可以用len函数

1
child = len(name_list) # 一句话搞定

针对这种需要初始化字典键值的情况,Python 标准库里还有一个叫 collections.defaultdict 的神器,可以把中间那个 if...else... 判断省掉

什么是 defaultdict

defaultdict 是 Python 内置字典(dict)的一个升级版。它的超能力是:当你试图访问或修改一个不存在的键时,它不会报错,而是会自动帮你创建一个默认值。

如果你告诉它值的类型是 int,它就会把默认值设为 0;如果是 list,默认值就是空列表 []

看看代码对比

以前的写法(手动检查并初始化):

1
2
3
4
5
6
if name not in grade_sum:
grade_sum[name] = grade
grade_count[name] = 1
else:
grade_sum[name] += grade
grade_count[name] += 1

**使用 defaultdict 后的写法(直接累加,无脑操作):

1
2
3
# 因为不存在时会自动初始化为 0,所以直接加就行!
grade_sum[name] += grade
grade_count[name] += 1

终极进化版代码

结合 defaultdict,并且直接对字典的键进行排序(连额外的 name_list 列表都不需要建了),代码可以浓缩成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from collections import defaultdict

n = int(input())

# 告诉 defaultdict,字典里的值默认都是整数 (0)
grade_sum = defaultdict(int)
grade_count = defaultdict(int)

for _ in range(n):
name, grade = input().split()
grade = int(grade)

# 彻底告别 if...else,哪怕是第一次遇到这个名字,
# defaultdict 也会悄悄把它初始化为 0,然后顺利加上去
grade_sum[name] += grade
grade_count[name] += 1

# 直接获取字典所有的键(名字),排序后遍历
for name in sorted(grade_sum.keys()):
avg = grade_sum[name] / grade_count[name]
print(f"{name} {avg:.2f}")

为什么推荐这么写?

  1. 逻辑更专注:不需要再去关心“这个变量初始化了没有”,大脑可以完全聚焦在“我要计算总和与次数”这个核心逻辑上。
  2. 结构更精简:从维护三个容器(一个列表、两个字典),变成了只维护两个字典,减少了数据冗余。

其实这里可以用列表作为字典的值,巧妙地利用了Python强大的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
n = int(input())
gradelist = {}

for _ in range(n):
name, grade = input().split()
grade = int(grade)

# 巧妙改动:字典里存的不再是单一数字,而是一个存放该学生所有成绩的列表
if name not in gradelist:
gradelist[name] = [grade] # 第一次出现,创建一个包含当前成绩的列表
else:
gradelist[name].append(grade) # 再次出现,把新成绩追加进列表

# 题目要求按名字的字母顺序输出,所以用 sorted() 对字典的键(名字)进行排序
for name in sorted(gradelist.keys()):
scores = gradelist[name] # 获取该学生的所有成绩列表
avg_grade = sum(scores) / len(scores) # 计算真正的平均分:总分 / 次数

# 推荐使用 f-string 格式化输出,更加清晰
print(f"{name} {avg_grade:.2f}")

顺带讲一下这里的格式化输出:

1
print(f"{name} {avg_grade:.2f}")
  1. 开头的字母 f
  • 只有在字符串的引号前面加上 f(或 F),这个字符串才会拥有魔法。如果没有 f,Python 就会把它当成普通文本,原样输出 {name} 这几个字符。
  1. 第一处填空 {name}
  • 这里的要求是“原样放入变量”。Python 看到 {name},就会去内存里找变量 name 的值(比如 “Ivanov”),直接填在这个位置。
  1. 第二处填空 {avg_grade:.2f}

这里分成了两部分,中间用冒号 : 隔开:

  • 冒号左边 (avg_grade):你要放入的变量。
  • 冒号右边 (.2f):你要对这个变量执行的格式化规则

那这一题最Pythonic的写法长啥样?

抛开 C 语言手动管理数据的包袱,在 Python 中处理这类“分组收集再统计”的问题,最“标准”且最能体现 Python 优雅风格(Pythonic)的写法,是使用 defaultdict(list)

它的核心思路是:把每一个学生的名字作为键,把他们的所有成绩放进一个列表里。最后利用内置函数直接求和、求长度。

这是这道题的标准答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from collections import defaultdict

# 1. 创建一个字典,当遇到新名字时,自动给ta分配一个空列表 []
student_grades = defaultdict(list)

n = int(input())

# 2. 收集数据
for _ in range(n):
name, grade = input().split()
# 直接把成绩转化为整数,并追加到该学生的列表中,无需任何 if-else 判断
student_grades[name].append(int(grade))

# 3. 按字母顺序排序并计算输出
for name in sorted(student_grades.keys()):
scores = student_grades[name] # 取出该学生的所有成绩列表

# 告别 C 语言的 for 循环累加,直接使用 Python 内置的 sum() 和 len()
avg_grade = sum(scores) / len(scores)

print(f"{name} {avg_grade:.2f}")

1. defaultdict 的本体是什么?

只要你写下 defaultdict(...),你就是在创建一个字典。它和普通字典 {} 的结构一样,都是由“键-值对”(Key-Value)组成的。

2. 括号里的参数(intlist)决定了什么?

括号里的值,决定的不是这个容器本身是什么,而是决定了:当这个字典遇到一个不存在的键时,它自动补上的“默认值”是什么。

  • defaultdict(int)
    • 动作:创建一个字典。当访问不存在的键时,自动调用 int(),默认补上一个整数 0
    • 长相:字典里的“值”都是数字,例如 {'Ivanov': 5, 'Petrov': 4}
    • 最佳用途计数、累加
  • defaultdict(list)
    • 动作:创建一个字典。当访问不存在的键时,自动调用 list(),默认补上一个空列表 []
    • 长相:字典里的“值”都是列表,例如 {'Ivanov': [5, 3], 'Petrov': [4]}
    • 最佳用途分组、收集数据。(像标准答案里把同一个人的所有成绩装进同一个列表里)。

3. 为什么这是标准答案?

  1. 逻辑极简:没有中间的计数器变量,没有初始化的判断语句。你读这段代码,就像读自然的英语逻辑一样顺畅:“提取名字和成绩、把成绩追加给这个人、排序并打印平均分”。
  2. 充分利用内置函数:在 C 语言里,为了计算数组的总和,需要写一个循环遍历累加;在 Python 里,直接把列表丢给 sum(scores),用 len(scores) 获取个数,底层由 C 语言实现的内置函数运行速度远快于你手写的 Python 循环。
  3. 数据结构清晰:在这个版本中,字典长这样:{'Ivanov': [5, 3, 5], 'Petrov': [4, 5], ...}。数据结构直接反映了客观世界的逻辑(一个人对应多个成绩),非常直观。