Skip to content

2026-计算概论C-py作业5 ​

本次作业对应第5章《模块与库》,熟悉 turtle 的基本绘图函数与 random 随机数函数。

提交要求

完成下列 必做题 1、2,将 源代码和运行结果截图 提交到 教学网。截止时间为 2026 年 10 月 27 日(周二)中午 12:00(北京时间)。

本次在本地运行程序并提交到教学网,不在 OpenJudge 判题。另有 py作业4 于当天 13:00 在 OJ 截止,请区分两个时间。

返回本周上机课的课程作业

必做题 1:正方形螺旋线 ​

题目要求 ​

利用 turtle 库从外往里绘制以坐标原点为对称点的正方形螺旋线,如下图所示。

作业原图:水平边为黄色、垂直边为绿色的正方形螺旋线

  • 最大正方形的边长由用户输入,范围为 100–400,且必须是 偶数;程序需要判断输入是否符合要求。
  • 两个正方形之间的间距为 2。
  • 垂直边为绿色,水平边为黄色。
  • 可以使用 turtle.heading() 获取当前方向角度。

知识点与 PPT ​

第5章第10–16页:turtle 的窗体、坐标、画笔、运动和方向;第4章第4–8页:自定义函数、参数和返回值;第3章:分支判断与循环。

老师给出的程序框架 ​

下面的主函数和辅助函数按作业文档截图转写。需要补充 getMaxSide() 与 drawSquareHelix(length) 的具体实现。

python
import turtle as t


def initializeBrush(length):
    t.mode('standard')
    t.penup()
    t.goto(-length / 2, -length / 2)
    t.pendown()
    t.seth(90)


def recoverBrush():
    t.penup()
    t.goto(0, 0)
    t.pendown()
    t.seth(0)
    t.hideturtle()


def main():
    length = getMaxSide()
    t.setup(500, 500, 200, 200)
    initializeBrush(length)
    drawSquareHelix(length)
    recoverBrush()
    t.done()

这里是待补全的框架;先定义缺少的两个函数,再在文件末尾调用 main()。

解题提示

1. 输入条件要同时满足。 先确认输入能解释为整数,再检查 100 <= length <= 400 和 length % 2 == 0。输入非法时提醒并重新输入,合法后由 getMaxSide() 返回数值。不要只把 int(input()) 写完就认为已经检查了范围和奇偶。

2. 理解起点与方向。 (-length / 2, -length / 2) 是外框的左下角,t.seth(90) 表示朝上。移动到起点前抬笔,避免从原点多画出一条斜线。

heading 角度方向本题颜色
0右yellow
90上green
180左yellow
270下green

画每条边之前读取当前方向,设置 t.pencolor(...),再 t.fd(...),最后 t.right(90)。heading() 是查询方向,right(90) 才是转向。

3. 先在纸上追踪前几条边。 外侧从左下角朝上走,前两段长度均为 L;接下来的两段为 L-2,再接下来为 L-4。同一长度走两段,再缩短 2,保持从外向内的连续螺旋。可以先用较小的示意边长手算坐标,实际输入仍需满足 100–400。

4. 结束条件看剩余边长。 边长应保持正数;变成 0 后就停止,不要继续用负数让画笔往回走。recoverBrush() 只恢复画笔状态,不能代替绘图循环的结束条件。

易错点

  • 画成独立正方形: 每次画四条相同长度的边会闭合;本题要画一条连续向内的螺旋。
  • 每条边都减 2: 先检查前几段长度和起点轨迹,不能不追踪坐标就机械缩短。
  • 颜色晚设一步: 先设置颜色再画当前边;转弯后查到的是下一条边的方向。
  • 忘记调用 main 或 done: 定义函数不代表已经运行;t.done() 让绘图窗口保持打开。
  • 运行环境不对: turtle 需要支持图形窗口的本地 Python 环境。程序文件不要命名为 turtle.py,否则可能遮蔽标准库。

必做题 2:随机发红包 ​

题目要求 ​

应用随机数函数模拟微信发红包。用户只输入一次 红包总金额(整数)和红包数量;计算机随机分配每个红包的金额,每个红包至少 1 元,并给尚未分配的每个红包留出至少 1 元。

在相同总金额、相同红包数量下模拟 10 次,每次的红包结果存入一个列表,并输出 10 次结果。作业给出两种方法的主程序:方法 1 已提供;需要根据提示补全方法 2,并观察两种方法的结果。

知识点与 PPT ​

第5章第27–28页:random、randint、uniform 和随机种子;第4章第4–8页:函数参数与返回值;第6章列表知识:[]、append()、sum()。

老师给出的主程序 ​

文档主程序先读取红包数,再读取总金额;只读入一次,在循环里重复调用函数。每轮分别输出两种方法的结果,共 10 轮。

python
import random

number = int(input("红包总数:"))
totalMoney = int(input("红包总金额(整数):"))
for i in range(10):
    each1 = redEnvelope1(totalMoney, number)
    print(each1)
    each2 = redEnvelope2(totalMoney, number)
    print(each2)
    print()

调用前要先定义两个函数,并检查 number > 0、totalMoney >= number;否则无法保证每份至少 1 元。

方法 1:文档已提供的参考代码
python
def redEnvelope1(total, num):
    r = []
    already = 0
    for i in range(1, num):
        money = random.randint(1, (total - already) - (num - i))
        r.append(money)
        already += money
    r.append(total - already)
    return r

randint(a, b) 包含两个端点。第 i 次分配前,尚有 total - already 元;本次之后还剩 num - i 个红包,因此本次最多取“余额减去后续红包数”。最后一个领取所有余额,保证总额不变。这个顺序分配方式容易让较大的红包出现在前面。

方法 2:根据随机权重分配 ​

文档已经给出生成权重的部分,需要补全计算红包金额、处理最后一份并返回列表的部分:

python
def redEnvelope2(total, num):
    r = []
    n = []
    for i in range(num):
        n.append(random.uniform(1, 100))
    sn = sum(i for i in n)
    # 待补:根据权重计算前 num - 1 份红包
    # 待补:剩余金额分配给最后一份
    return r

解题提示

1. 随机数是权重,不直接是金额。 文档用 uniform(1, 100) 为每份生成一个正权重。某份的占比为“该权重 ÷ 所有权重之和”。正文中的“随机数函数 random()”是在介绍随机分配,给定框架实际使用 randint 和 uniform,实现时应理解各函数的区别。

2. 先满足最低金额,再按权重分配余额。 若把全部金额直接乘占比,较小权重可能分不到 1 元。例如 10 元分给 3 人,权重为 1、1、98,直接按比例只有 0.1、0.1、9.8 元。要同时满足题目“每份至少 1 元”,可以先给每份预留 1 元,再分配剩下的 total - num 元。

3. 用一个手算例子理解。 总金额 10 元、3 个红包,权重假设为 1、2、4:预留 3 元后剩 7 元,按权重分成 1、2、4 元;加上各自的保底,结果为 2、3、5 元,总额仍为 10 元。这是便于推演的示例,程序的权重由随机函数生成。

4. 取整与余款要一起考虑。 文档明确总金额为整数,未限定每份必须整数还是保留两位。可以沿方法 1 使用整数元,也可以使用“分”保存金额再显示两位小数。采用整数元时,对前 num-1 份的额外分配向下取整,最后一份由总额减去已发金额计算;采用“分”时,每份先预留 100 分,后续同样按整数处理。不要每份独立 round 后就直接返回,舍入可能使总和偏离总额。

5. 每次调用都重新开始。 结果列表、权重列表、已发金额都在函数内部初始化。输入放在 10 次循环外,函数调用放在循环内;不要每轮让用户重新输入,也不要在每次调用中重新设置同一个随机种子,否则每轮结果可能完全相同。

易错点

  • random.uniform() 生成浮点数;random.randint() 生成整数,两个函数不是同一种输出。
  • append() 修改列表,返回 None;写 r = r.append(money) 会丢掉原来的列表。
  • 最后一份应使用余额,不能再独立随机抽取;否则总额不一定正确。
  • 检查每次结果的 长度、最小金额和总和:分别是 num、至少 1 元、等于 total。总金额恰好等于红包数时只能全是 1 元,这种情况没有金额随机变化是正常的。
  • 用随机权重不等于所有合法分配方案“等概率”;最后一份接收余款也会有取整影响,不应把随机性描述为严格公平。

练习题 1:自动出题并判分 ​

在第5章 random 模块示例 1 的基础上出 10 道题,包含除法和乘方。一般题目答对得 1 分;结果为负数的减法题、除法题、乘方题,每题答对多得 1 分。最后输出答对的题数与总得分。

知识点与 PPT: 第5章第29页(自动出题示例)、第27页(随机选择);第2章数字运算;第3章循环与分支。

解题提示

  • 用两个变量分别统计“答对题数”和“得分”。答对一题,前者加 1;得分根据题型加 1 或 2。答错不加分。
  • 先随机生成操作数与运算符,再计算正确结果。Python 乘方用 **,不是 ^。
  • 加分条件为:减法且结果小于 0,或除法,或乘方。不要让所有减法都得 2 分,也不要把“负数减法”的条件套到除法和乘方上。
  • 除法的除数不能为 0。课件原例操作数为 1–9,沿用此范围即可避开;若修改范围,需要相应检查。
  • 本题没有规定除法回答保留几位。程序出题时应先明确约定,例如提示保留两位,再按同样精度比较;不要把自行采用的规则写成原题要求。
  • 先将每种题型各手动检查一次,再加入随机选择;固定随机种子可用于调试,但不要在每轮出题前重复设同一个种子。

练习题 2:同心圆 ​

自定义函数 drawMCircle(radius, dist, num):radius 为最小圆半径,dist 为相邻圆半径之差,num 为同心圆数量。使用 turtle.circle() 绘制同心圆,关键是确定每个圆的起笔位置。

知识点与 PPT: 第5章第12–16页(坐标、移动与画圆);第4章参数与函数调用。

解题提示

第 i 个圆的半径为 radius + i * dist,其中 i 从 0 开始。若希望圆心在原点,在 standard 模式下,可先抬笔移到 (0, -r),将方向设为 0(朝右),落笔后调用 circle(r)。正半径的圆心在画笔左侧,此时恰好是原点。

每画一个圆都重新确定位置和朝向;如果始终站在原点朝右画不同半径的圆,圆心会位于不同的 (0, r),得到的不是同心圆。抬笔移动还可以避免圆之间出现连接线。

练习题 3:随机多边形装饰画 ​

定义一个绘制正多边形的函数,随机生成边数、边长、颜色与位置,组合成抽象装饰画;还可以将数学曲线定义成函数并加入图案。

原文函数签名为 polygon(a, size, color),说明中称“正 n 边形”。这里将 a 解释为边数 n,保持参数含义一致:边数、边长和填充色。

知识点与 PPT: 第5章第14–16页(画笔、运动、方向)、第27–28页(随机数和随机选择);第4章自定义函数。

解题提示

  • 边数至少为 3。画一条边后转的角度是 外角 360 / a,不是内角;共重复 a 次。
  • 填充颜色用 fillcolor(color);绘制轮廓前 begin_fill(),完成后 end_fill()。只设置画笔颜色不会自动填充内部。
  • 在主程序里随机选择边数、边长、颜色和位置,再调用 polygon(...);函数本身负责画一个图形。
  • 移动到下一幅图形前先抬笔,到达后落笔,避免装饰画之间多出连线。位置范围也要考虑图形大小,不要让大部分图形跑到画布外。
  • 颜色可从预先准备的颜色名称列表中 random.choice(...),不必一开始就处理 RGB 模式。

课程资料由授课教师、助教与同学共同积累。