题目二

任务描述

本关任务:三天体能两天考试的制度还没执行多久,郑付就不满意了,认为应该三天考试两天体能,可是蔡若坤不同意。在王思远的提议下,两人准备采取投票实现民主决议。请你根据投票数据输出结果。

相关知识

完成本关,你需要具备以下知识。

  • 文件读取
  • 字符串格式化
  • 其他知识(之后公布)

文件读取

打开文件

使用open(path)函数返回一个文件对象

f = open('data.txt', 'r')
···
f.close()

记得关闭文件
推荐使用上下文管理器with,将会在文件使用完毕后自动关闭,随着学习的深入,你还会见识到它的更多运用场景。

with open('data.txt', 'r') as f:
    content = f.read()
读取文件
  • 读取所有内容,返回一个字符串
with open('data.txt', 'r') as f:
    content = f.read()
  • 读取一行
with open('data.txt', 'r') as f:
    line = f.readline()
  • 逐行遍历文件
with open('data.txt', 'r') as f:
    for line in f:
        print(line)
  • 读取所有行,返回列表
with open('data.txt', 'r') as f:
    lines = f.readlines()

编程要求

根据提示,在右侧编辑器补充代码,读取文件统计投票结果,并按票数从大到小输出。

测试说明

平台会对你编写的代码进行测试:

例如:
文件内容:

体能
体能
考试
体能
考试
体能
考试

预期输出:

总票数:7票
1.体能:4票
2.考试:3票

注意: 有些牛马既不愿意练体能,也不愿意考试,所以文件中可能会存在如自由活动之类的废票。

如果有无效票,输出结果如下:
测试文件:

体能
自由活动
考试
体能
自由活动
体能
考试
睡觉

预期输出:

总票数:8票
有效票:5票
1.体能:3票
2.考试:2票
无效票:3票
1.自由活动:2票
2.睡觉:1票

开始你的任务吧,祝你成功!

答案

分析题目和测试说明,需要计算总票数,对于有乱填的情况还需计算有效票无效票,几个if判断就能完成这一工作。第一个难点在于如何统计每个选项的票数,别忘了Python中有种二维数组叫字典,字典以键值对的形式储存数据,把每个选项做为键,每个选项的票数作为值,就能很简单地记录它们了。

复习一下字典的基本操作:

# 定义一个空字典
dic = {}
# 也可以使用函数生成一个字典
dic = dict()
# 给字典添加数据
dic['key'] = 'value'
# 修改字典键对应的值
dic['key'] = 'value1'
# 获取字典键对应的值
dic['key']

以上是本关会用到的字典最基础的一些操作,还有很多常用且强大的功能可自行阅读相关文档。

接下来实现计算总票数

# 首先定义两个字典用于储存有效票和无效票
# 有效票
pos_dic = {}
# 无效票
neg_dic = {}
# 打开文件
with open(path, 'r') as f:
# 读取文件,并把每行的内容保存到列表中
    votes = [x.strip() for x in f]
# 总票数就是列表的长度
total = len(votes)

讲解一下:

votes = [x.strip() for x in f]

这里用到的是列表生成式,用于简化代码,底层相当于:

votes = []
for x in f:
    vote.append(x.strip())

看到这里,你翻了翻之前的测试文件:

体能
体能
考试
体能
考试
体能
考试

你也许会想,为什么要搞那么复杂,不是要生成一个储存每个选项的列表吗?直接用f.readlines()不就完了吗。注意,这是一个很容易出错的地方,在很多情况下如果不注意的话你就会发现写入或读取的数据跟你预想的不太一样。因为每一行的末尾都会有一个\n换行符,为了之后使用方便,需要将这个换行符给去掉。

字符串对象有一个方法叫strip(),作用是去掉字符串两端的指定字符串并返回,如果不指定参数就是去掉空白字符\n\t\r、空格等)。与之类似的还有去掉坐端字符串lstrip()和右端rstrip()

下一步要在字典中储存各票的数量。

  • 第一种方法:利用集合
# 将列表转换乘集合,利用集合的互异性获取到所有的票
votes_set = set(votes)
# 遍历集合,计算出每票的票数储存在字典中
for vote in votes_set:
    # 如果是有效票
    if vote in ('体能', '考试'):
        # 算出原列表中这个选项有多少票
        pos_dic[vote] = votes.count(vote)
        # 有效票加一
        pos_count += 1
    else:
        neg_dic[vote] = votes.count(vote)
  • 第二种方法:使用字典的赋值与更新

这里需要补充一个字典的知识点:get(key, default)

# 定义一个字典储存水果的价格
dic = {"西瓜": 10, "芒果": 15}
# 获取西瓜的价格并打印
print(f'西瓜:{dic["西瓜"]}元')
# 获取葡萄的价格并打印
print(f'葡萄:{dic["葡萄"]}元') # 报错KeyError

如果要获取到字典中没有的键的值就会报错,但使用字典的get()方法就不会。

price = dic.get("葡萄", 0)
print(f'葡萄:{price}元') # 葡萄:0元

get(key, default)有两个参数,get是你要访问的键,default是如果字典没有这个键返回的值,上面的例子中因为没有葡萄这个键,所有就会返回你自己设置的值0。之后你就会知道这个方法的作用。

# 遍历每一票
for vote in votes:
    # 如果是有效票
    if vote in ('体能', '考试'):
        # 获取字典中这个选项有多少票(如果字典中不存在这个键就默认为0)并加上一
        pos_dic[vote] = pos_dic.get(vote, 0) + 1
        pos_count += 1
    else:
        neg_dic[vote] = neg_dic.get(vote, 0) + 1

离成功就差一步了,观察两种情况的输出结果,看看我们还差什么

  • 如果没有无效票
总票数:7票
1.体能:4票
2.考试:3票
  • 如果有无效票
总票数:8票
有效票:5票
1.体能:3票
2.考试:2票
无效票:3票
1.自由活动:2票
2.睡觉:1票

可以看到最开始都需要输出一个总票数:x票,对于有效票和无效票都需要根据票数进行从大到小排序。

在这里为了提高代码的重用性,定义一个函数,它能够把具体的投票结果排好序并格式化

1.体能:4票
2.考试:3票
···
# 先把结果的第一行弄出来,之后在之后加内容即可
result = f'总票数:{total}票'
# 获取有效票的最终结果
pos_result = sort(pos_dic)
# 如果有无效票
if neg_dic:

    # 获取无效票的最终结果
    neg_result = sort(neg_dic)

    # 拼接结果
    result += f'\n有效票:{pos_count}票'+pos_result+f'\n无效票:{total-pos_count}票'+neg_result
# 如果没有无效票
else:
    # 拼接结果
    result += pos_result

# 返回结果
return result

思路很清晰,但是这里很容易出错,因为有很多换行符,仔细一点就行了。

最后只需要实现sort()函数了

要让这个函数对选项进行排序,肯定需要把对应的字典作为参数传入。

def sort(dic):
    # 生成一个由字典键值对元组组成的列表
    lst = list(dic.items())
    # 对这个列表排序
    lst.sort(key=lambda x: x[1], reverse=True)
    # 初始化结果
    result = ''
    # 排名计数
    rank = 1
    # 遍历每一票,拼接结果
    for choice, number in lst:
        result += f'\n{rank}.{choice}:{number}票'
        rank += 1
    return result

方法items()会将字典的所有的键值对元组构成的序列,将这个序列转化为列表[(key1, value1), (key2, value2)...]并返回

方法sort(key, reverse)对列表进行排序,直接对原列表进行操作,没有返回值,参数key为一个函数,将根据这个函数的返回值对列表进行排序,reverse为一个bool值,True或者False,True为从大到小,False为从小到大(默认)。两个参数都为可选

现在对sort()方法具体讲解:

  • 不加参数
# 先定义一个列表
l = [2, 5, 6, 10, 3]
l.sort()
print(l) # [2, 3, 5, 6, 10]
  • 倒序
# 再定义一个列表
l2 = [2, 5, 6, 10, 3]
l2.sort(reverse=True)
print(l) # [10, 6, 5, 3, 2]
  • key
# 再来一个列表
l3 = [6, -5, 4, 2, -3, 1]
# 如果想根据特定的规则对它进行排序,用key就对了
# 比如说根据绝对值对其进行排序
l3.sort(key=abs)
print(l3) # [1, 2, -3, 4, -5, 6]
# 再来个例子
l4 = [(1, 5), (2, 4), (3, 3), (4, 7), (5, 2)]
# 如果要对这个列表进行排序,如果不用key参数那就没办法了
l4.sort() # 不会发生任何变化,因为Python解释器不知道如何对其排序
# 这里用每个元组的第二个元素来排序
# 先定义一个函数,返回元组的第二个元素
def get_sec(tuple):
    return tuple[1]
l4.sort(key=get_sec)
print(l4) # [(5, 2), (3, 3), (2, 4), (1, 5), (4, 7)]
# 上面的get_sec()函数只会在对列表排序的时候用到,没必要单独定义一个函数
# 可使用匿名函数简化代码
l4.sort(key=lambda x: x[1])

[get]篇幅所限,关于匿名函数的具体内容,还有高阶函数、函数的高级运用、字符串的高级运用、生成式、可迭代序列等内容我会另外开个专题进行讲解[/get]

下面放出本关的完整代码

  • 法一
def get_result(path):
    pos_dic = {}
    neg_dic = {}
    with open(path, 'r') as f:
        votes = [x.strip() for x in f]
    total = len(votes)
    pos_count = 0

    # 此处也可选择集合的方法
    for vote in votes:
        if vote in ('体能', '考试'):
            pos_dic[vote] = pos_dic.get(vote, 0) + 1
            pos_count += 1
        else:
            neg_dic[vote] = neg_dic.get(vote, 0) + 1
    result = f'总票数:{total}票'
    pos_result = sort(pos_dic)
    if neg_dic:
        neg_result = sort(neg_dic)
        result += f'\n有效票:{pos_count}票'+pos_result+f'\n无效票:{total-pos_count}票'+neg_result
    else:
        result += pos_result

    return result

def sort(dic):
    lst = list(dic.items())
    lst.sort(key=lambda x: x[1], reverse=True)
    result = ''
    rank = 1
    for choice, number in lst:
        result += f'\n{rank}.{choice}:{number}票'
        rank += 1
    return result