资讯详情 Python实验一:排列、素数、四叶玫瑰数与乘法表全解析
📅 2026/10/11 23:08:04
1. Python实验一的核心问题拆解与整体设计思路先说说这道实验题本身。它看起来是四个独立的小任务但放在一起其实是精心设计的一套组合拳三位数排列、素数判断、四叶玫瑰数、九九乘法表四个题目覆盖了Python基础语法里最常用的几个能力点——循环嵌套、条件分支、列表操作、函数封装、字符串格式化。作为初学者把这四个任务完整做一遍基本就能把Python的基础语法脉络打通。我自己在带新人入门的时候经常看到有人拿到这组题目就直接开写然后在第二个素数题上卡住又在第三个四叶玫瑰数上绕进死胡同。问题往往不是不会写代码而是没有先做需求拆解。比如“互不相同且无重复数字的三位数”这句话的意思是把1、2、3、4这四个数字全排列成三位数不允许重复使用同一个数字而“四叶玫瑰数”则是四位数的自幂数需要先理解“自幂数”这个数学概念。如果题目理解偏了后面代码再怎么调都是错的。所以这篇博文我会按照“先分析需求→再设计思路→然后给参考代码→最后补调试经验”的顺序来写尽量把这组实验题讲透。适合的人群很明确刚学Python、正在刷基础练习题的同学以及需要带实验课、想给学生讲清楚解题思路的助教或者老师。当然如果你只是想把这四个任务快速跑通也可以直接参考文中的代码段。2. 任务一从1、2、3、4中生成互不相同且无重复数字的三位数2.1 需求语义的准确理解“有1、2、3、4个数字能组成多少个互不相同且无重复数字的三位数分别是什么”这句话看起来简单但里面藏了两个关键约束互不相同三个位置上的数字不能有重复即百位、十位、个位各不相同比如123可以122不行。无重复数字这里的“重复”不是指与已有的三位数比较而是指数字本身的重复使用。本质上它是从四个不同数字中选取三个进行排列排列数应该是 P(4,3)4×3×224而不是组合。2.2 三重循环枚举法实现最直接的思路是用三重循环把百位、十位、个位分别从1到4枚举一遍然后在内层判断三个数字是否互不相同。第一次写这个题目我建议就用这种最笨的枚举法因为它的逻辑最直观也最容易和“排列组合”的数学概念对应上。count 0 for hundred in range(1, 5): for ten in range(1, 5): for one in range(1, 5): if hundred ! ten and ten ! one and hundred ! one: num hundred * 100 ten * 10 one print(num, end ) count 1 print(\n共有, count, 个三位数)这段代码用三层循环模拟了百位、十位、个位的选择过程每次判断用 and 连接三个不等式条件。运行后输出的就是全部24个数字。需要注意的细节是判断条件里最好不要写成hundred ! ten ! one这种链式比较虽然Python是支持的但在早期学习阶段容易造成理解混乱还是逐条写清楚比较稳妥。2.3 用列表推导式和itertools的进阶写法当你对基本的循环写法完全吃透以后可以看看下面这两种写法它们能帮你更好地理解Python“代码即表达”的风格。第一种是用列表推导式配合条件过滤nums [ h * 100 t * 10 o for h in range(1, 5) for t in range(1, 5) for o in range(1, 5) if h ! t and t ! o and h ! o ] print(len(nums), nums)第二种是直接使用标准库itertools里的permutations函数它对可迭代对象生成指定长度的排列天然就能保证元素不重复from itertools import permutations nums [h * 100 t * 10 o for h, t, o in permutations(range(1, 5), 3)] print(len(nums), nums)我当时带实验课的时候要求是先写循环版本理解透了再练习列表推导式最后再尝试permutations。原因很简单列表推导式写起来很爽但如果连基础循环都还没掌握一旦出错就可能完全不知道该从哪里排查。提示如果题目改成“0到9中取三个数字组成无重复三位数”那百位就不能为0需要额外加一个h ! 0的判断这是一个常见变形考点。3. 任务二判断素数并输出指定范围内的全部素数3.1 什么是素数判断依据是什么素数的定义很明确大于1的自然数中除了1和它本身以外不再有其他因数的数。所以2是最小的素数1不算素数0和负数也不在讨论范围内。判断一个数 n 是不是素数最朴素的思路就是从2开始一直试除到 n-1看是否存在能整除 n 的数。这种做法的数学原理很简单但问题是当 n 很大时循环次数会非常大。于是就有了第一个优化点一个合数必有小于等于其平方根的因数。假设 n a × b如果 a 和 b 都大于 sqrt(n)那么 a × b 就会大于 n矛盾。所以只需要检查2到sqrt(n)之间的整数是否整除 n 就够了。同时还有一个常用优化2单独判断然后从3开始只检查奇数。3.2 基础版判断函数与输出对于实验一的难度其实只需要一个能判断单个数字是否为素数的函数再加一个循环就能完成任务要求。下面这个版本我留了两个关键细节值得细看import math def is_prime(num): if num 2: return False if num 2: return True if num % 2 0: return False limit int(math.sqrt(num)) 1 for i in range(3, limit, 2): if num % i 0: return False return True # 输出100以内的素数 primes [n for n in range(2, 101) if is_prime(n)] print(primes)这里先用num 2做特判再把所有偶数排除最后只用奇数做试除。我见过不少初学者喜欢把limit直接写成int(math.sqrt(num))少了后面的1。实际上当数字的平方根正好是整数时比如9的平方根是3不加1会导致3没有被检查到9就被误判成素数了。这是一个非常经典的边界问题。3.3 埃拉托斯特尼筛法批量生成素数的利器如果实验题目要求输出某个区间内所有素数一次一次调用is_prime固然没问题但如果有大量数字需要判断性能就有点难看。这时候就可以用筛法。它的核心思想是从2开始把每个素数的倍数都筛掉剩下的自然就是素数。def sieve_of_eratosthenes(limit): is_prime [True] * (limit 1) is_prime[0] is_prime[1] False for i in range(2, int(limit ** 0.5) 1): if is_prime[i]: for j in range(i * i, limit 1, i): is_prime[j] False return [i for i, prime in enumerate(is_prime) if prime] print(sieve_of_eratosthenes(100))筛法的效率比单个判断要高不少特别是当区间上限达到几十万甚至上百万时体验差距非常明显。不过我还是想强调一句实验题不会要求你用筛法但如果你能在作业里主动用上并且能在注释里说明原理这通常会是一个加分项。补充一个容易踩的坑筛法里的内层循环起始位置应该从i * i开始而不是2 * i。因为比i * i小的倍数例如 2i、3i 等已经在处理更小素数的时候被筛掉了多筛数次会影响效率。另外列表下标从0开始所以is_prime[0]和is_prime[1]必须手动设为False否则会把0和1当素数输出。4. 任务三打印“四叶玫瑰数”——理解自幂数与循环嵌套4.1 自幂数的概念与四位数的特例数学上有一种数叫“自幂数”也叫“阿姆斯特朗数”指的是一个 n 位数它的各位数字的 n 次方之和恰好等于它本身。三位数的自幂数叫“水仙花数”比如 153 1³ 5³ 3³。四位数的自幂数就叫“四叶玫瑰数”比如 1634 1⁴ 6⁴ 3⁴ 4⁴。所以这个任务本质上要求的是遍历所有四位数把每个数拆成四个数字分别求四次方再求和判断结果是否与原数相等。四位数只有9000个每个数字拆分的计算量也很小所以用最直接的方法就能完成任务根本不用做什么额外优化。4.2 从整数拆分到幂次计算拆分数位有两种常见方式。第一种是数学运算法利用整除和取余不断分离出每个位上的数字num 1634 thousands num // 1000 hundreds (num // 100) % 10 tens (num // 10) % 10 ones num % 10第二种方法是把数字转成字符串然后逐字符取出来再转回整数代码看起来更简洁但也更容易让初学者忽略“字符”和“整数”的区别出错时不太好定位。实际上把 1634 转成字符串 1634再对每个字符.split()或直接索引取出最后用int()转换是完全没有问题的。4.3 完整参考代码与运行结果下面这个版本我认为比较贴近实验课的作业风格——结构清晰每行都有注释输出的内容直接可读def is_four_leaf_rose(num): # 拆分数位 a num // 1000 # 千位 b (num // 100) % 10 # 百位 c (num // 10) % 10 # 十位 d num % 10 # 个位 return a**4 b**4 c**4 d**4 num for i in range(1000, 10000): if is_four_leaf_rose(i): print(i)你跑一下就能得到四个结果1634、8208、9474。顺便提一句如果你把位数换成三位那就是打印水仙花数153、370、371、407这个变形很多老师会顺口留成课后作业原理完全一样改一下循环范围和幂次就行。这里还要强调一个基础概念Python里的**是幂运算符^在Python里是异或运算符而不是幂运算。我见过至少三四个初学的人把a**4写成a^4结果跑出来的全是错误的数还一度怀疑自己抄错了代码。这是新手比较容易踩的坑值得记住。4.4 字符串拆分的另一种写法如果你对字符串操作比较熟悉也可以这样写def is_four_leaf_rose_str(num): digits [int(ch) for ch in str(num)] return sum(d ** 4 for d in digits) num这种写法在处理任意位数的自幂数时会方便很多不需要手写整除和取余来拆位。不过我还是建议初学者先实现一遍纯数学运算的版本因为整除和取余是底层逻辑字符串转换只是语法糖。真正理解了第一种第二种才能一眼看穿它在干什么。注意四叶玫瑰数是四位数的专称请不要和三位数的“水仙花数”混用。作实验报告时概念定义写准确也是一个加分细节。5. 任务四统计数字出现次数——从数组遍历到字典统计5.1 任务描述与最简单的统计思路从标题里的“4.统计数”来看这不是一个独立的大题而是基于前面的数据结果做统计。以任务一为例常见的统计变形是用1、2、3、4这四个数字能组成多少个无重复的三位数其中各位数字之和为偶数的有多少个或者更常见的场景统计一个随机生成的数字列表里各个数字出现的次数。无论是哪种版本统计数字出现次数的核心操作就是两件事遍历数据、按值计数。最简单的实现就是语言里自带的计数方法或者自己建一个字典手动累加。在Python中最直观的写法是用字典import random nums [random.randint(0, 9) for _ in range(100)] counter {} for n in nums: counter[n] counter.get(n, 0) 1dict.get(key, default)是这里的关键它会在键不存在时返回默认值0然后加1再存回去。这一行代码同时完成了“初始化”和“累加”两个动作非常简洁。5.2 用collections.Counter一行实现统计如果允许使用标准库collections.Counter是专门干这个的from collections import Counter nums [1, 1, 2, 3, 3, 3, 4, 4, 4, 4] counter Counter(nums) print(counter) # 输出Counter({4: 4, 3: 3, 1: 2, 2: 1})需要说明的是Counter返回的对象本身也支持counter[5]这种访问方式如果某个数字不存在返回值是0而不是报错这一点比普通字典要友好。对于实验报告来说如果你能在统计题里主动使用标准库并解释它比手写字典好在哪也能让报告显得更有层次。5.3 按出现次数排序和输出前几名统计完了之后有时候还需要按出现次数排序比如找出出现最多的数字。这里有一个容易犯的错误是直接对counter本身用sorted()实际上应该对counter.items()排序sorted_items sorted(counter.items(), keylambda x: x[1], reverseTrue) print(sorted_items[:3])这行代码的排序逻辑是按第二项出现次数从高到低排列取前三个。配合“统计数”这个任务你可以把任务一生成的三位数全部拆成单个数统计每个数字出现的频率然后输出出现次数最高的数字——这样就能在同一份报告里把任务一和任务四串起来写显得整体思路很连贯。6. 任务五打印九九乘法表——嵌套循环与字符串格式化的完美练习6.1 需求拆解与两种布局方向九九乘法表真正练的是两件事外层循环控制行内层循环控制列以及对齐格式的处理。常见的输出样式有两种左下三角布局每行从1开始到当前行号结束和右上三角布局每行从行号开始到9结束左边补空格。对于实验题来说按左下三角输出就满足要求了。先把基本框架搭出来for i in range(1, 10): for j in range(1, i 1): print(f{j} * {i} {i * j}, end\t) print()这段代码的关键点有两个内层循环的范围是range(1, i 1)也就是第i行只输出前i列print()里的end\t用制表符对齐这样乘法算式之间能拉开间距。每行结束以后再执行一次空print()作用是换行。如果不用 f-string也可以写print(f{j} * {i} {i * j}, end\t)的等价旧式格式化版本print(str(j) * str(i) str(i*j), end\t)。旧式写法能帮你理解字符串拼接的底层层理但代码会显得很啰嗦我大概只在教学演示时会用旧写法。6.2 对齐问题的完美解决方案上面这版基本能跑通但有经验的读者一眼就能看出问题当i*j是个位数时格子宽度和其他两位数不一致当j*i小于10时字符没有右对齐看起来会有点乱。用制表符\t可以缓解但并不是所有终端环境显示效果都一致。更好的方案是利用 f-string 的对齐控制。print(f{j} * {i} {i*j:2}, end )里的:2表示右对齐并占据两个字符宽度。这样输出会整齐很多for i in range(1, 10): for j in range(1, i 1): print(f{j} * {i} {i*j:2}, end ) print()这里我用的是2意思是结果左对齐占两个字符整体打印出来也齐整美观。两种对齐方式都可以关键是让结果位的宽度固定这样不会因为结果的位数不同导致整列偏移。这是九九乘法表打印里最容易忽略、也最能体现代码审美的地方。注意如果要求输出成上三角样式也就是左上三角核心逻辑变成j in range(i, 10)但在每行开始前要补空格。你可以自己想一想上三角的第一行需要几个空格第二行呢这其实是个非常经典的找规律练习。6.3 将九九乘法表封装成函数到了这个阶段就可以把代码稍微工程化一点把输出乘法表封装成一个带参数函数方便控制打印范围def print_multiplication_table(rows9): for i in range(1, rows 1): for j in range(1, i 1): print(f{j} * {i} {i * j:2}, end ) print() print_multiplication_table() print_multiplication_table(6) # 只打印前6行rows参数是行数上限增加了灵活度。对于实验报告来说能写函数的题目尽量用函数封装至少每个任务一个函数这样的代码才符合“工程实践从入门就开始培养”的要求。我见过很多学生交上来的作业是一长串顺序执行的代码也能跑出结果但可读性和可维护性都差很多。从这些基础实验开始养成封装习惯后面学到类和模块时会省力不少。7. 完整实验代码一次跑通四个任务7.1 合并后的参考实现下面给出一个整合版参考代码每个任务用独立函数封装主程序部分依次调用。代码里我尽量加了足够的注释方便你在实验报告里截图或者直接贴代码段import math from itertools import permutations # 任务一1、2、3、4组成互不相同且无重复数字的三位数 def task1_no_repeat_three_digits(): nums [h * 100 t * 10 o for h, t, o in permutations(range(1, 5), 3)] print(任务一无重复三位数共有, len(nums), 个) print(nums) # 任务二判断素数并输出100以内的素数 def is_prime(num): if num 2: return False if num 2: return True if num % 2 0: return False limit int(math.sqrt(num)) 1 for i in range(3, limit, 2): if num % i 0: return False return True def task2_prime_list(): primes [n for n in range(2, 101) if is_prime(n)] print(任务二100以内的素数) print(primes) # 任务三打印四叶玫瑰数 def is_four_leaf_rose(num): a num // 1000 b (num // 100) % 10 c (num // 10) % 10 d num % 10 return a**4 b**4 c**4 d**4 num def task3_four_leaf_rose(): roses [i for i in range(1000, 10000) if is_four_leaf_rose(i)] print(任务三四叶玫瑰数) print(roses) # 任务四统计随机数字出现次数 from collections import Counter import random def task4_count_numbers(): random_nums [random.randint(0, 9) for _ in range(100)] counter Counter(random_nums) print(任务四随机生成的100个数字的统计结果) for digit in sorted(counter.keys()): print(f数字 {digit} 出现 {counter[digit]} 次) most_common counter.most_common(1)[0] print(f出现最多的数字是 {most_common[0]}共 {most_common[1]} 次) # 任务五打印九九乘法表 def task5_multiplication_table(): print(任务五九九乘法表) for i in range(1, 10): for j in range(1, i 1): print(f{j} * {i} {i * j:2}, end ) print() if __name__ __main__: task1_no_repeat_three_digits() print() task2_prime_list() print() task3_four_leaf_rose() print() task4_count_numbers() print() task5_multiplication_table()7.2 运行结果概览为了让你心里有底我把输出结果展开写一下。任务一输出的是24个三位数任务二输出的是100以内共25个素数任务三输出的四叶玫瑰数是三个任务四会根据随机结果变化任务五就是标准的九行乘法表。整体的输入输出结构和输出格式都是能直接交作业的水平。有一点要提醒由于任务四用了random生成数字每次运行的统计结果都不一样。如果你在实验报告里需要贴固定结果建议先运行一次把结果截图保存或者将种子设定为固定值random.seed(42)这样每次运行都一样打印出来也会更严谨。7.3 实验报告的版本管理经验写实验报告的时候很多同学喜欢只贴最终完整版代码不带过程版本。我个人不太推荐这种习惯因为课程老师更希望看到你的调试过程比如你的任务二一开始写的版本没有加math.sqrt优化后来改成了试除到平方根任务三一开始用字符串拆分写后来理解整除取余后改成了数学运算版。过程性代码片段能体现思考轨迹加上一两句“遇到的问题和解决方案”会让报告质量提升明显。8. 常见报错与调试实录8.1 NameError: name math is not defined这个报错出现频率超高原因是写了import math但没放在代码文件开头或者函数内部局部导入后另一个函数里用了math但忘了导入。解决办法是统一把所有 import 语句放在文件顶部。Python 虽然是解释型语言导入的位置比较自由但项目文件还是建议保持规范。8.2 IndentationError: unexpected indent这种错误绝大多数发生在复制粘贴代码时。比如从网页复制 Python 代码到编辑器里原本的缩进可能是空格但在帖子里是无序的或者你在某个循环块下面多按了一下 Tab。解决方法是始终用四个空格做缩进编辑器里把 Tab 自动转换成空格几乎所有主流编辑器都支持这个设置。需要注意的一点是Python 对缩进极其敏感同一个代码块里如果混用了 Tab 和空格编译阶段就会报错。这种错误比较隐蔽建议直接通过编辑器的“显示空格”功能排查。8.3 结果为空的循环如何排查任务三输出为空也是常见问题。我通常会用三步排查第一先确认is_four_leaf_rose(1634)单独调用返回的是True还是False第二检查拆位逻辑打印一下a,b,c,d看看是否得到了4、3、6、1第三检查幂运算是否写成了^。三步下来90% 的问题都能定位到。还有一个比较隐蔽的问题是range(1000, 10000)的取值范围。如果你不小心写成了range(1000, 1001)当然就只能测一个数字什么也打印不出来。这类错误从逻辑上完全合理但一运行就很泄气。所以遇到“没有输出的循环”第一步永远是确认循环的确切范围。8.4 九九乘法表输出不换行的问题如果打印出来的乘法表全部挤在同一行十有八九是print()里的end参数没有正确返回默认值。比如你把两个print混写成了print(..., end )和print()检查一下是不是漏了第二个print()的括号。这是一个极小但极常见的失误。还有一种情况是内层循环结束以后没有打印换行而是继续在原行用制表符分隔这也会导致一堆数字横向连排。解决方式仍然是那句老话外层循环结束时必须执行一次不带参数的print()。9. 一些亲测有效的学习建议和扩展方向9.1 从“能跑”到“写得更好”把这五个任务做出来只是第一步。同样的题目可以用不同的方法写我个人建议你把每种写法都试着实现一遍任务一分别用循环版、列表推导式版、itertools版任务二分别用暴力版、平方根优化版、埃氏筛版任务三分别用整除取余版和字符串转换版任务四分别用字典版和Counter版任务五分别用 f-string 版和format版。这个过程的意义不在于炫技而在于训练你对同一问题的多角度建模能力。不要小看这种练习等以后处理真实项目的时候你会感谢这些“基础功”——很多复杂的业务逻辑最终都可以被抽象成这几种基本模式。9.2 进一步扩展的方向学有余力的话建议做一些变形练习这比反复写同一个题有用得多把任务一改成“从0到9取n个数字排成n位数输出所有满足条件的排列”同时要考虑首位不能为0把任务二改成“输出前100个素数”“统计1到1000之间有多少个孪生素数对”把任务三改成“打印所有三位水仙花数、五位数五角星数”它们都属于自幂数家族的变体把任务四改成“不用 Counter只用字典手写计数并尝试对结果按次数排序”把任务五改成“输出上三角乘法表”“输出任意 n×n 的加法表”。这些扩展方向每一个都能让你再花半小时到一小时的时间而且做完了都能往实验报告“思考题”部分填内容属于一举两得的事情。9.3 我的个人体会这套实验题我前前后后带过好几轮学生有一个细节每次都会提做题前先把运行环境理顺。哪怕你只是用 IDLE 自带的编辑器也要保证代码文件的后缀是.py文件编码是 UTF-8如果你用 PyCharm一定要确认项目解释器选对了 Python 版本否则代码里用了 f-string 语法而解释器是 Python 3.5一运行就会直接报语法错误。很多人没把题目本身写错反而栽在这些环境问题上非常可惜。另外每次运行完以后我建议你把输出结果单独截图保存和代码放到同一个实验报告文件夹里。等你真正开始写报告的时候就知道这几张图能省多少时间了。这个习惯放到以后做项目、写技术文档也同样适用。最后说个我自己刚带新人时的经验判断素数那块一定把你一开始“从2试除到n-1”的初版代码保留在报告里再对比优化版本。老师看到的不只是你能写出答案更看重你有没有思考过程。代码本身只是结果思路才是真正值钱的东西。