查看全课大纲(第 28 / 51 节)
  1. 1.为什么人人都该学点编程?
  2. 2.计算机与程序设计:你的电脑到底在“想”什么?
  3. 3.Python 这门语言,到底有什么魔力?
  4. 4.Python 开发环境配置:如何拥有你的第一个“编程工具箱”?
  5. 5.如何安装一个更强大的代码编辑器?
  6. 6.汇率兑换1.0:你的第一个“钱生钱”小程序
  7. 7.你的第一行代码,藏着哪些秘密?
  8. 8.汇率兑换2.0(上):让程序看懂“CNY”和“USD”
  9. 9.汇率兑换程序,如何让它“聪明”地判断币种?
  10. 10.汇率兑换程序能一直运行,直到我喊停吗?
  11. 11.汇率兑换4.0:如何把“算钱”功能打包成积木?
  12. 12.汇率兑换5.0:给你的程序穿上“结构化”的西装
  13. 13.怎么用 Python 画一个会动的五角星?
  14. 14.如何让程序自动画出不同大小的五角星?
  15. 15.函数自己调用自己,会发生什么?
  16. 16.用递归画出神奇的分形树
  17. 17.Python 怎么计算你一天躺着不动能消耗多少热量?
  18. 18.如何让程序“活”起来,持续为你服务?
  19. 19.如何让 BMR 计算器更“聪明”?
  20. 20.当程序遇到“不按套路出牌”的用户怎么办?
  21. 21.怎么用 Python 模拟一个“滚雪球”式的存钱计划?
  22. 22.如何用列表记录每一周的存款?
  23. 23.如何让程序自己数数?—— 用 for 循环解放你的双手
  24. 24.52周存钱挑战4.0:你的存款计算器能有多灵活?
  25. 25.52周存钱挑战:怎么根据日期自动查存款?
  26. 26.Python 怎么判断一年中的第几天?
  27. 27.列表 vs 元组:哪个更适合存储月份天数?
  28. 28.用集合给月份分个队,换个思路算天数
  29. 29.把月份和天数“锁”在一起:用字典给数据配个对
  30. 30.Python 怎么判断密码强弱?
  31. 31.Python 怎么给密码设置“防沉迷”系统?
  32. 32.Python 怎么把密码保存到文件里?
  33. 33.Python 怎么读取文件里的密码?
  34. 34.Python 怎么把密码检查工具“打包”起来?
  35. 35.Python 怎么把文件操作也“打包”起来?
  36. 36.计算机怎么“扔骰子”?——用Python模拟随机事件
  37. 37.如何用 Python 模拟掷两个骰子?
  38. 38.怎么用 Python 把骰子轨迹画出来?
  39. 39.骰子点数分布长啥样?用直方图一眼看穿!
  40. 40.用科学计算库让骰子程序“起飞”
  41. 41.空气质量指数怎么算?手把手教你写一个AQI计算器
  42. 42.空气质量指数计算:从零开始,让程序看懂空气
  43. 43.如何读取空气质量数据文件并找出空气质量最好的五个监测点?
  44. 44.JSON 数据怎么变成 Excel 表格?—— 让数据换个“马甲”
  45. 45.Python 怎么让程序自己判断文件类型?
  46. 46.如何让程序自己上网查空气质量?
  47. 47.用美味的汤来解析网页:Beautiful Soup 入门
  48. 48.如何让爬虫自动抓取全国所有城市的空气质量?
  49. 49.爬到的数据怎么存下来?—— 把全国空气质量数据保存为CSV文件
  50. 50.用 pandas 玩转空气质量数据:找出全国空气最好和最差的城市
  51. 51.空气质量数据怎么“洗”干净,还能画成图?

用集合给月份分个队,换个思路算天数

约 15 分钟

小象实战讲义 · Python 入门:8 个小项目边玩边学

还记得我们之前是怎么算天数的吗?我们用一个列表(或元组)把每个月的天数按顺序存起来,然后累加。这就像你有一个日历,一页一页翻过去,把天数加起来。

这一节,我们要玩点不一样的!我们来给月份重新“分个队”:把所有有31天的月份放到一个“篮子里”,把所有有30天的月份放到另一个“篮子里”。然后,我们只需要判断每个月份属于哪个“篮子”,就知道该加多少天了。这个神奇的“篮子”,就是Python里的集合(set)。

学完这一节,你的“判断第几天”程序将拥有第三种解法,你的编程工具箱里又会多一个趁手的工具——集合。

💡 核心导读

  • 认识新朋友:集合:一种不重复、无顺序的数据容器,就像数学里的集合。
  • 集合的魔法操作:求差集、交集、并集,和数学课上学的一模一样。
  • 换个思路解决问题:把月份按天数分组,用集合的“成员检查”来快速判断。
  • 修复一个“小陷阱”:在累加天数时,别忘了把“当天”也加进去。

集合:一个不装重复数据的“篮子”

我们已经认识了列表(list)和元组(tuple),它们都能装下一系列数据。今天要认识的集合(set),和它们有两个最大的不同:

  1. 集合里的元素不能重复。你往集合里放两个“苹果”,它最终只会保留一个。
  2. 集合里的元素没有顺序。你不能说“我要集合里的第3个元素”,因为它压根就没排第几。

这和我们数学里学的“集合”概念几乎一样。它特别适合用来做两件事:快速判断一个东西在不在这个组里,以及给一组数据去重。

怎么创建一个集合呢?很简单,用 set() 函数,或者直接用花括号 {} 把元素括起来。

# 创建一个集合
fruits = {'apple', 'banana', 'orange'}
print(sorted(fruits))  # sorted() 会把集合排序后转成列表:['apple', 'banana', 'orange']

# 用set()函数从列表转换
number_list = [1, 2, 2, 3, 3, 3]
number_set = set(number_list)
print(sorted(number_set))  # 输出排好序的列表:[1, 2, 3],重复的都被去掉了!
['apple', 'banana', 'orange']
[1, 2, 3]

本节新增了什么? 没错,这一节的主角就是集合 set 这个新数据类型!我们用它来给月份重新分类。

集合的“社交”操作:并集、交集、差集

集合之间可以像数学集合一样进行运算,非常直观。

假设我们有两个集合:

  • S = {1, 3, 5, 7, 9}
  • T = {1, 2, 3, 4, 5}
S = {1, 3, 5, 7, 9}
T = {1, 2, 3, 4, 5}

# 差集:在S中但不在T中 (S - T)
print("差集 S - T:", S - T)  # 输出:{9, 7}
# 也可以用方法
print("差集 S.difference(T):", S.difference(T))  # 输出:{9, 7}

# 交集:既在S中又在T中 (S & T)
print("交集 S & T:", S & T)  # 输出:{1, 3, 5}
print("交集 S.intersection(T):", S.intersection(T))  # 输出:{1, 3, 5}

# 并集:S和T中所有的元素 (S | T)
print("并集 S | T:", S | T)  # 输出:{1, 2, 3, 4, 5, 7, 9}
print("并集 S.union(T):", S.union(T))  # 输出:{1, 2, 3, 4, 5, 7, 9}

# 对称差集:在S或T中,但不同时在两者中 (S ^ T)
print("对称差集 S ^ T:", S ^ T)  # 输出:{2, 4, 7, 9}
差集 S - T: {9, 7}
差集 S.difference(T): {9, 7}
交集 S & T: {1, 3, 5}
交集 S.intersection(T): {1, 3, 5}
并集 S | T: {1, 2, 3, 4, 5, 7, 9}
并集 S.union(T): {1, 2, 3, 4, 5, 7, 9}
对称差集 S ^ T: {2, 4, 7, 9}

这些操作在我们这个项目里暂时用不上,但它们是集合非常强大的功能,以后你会经常遇到。

实战:用集合重新构思“判断第几天”

好了,理论说再多不如动手。我们怎么用集合来改造我们的程序呢?

核心思路:我们创建两个集合,一个装所有有31天的月份,另一个装所有有30天的月份。二月份比较特殊,我们单独处理。

然后,对于输入月份之前的每一个月(比如输入3月,就看1月和2月),我们检查它属于哪个集合:

  • 如果在“31天集合”里,总天数就加31。
  • 如果在“30天集合”里,总天数就加30。
  • 如果都不在(那只能是2月了),就先按平年加28天。

最后,再判断是否是闰年且月份大于2月,如果是,总天数再加1。

听起来是不是很清晰?我们来看看代码怎么写。

# 判断第几天 v3.0 - 使用集合
from datetime import datetime

def is_leap_year(year):
    """判断是否为闰年"""
    return (year % 400 == 0) or ((year % 4 == 0) and (year % 100 != 0))

# 原程序这里使用 input() 获取用户输入,为了你能直接运行,我们用一个固定日期代替
# 你可以将下面这行改为你自己的日期,格式必须是 yyyy/mm/dd
date_str = '2018/03/05'  # 例如:2018年3月5日

# 解析日期字符串为datetime对象,方便获取年、月、日
input_date = datetime.strptime(date_str, '%Y/%m/%d')
year = input_date.year
month = input_date.month
day = input_date.day

# 核心变化在这里!我们创建两个集合来给月份"分篮"
_30_days_month_set = {4, 6, 9, 11}      # 有30天的月份集合
_31_days_month_set = {1, 3, 5, 7, 8, 10, 12} # 有31天的月份集合

# 初始化总天数,注意!这里一定要把"当天"的天数先加上
days = day

# 循环累加之前月份的天数
for i in range(1, month):  # range(1, month) 会产生 1, 2, ..., month-1
    if i in _30_days_month_set:
        days += 30
    elif i in _31_days_month_set:
        days += 31
    else:
        # 既不在30天集合,也不在31天集合,那只能是2月了
        days += 28

# 闰年且月份大于2月的特殊处理
if is_leap_year(year) and month > 2:
    days += 1

print(f'{date_str} 是{year}年的第{days}天。')
2018/03/05 是2018年的第64天。

看,if i in _30_days_month_set: 这行代码就是集合的魔力!它用 in 关键字快速检查月份 i 是否在30天的集合里,非常直观和高效。

⚠️ 注意那个“小陷阱”:在最初的版本中,我们很容易忘记把输入日期中的“日”(day)加到总天数 days 里,导致结果总是少几天。所以初始化 days = 0 之后,一定要立刻 days += day。

思维升级:从“顺序查找”到“分组判断”

对比一下我们学过的三种方法:

  1. v1.0 用元组:把每月天数按顺序存好,直接按索引取。
  2. v2.0 用列表:和元组几乎一样,但列表可变。
  3. v3.0 用集合:不关心月份顺序,只关心它属于哪一组(30天组还是31天组)。

v3.0的思维是一种逆向思维。前两种方法是“我知道每个位置(月份)对应多少天”,而集合方法是“我知道多少天对应哪些月份”。哪种更好?没有绝对答案,但掌握多种思路,能让你在遇到问题时更加灵活。

📝 动手练一练

  1. 集合去重实验:请你写一段代码,将一个包含重复元素的列表 [5, 2, 5, 8, 2, 1, 8, 8] 转换成集合,看看结果是什么。然后再将这个集合转换回列表,观察列表的变化。

  2. 月份集合操作:我们定义了 _31_days_month_set 和 _30_days_month_set。请你思考一下,这两个集合的并集(union)应该是什么?写代码验证你的想法。那么,如果用一个集合表示“非31天的月份”,该怎么用集合运算得到?

  3. “陷阱”重现与修复:故意将上面v3.0程序中的 days = day 改成 days = 0,然后计算 2018/01/05 是第几天。运行程序,观察结果错在哪里。这能帮你深刻理解为什么那个初始化步骤如此重要。

👉 点击查看第1题参考答案

思路:利用集合自动去重的特性,先将列表转为集合,再将集合转回列表,即可得到无重复元素的列表。

original_list = [5, 2, 5, 8, 2, 1, 8, 8]
print("原始列表:", original_list)

# 转换为集合,自动去重
unique_set = set(original_list)
print("转换后的集合:", unique_set) # 顺序可能不同,如 {1, 2, 5, 8}

# 再转换回列表
new_list = list(unique_set)
print("去重后的列表:", new_list) # 如 [8, 1, 2, 5]
原始列表: [5, 2, 5, 8, 2, 1, 8, 8]
转换后的集合: {8, 1, 2, 5}
去重后的列表: [8, 1, 2, 5]
👉 点击查看第2题参考答案

思路:一年中除了2月,就是30天或31天的月份。所以这两个集合的并集应该包含1-12月除了2月以外的所有月份。“非31天的月份”就是全年月份集合减去“31天月份集合”,但要注意2月。

_31_days_month_set = {1, 3, 5, 7, 8, 10, 12}
_30_days_month_set = {4, 6, 9, 11}

# 1. 求并集:所有30天和31天的月份
union_set = _31_days_month_set | _30_days_month_set
print("30天与31天月份的并集:", union_set) # 输出:{1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}

# 2. "非31天的月份" = 全年月份 - 31天月份
all_months_set = set(range(1, 13)) # {1,2,3,...,12}
non_31_days_set = all_months_set - _31_days_month_set
print("非31天的月份:", non_31_days_set) # 输出:{2, 4, 6, 9, 11}
30天与31天月份的并集: {1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
非31天的月份: {2, 4, 6, 9, 11}

本章小结

恭喜!你又解锁了一种新的数据容器——集合(set),并用它以一种全新的分组思维解决了“判断第几天”的问题。

📋 行动清单

  • 运行今天的v3.0代码,尝试几个不同的日期,确保结果正确。
  • 完成动手练习题,特别是体验一下集合去重和集合运算。
  • 思考一下:如果让你设计一个程序,统计一篇文章里出现了哪些不同的单词,用集合是不是非常合适?

我们已经用元组、列表、集合三种方式解决了同一个问题。Python里还有一种非常强大、几乎每天都会用到的“键值对”容器,它能将月份和天数更直观地关联起来。想知道是什么吗?我们下节揭晓!

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第6章代码包:判断第几天(含全部版本 4 个 .py)
    ZIP · 3KB
    下载
看图文没弄懂?展开看本节视频讲解(约 15 分钟)
📺 正在播放小象学院官方高清录播(支持倍速与清晰度调节)