放弃“for循环”,教你用这种算法 !(附代码)

原文标题:Why you should forget ‘for-loop’ for data science code and embrace vectorization

作者:Tirthajyoti Sarkar

翻译:杨金鸿

校对:丁楠雅

本文长度为1986字,建议阅读5分钟

数据科学需要快速计算和数据转换的能力。Python中的NumPy对象提供了优于常规编程结构算法,比如for循环。如何用简单的代码来演示它呢?


在11月27日至12月3日的KDnugget网站上,这篇文章被转载最多(https://www.kdnuggets.com/2017/12/top-news-week-1127-1203.html)


我们使用for循环来完成大部分工作,这些工作需要对一长串的元素进行更新。我敢断言,几乎所有人阅读这篇文章的读者,在他们高中或大学里都里有肯定有使用过for循环语句编写自己的第一个矩阵或矢量乘法代码。for循环为编程社区提供了长期稳定的服务。


然而,for循环在处理大型数据集时执行速度通常较慢(例如:在大数据时代处理几百万条记录)。对于像Python这样的解释性语言来说尤其如此。如果您的循环体很简单,那么循环解释器会占用大量的开销。


幸运的是,大部分主流的编程语言都有另外一种编程语言可以取代它。Python也是如此。


Numpy是Numerical Python(http://numpy.org/)的简称,同时也是Python生态系统中高性能科学计算和数据分析所需要的基本包。它几乎是所有高级语言工具的基础,如Pandas和 scikit-learn都是在Numpy的基础上编译的。TensorFlow使用NumPy阵列作为底层编译块。在这之上构建了Tensor对象和用于深度学习的graphflow(使用了大量的线性代数运算在一个长的列表/矢量/矩阵)。

 

Numpy提供的两个最重要的特性是:

  • Ndarray:一个快速空间高效的多维数组,提供了矢量化计算操作和复杂的广播能力(https://towardsdatascience.com/two-cool-features-of-python-numpy-mutating-by-slicing-and-broadcasting-3b0b86e8b4c7)

  • 标准的数学函数,可以在不写循环的情况下,对整个数据数组进行快速操作。


在数据科学、机器学习和Python社区中,您经常会遇到这样的断言:Numpy是更速度的。因为它是基于矢量的实现,而且它的许多核心例程都是用C语言编写(基于CPython 框架:https://en.wikipedia.org/wiki/CPython)。


这篇文章是一个CPython 框架的很好阐述(http://notes-on-cython.readthedocs.io/en/latest/std_dev.html)Numpy可以与各个方面协同工作。甚至可以使用Numpy api编写裸机骨C例程。Numpy阵列是均匀类型的密集阵列。相反,Python列表是指向对象的指针数组,即使它们是相同的对象类型。你可以从区域性关联(https://en.wikipedia.org/wiki/Locality_of_reference)得到收获。


许多Numpy操作是用C语言实现的,避免了Python中循环的开销、指针指向每个元素的动态类型检查(https://www.sitepoint.com/typing-versus-dynamic-typing/)。Numpy速度的提升取决于你所执行的操作。对于数据科学和现代机器学习来说,这是一个非常宝贵的优势,因为通常数据集的大小会达到数百万甚至数十亿。并且您不希望使用For循环和它的相关的算法进行更新。


如何用一个中等大小的数据集来验证它呢?


这里是Jupyter Github代码链接(https://github.com/tirthajyoti/PythonMachineLearning/blob/master/How%20fast%20are%20NumPy%20ops.ipynb)。其中在一些简单的代码行中,Numpy的操作速度与常规Python编程的速度不同,比如for循环、map-function(https://stackoverflow.com/questions/10973766/understanding-the-map-function)或list-comprehension(http://www.pythonforbeginners.com/basics/list-comprehensions-in-python)


这里我简单的概括下基本流程:

  • 创建一个中等数量集的浮点数列表,最好是从连续的统计分布中抽取出来,比如高斯分布或均匀随机分布。为了演示我选择了100万条数据

  • 在列表中创建一个ndarray对象,也就是矢量化

  • 编写简短的代码块来更新列表,并在列表上使用数学运算,比如以10为底的对数。使用for循环、map-function和list-comprehension。并使用time()函数来核实处理100万条数据需要花费多长时间

t1=time.time()

for item in l1:

    l2.append(lg10(item))

t2 = time.time()

print("With for loop and appending it took {} seconds".format(t2-t1))

speed.append(t2-t1)


  • 用Numpy的内置数学方法(np.log10)在ndarray对象上做同样的操作。计算出花费了多长时间

t1=time.time()

a2=np.log10(a1)

t2 = time.time()

print("With direct Numpy log10 method it took {} seconds".format(t2-t1))

speed.append(t2-t1)


  • 在一个列表中存储执行时间,并绘制出一个差异的柱状图


下面是结果显示。你可以运行Jupyter笔记本上的所有代码单元块来重复整个过程。每次它会生成一组新的随机数,因此精准的执行时间可能会有所不同。但总体来说,趋势始终是相同的。您可以尝试使用各种其他的数学函数/字符串操作或者集合,来检查是否适用于一般情况。


这里有一个由法国神经科学研究员编写的完整开源在线书籍(https://www.labri.fr/perso/nrougier/from-python-to-numpy/#id7)


简单数学运算比较速度的柱状图


如果你有任何问题或想法要分享,请与作者联系(tirthajyoti@gmail.com)。您也可以在Python、R或MATLAB和机器学习资源中查看作者的GitHub库(https://github.com/tirthajyoti),获得其他有趣的代码片段。你也可以在LinkedIn(https://www.linkedin.com/in/tirthajyoti-sarkar-2127aa7/)上关注我。


原文链接:

https://towardsdatascience.com/why-you-should-forget-for-loop-for-data-science-code-and-embrace-vectorization-696632622d5f


杨金鸿,北京护航科技有限公司员工,在业余时间喜欢翻译一些技术文档。喜欢阅读有关数据挖掘、数据库之类的书,学习java语言编程等,希望能在数据派平台上熟识更多爱好相同的伙伴,今后能在数据科学的道路上走的更远,飞的更远。


翻译组招募信息

工作内容:将选取好的外文前沿文章准确地翻译成流畅的中文。如果你是数据科学/统计学/计算机专业的留学生,或在海外从事相关工作,或对自己外语水平有信心的朋友,数据派翻译组欢迎你们加入!

你能得到:提高对于数据科学前沿的认知,提高对外文新闻来源渠道的认知,海外的朋友可以和国内技术应用发展保持联系,数据派团队产学研的背景为志愿者带来好的发展机遇。

其他福利:和来自于名企的数据科学工作者,北大清华以及海外等名校学生共同合作、交流。

点击文末“阅读原文”加入数据派团队~

转载须知

如需转载,请在开篇显著位置注明作者和出处(转自:数据派THUID:DatapiTHU),并在文章结尾放置数据派醒目二维码。有原创标识文章,请发送【文章名称-待授权公众号名称及ID】至联系邮箱,申请白名单授权并按要求编辑。

发布后请将链接反馈至联系邮箱(见下方)。未经许可的转载以及改编者,我们将依法追究其法律责任。


点击“阅读原文”加入组织~

<think>嗯,用户现在想用中点画线算法替换之前代码中的画圆算法。首先,我需要回忆一下原来的代码是用turtle库的circle方法来画圆的,而中点画线算法通常是用来画直线或者圆的,但这里应该是中点画圆算法吧?用户可能打错了,或者确实是想用中点画线算法来画圆,不过中点画线算法一般是画直线的,中点圆算法才是画圆的。不过先得确认用户的需求。 首先,我需要检查之前的代码,原代码中使用的是turtle的circle函数来绘制雪人的三个圆形身体部分。用户希望用中点画线算法替换这个画圆的算法。那中点画圆算法和中点画线算法是不同的,中点画圆算法属于光栅图形学中的算法,用于在像素级别绘制圆,而中点画线算法是用于绘制直线的。可能用户是希望用更底层的算法来绘制圆,而不是用turtle自带的circle方法。 那问题来了,中点画线算法是否能用来画圆?应该不行,可能用户是想要用中点圆算法来替代。或者用户可能混淆了两种算法的名称。但根据常规理解,替换画圆的算法应该是中点圆算法,也就是Midpoint Circle Algorithm。所以可能需要先确认这一点,但用户明确说是中点画线算法,可能需要进一步澄清,但作为助手,可能需要按照中点圆算法来处理,或者解释两者的区别。 不过,假设用户确实是想用中点画线算法来画圆,可能需要说明中点画线算法通常用于直线,而画圆一般用中点圆算法。不过用户可能希望用中点画线算法来绘制圆的每一段,比如用直线段来逼近圆。但这可能不太高效,或者需要将圆分解为多个直线段。不过中点圆算法本身就是用来生成圆上的点,然后连接这些点形成圆。 或者用户可能希望用中点算法来绘制圆,而不是使用内置函数。因此,我需要解释如何用中点圆算法替换原来的circle方法,然后修改代码中的draw_circle函数,使用该算法来绘制圆。 接下来,我需要考虑如何用中点圆算法在turtle中实现画圆。中点圆算法的原理是基于八分法,计算圆上各点的坐标,然后根据对称性绘制其他七个部分。然后,在turtle中移动画笔到这些点,可能通过绘制线段来形成圆的轮廓,或者点阵来形成圆。 不过,turtle的移动是基于坐标的,因此需要将中点圆算法生成的每个点转换为turtle的移动和绘制操作。这可能比较耗时,因为需要逐个点计算并移动,但可以实现。 因此,我需要先编写中点圆算法的函数,给定圆心和半径,生成圆上的所有点坐标,然后让turtle移动到这些点,进行绘制。这可能比直接使用circle方法慢,但能展示算法原理。 然后,原来的draw_circle函数是用turtle的circle方法,现在需要替换成中点圆算法的实现。同时,原来的函数还填充颜色,因此可能需要考虑如何填充,但中点圆算法只是绘制轮廓,填充可能需要其他方法,或者用户可能只需要轮廓。但原代码中的雪人身体是填充白色的,所以可能需要调整。 但问题来了,中点圆算法通常只绘制圆的边界,而填充圆形需要扫描线填充算法或其他填充方法,这可能比较复杂。因此,可能用户只是希望替换绘制圆轮廓的算法,而填充可能仍然使用turtle的填充功能,或者需要手动实现填充。但原代码中的draw_circle函数使用了begin_fill和end_fill来填充颜色,所以如果替换成中点圆算法,可能无法直接填充,需要另外处理。 不过,用户可能只需要替换画圆的轮廓部分,而填充还是用原来的方法?但原来的代码中,circle方法本身会绘制并填充圆形。如果使用中点圆算法来绘制轮廓,同时结合turtle的填充功能,可能需要在轮廓绘制完成后进行填充。但turtle的填充需要路径闭合,而手动绘制的圆可能无法正确闭合,导致填充问题。 或者,可能需要结合两种方法:用中点圆算法绘制圆的轮廓,然后使用turtle的填充功能进行填充。但需要确保路径是闭合的。或者,可能需要手动计算圆的所有内部点,但这效率很低,不现实。 因此,可能的解决方案是:使用中点圆算法绘制圆的轮廓,然后使用turtle的begin_fill和end_fill来填充颜色,但这需要轮廓闭合。但中点圆算法生成的轮廓可能不闭合,或者路径可能不连续,导致填充错误。 或者,用户可能只需要替换绘制圆的轮廓,而不需要填充。但原代码中的雪人身体是填充白色的,所以可能需要保持填充。这时候可能需要保留原来的填充方法,但用中点圆算法来绘制轮廓。这可能比较复杂。 或者,用户可能只是想了解如何用中点算法来绘制圆,替换原来的circle方法,而不关心填充。因此,可能需要调整draw_circle函数,使用中点圆算法绘制圆的轮廓,而放弃填充,或者手动实现填充。 这时候需要权衡,是否填充是必须的。原代码中的雪人身体是白色的填充圆形,因此如果替换为用中点圆算法绘制轮廓,但没有填充,雪人可能只有边框,而没有实心。这显然不符合原效果。因此,必须找到一种方法,在使用中点圆算法绘制轮廓的同时,进行填充。 但turtle的填充机制需要路径闭合,所以可能需要绘制完整个圆的轮廓后,进行填充。而中点圆算法绘制的轮廓是否能够形成闭合路径是关键。例如,如果使用中点圆算法生成所有圆上的点,并让turtle依次移动到这些点,形成一个多边形近似圆,然后填充,可能可以实现。 但中点圆算法生成的是一系列点,将这些点连接起来形成一个多边形,近似圆形。然后填充这个多边形。但circle方法本身可能也是用多边形近似圆,但边数较多,所以看起来平滑。而手动实现的算法可能需要设置足够的点来近似圆,否则会有棱角。 因此,可能的解决方案是:用中点圆算法生成圆上的点,然后让turtle移动到这些点,形成多边形,然后填充。这样既可以绘制轮廓,也可以填充颜色。 现在,我需要具体实现中点圆算法,生成圆上的所有点,然后让turtle依次移动到这些点,形成一个多边形,最后填充。 首先,中点圆算法的实现步骤: 1. 输入圆心(xc, yc)和半径r。 2. 初始化x=0,y=r,d=1-r。 3. 循环直到x <= y: a. 根据八分对称性,绘制八个点。 b. 根据d的值更新d,并决定y是否减少。 4. 生成所有点的坐标,相对于圆心。 然后,将这些点转换为绝对坐标,并让turtle移动到这些点,形成路径,最后填充。 但需要考虑的是,中点圆算法生成的点是按八分对称生成的,所以需要收集所有点,并按顺序连接,以形成闭合路径。例如,从0到45度,然后对称到其他七个部分,但点的顺序可能需要调整,以形成连续的路径,否则填充会出现问题。 或者,可能需要按顺序生成所有点,让turtle依次连接,形成一个多边形。这可能需要将中点圆算法生成的点按顺时针或逆时针顺序排列,形成闭合路径。 这可能比较复杂,因为中点圆算法生成的点是按八分法对称绘制的,顺序可能不是连续的。因此,可能需要先生成一个八分之一的点,然后根据对称性生成其他部分的点,并按顺序排列,形成完整的圆形路径。 例如,生成第一八分之一的点(x, y),然后生成其他七个对称点,并按正确的顺序排列,使turtle可以依次连接这些点,形成闭合的多边形。 这需要仔细处理点的顺序,以确保路径闭合且填充正确。 或者,可以生成足够多的点,近似圆形,然后让turtle连接这些点,形成一个足够接近圆的多边形。这可能需要调整中点圆算法,生成更多的点,或者使用其他方法。 不过,这可能超出中点圆算法的范围,因为中点圆算法主要是为了在像素级别生成圆形的点,而不是生成连续的路径。因此,在turtle中使用中点圆算法可能需要额外的处理步骤。 综上,可能的步骤如下: 1. 实现中点圆算法,生成圆上的所有点坐标(相对于圆心)。 2. 将这些点转换为绝对坐标(相对于画布)。 3. 让turtle移动到第一个点,然后依次连接所有点,形成闭合路径。 4. 使用turtle的填充功能填充路径内部。 这样,就可以用中点圆算法绘制的多边形来近似圆,并进行填充。 现在,具体到代码实现: 原代码中的draw_circle函数使用了pen.circle(radius),这会以当前画笔位置为圆心,绘制指定半径的圆,并填充颜色。而中点圆算法需要根据圆心坐标和半径生成点,然后绘制。 因此,需要修改draw_circle函数,接受圆心坐标(x, y)、半径r和颜色,然后使用中点圆算法生成点,移动turtle到这些点,形成路径,填充颜色。 具体实现: def draw_circle(color, radius, xc, yc): pen.penup() pen.goto(xc, yc - radius) # 起始点在圆的下端 pen.pendown() pen.begin_fill() pen.fillcolor(color) # 使用中点圆算法生成点并移动 points = midpoint_circle(xc, yc, radius) for (x, y) in points: pen.goto(x, y) pen.end_fill() 但需要实现midpoint_circle函数,生成圆上的点坐标列表。然后,将这些点按顺序排列,使得turtle连接它们时形成闭合路径。 中点圆算法的实现: def midpoint_circle(xc, yc, r): points = [] x = 0 y = r d = 1 - r while x <= y: # 添加八个对称点 points.extend(symmetry_points(xc, yc, x, y)) if d < 0: d += 2*x + 3 else: d += 2*(x - y) + 5 y -= 1 x += 1 # 按顺序排列点,形成闭合路径 # 这里可能需要调整点的顺序,以便turtle可以正确连接 # 例如,按顺时针或逆时针顺序排列所有点 # 但由于对称性,可能需要额外处理 # 暂时先返回所有点,后续可能需要排序 return sort_points(points) 但symmetry_points函数需要生成八个对称点: def symmetry_points(xc, yc, x, y): points = [] # 八个对称点 points.append((xc + x, yc + y)) points.append((xc + y, yc + x)) points.append((xc + y, yc - x)) points.append((xc + x, yc - y)) points.append((xc - x, yc - y)) points.append((xc - y, yc - x)) points.append((xc - y, yc + x)) points.append((xc - x, yc + y)) return points 但这样生成的点的顺序可能不是连续的,导致turtle在连接时出现跳跃,无法形成闭合路径。因此,需要将这些点按圆形顺序排列,例如从0度开始,顺时针排列所有点。 这可能需要更复杂的处理,例如先生成一个八分之一的点,然后按顺序生成其他部分的点,以形成连续的路径。 例如,先生成第一象限的x从0到y的点,然后按顺时针方向添加其他对称点。但具体的顺序需要调整,以便路径连续。 或者,可能需要使用另一种方法,生成所有点后,按角度排序,这样turtle可以按顺序连接这些点,形成圆形。 例如,计算每个点的极角(角度),然后按角度排序。但这样计算量较大,但对于小半径来说可能可以接受。 修改后的midpoint_circle函数可能如下: def midpoint_circle(xc, yc, r): points = [] x = 0 y = r d = 1 - r while x <= y: # 生成所有对称点 octant_points = [ (x, y), (y, x), (y, -x), (x, -y), (-x, -y), (-y, -x), (-y, x), (-x, y) ] for dx, dy in octant_points: points.append((xc + dx, yc + dy)) if d < 0: d += 2*x + 3 else: d += 2*(x - y) + 5 y -= 1 x += 1 # 去重并排序 unique_points = list(dict.fromkeys(points)) # 去重 # 按极角排序 unique_points.sort(key=lambda p: math.atan2(p[1]-yc, p[0]-xc)) return unique_points 但这样排序后的点可能仍然无法形成连续的路径,因为atan2返回的角度在-π到π之间,而排序后可能从某个角度开始,导致路径跳跃。例如,可能从右侧开始,按逆时针排列,但turtle需要按顺序连接这些点,才能形成闭合路径。 不过,这样的排序可能仍然无法正确闭合,因为中点圆算法生成的点在不同的八分位,排序后可能形成连续的路径。但需要测试。 另一种方法是在生成点的时候,按顺时针或逆时针顺序添加各个八分位的点,以确保路径的连续性。例如,对于每个(x, y),生成八个点,并按顺时针顺序添加,这样在循环时,点会按顺序排列。 例如,在生成octant_points时,按顺时针方向添加各个对称点: octant_points = [ (x, y), (y, x), (y, -x), (x, -y), (-x, -y), (-y, -x), (-y, x), (-x, y) ] 但这样添加的顺序可能不是连续的,导致路径中出现跳跃。例如,从(x, y)到(y, x)可能是一个顺时针移动,但需要确保所有点按顺序排列,形成闭合路径。 然而,这可能比较复杂,可能需要先生成一个八分之一的点,然后按顺序生成其他部分的点,以确保路径的连续性。例如,先生成从(0, r)开始,沿顺时针方向生成所有点,按顺序添加到列表中。 不过,这可能超出中点圆算法的常规实现,因为中点圆算法通常用于生成所有八个对称点,但顺序可能不是连续的。 因此,可能需要另一种方法:使用中点圆算法生成所有圆上的点,然后按极角排序,形成一个闭合的路径。尽管计算角度会增加开销,但对于较小的半径来说,这可能可以接受。 但问题又来了,turtle的填充需要路径是闭合的,即起点和终点相同,或者通过pen.end_fill()自动闭合。如果路径是近似圆形的多边形,填充应该可以正确进行。 例如,生成足够多的点,按顺序排列,形成一个接近圆的多边形,然后填充。 但中点圆算法生成的点的数量可能不足,导致多边形棱角明显。例如,当半径较大时,可能需要更多的点来平滑边缘。因此,可能需要调整中点圆算法的步进,生成更多的点,但这可能影响性能。 不过,对于雪人来说,半径可能在50-100之间,中点圆算法生成的点的数量可能足够形成较为平滑的圆。 现在,回到代码实现。假设我们已经有了排序后的点列表,那么turtle可以依次移动到这些点,形成路径,然后填充。 但可能需要将点列表排序,使得路径按顺时针或逆时针顺序连接,形成闭合区域。 另外,需要注意的是,turtle的circle方法实际上是以当前画笔位置为圆心,绘制指定半径的圆,而中点圆算法需要给定圆心(xc, yc),所以需要调整坐标。 在原来的draw_circle函数中,pen.goto(x, y)是将画笔移动到圆心位置,然后调用pen.circle(radius),这会以(x, y)为圆心,绘制半径radius的圆。而中点圆算法需要生成相对于圆心(xc, yc)的点,因此需要将turtle移动到这些点,并绘制线条。 但这样绘制出来的圆可能由许多小的线段组成,而不是turtle内置的circle方法的平滑圆。不过,这是用户的需求,用中点算法替换。 综上,修改后的draw_circle函数可能如下: def draw_circle(color, radius, xc, yc): pen.penup() pen.fillcolor(color) # 生成圆上的所有点 points = midpoint_circle(xc, yc, radius) # 移动到第一个点 pen.goto(points[0]) pen.pendown() pen.begin_fill() # 连接所有点 for point in points[1:]: pen.goto(point) # 闭合路径 pen.goto(points[0]) pen.end_fill() pen.penup() 但需要确保points列表中的点按顺序排列,形成闭合路径。 然而,中点圆算法生成的点的顺序可能并不连续,导致绘制出的路径出现跳跃,无法正确填充。因此,如何正确排序这些点是关键。 可能的解决方案是使用极角排序,即对每个点相对于圆心的角度进行排序,然后按顺时针或逆时针顺序连接这些点。 修改midpoint_circle函数,加入极角排序: import math def midpoint_circle(xc, yc, r): points = [] x = 0 y = r d = 1 - r while x <= y: # 生成八个对称点 octant = [ (x, y), (y, x), (y, -x), (x, -y), (-x, -y), (-y, -x), (-y, x), (-x, y) ] for dx, dy in octant: px = xc + dx py = yc + dy points.append((px, py)) if d < 0: d += 2*x + 3 else: d += 2*(x - y) + 5 y -= 1 x += 1 # 去重 unique_points = list(dict.fromkeys(points)) # 按极角排序 # 计算每个点相对于圆心的角度 unique_points.sort(key=lambda p: math.atan2(p[1]-yc, p[0]-xc)) return unique_points 这样,点会按角度从小到大排列,即逆时针方向。然后,turtle按顺序连接这些点,形成闭合路径。 不过,由于对称性,可能会有重复的点,所以需要去重。使用dict.fromkeys可以保留顺序的同时去重。 现在,测试这个函数是否能生成正确的点列表,并按顺序排列。 例如,对于圆心在(0,0),半径5,生成的点应该围绕圆心,按逆时针排列。 一旦点列表正确,turtle就可以依次连接这些点,形成一个多边形,然后填充颜色。 但需要注意的是,中点圆算法生成的点的数量可能不足以形成平滑的圆,尤其是对于较大的半径。例如,半径100的圆,可能只有约100个点,导致多边形棱角明显。因此,可能需要增加点的数量,但这超出了中点圆算法的范围,或者需要调整算法参数。 不过,用户的需求是用中点画线算法(可能是中点圆算法)替换原来的circle方法,所以即使有棱角,也是可以接受的,或者用户可能希望了解如何实现算法,而不是追求完美效果。 接下来,将修改后的draw_circle函数替换原来的函数,并测试代码。 原代码中的draw_circle调用是: draw_circle("white", 100, 0, -200) draw_circle("white", 70, 0, 0) draw_circle("white", 50, 0, 150) 修改后,这些调用应该正确生成圆。 另外,原来的circle方法是以当前画笔位置为圆心,绘制半径,而中点圆算法需要指定圆心坐标。原来的代码在调用draw_circle时,已经将圆心坐标作为参数传递,因此修改后的函数应该可以正确工作。 不过,需要测试中点圆算法生成的圆是否与原来的circle方法效果一致,尤其是在填充颜色时。 可能遇到的问题: 1. 中点圆算法生成的点数量不足,导致填充区域不完整或有漏洞。 2. 点排序错误,导致路径无法正确闭合,填充失败。 3. 绘制速度变慢,因为需要生成和连接大量点。 因此,可能需要优化中点圆算法的实现,或者调整步进参数以生成更多的点。例如,在生成点时,每个八分位生成更多的点,但这可能需要修改算法本身,比如使用浮点运算,但这可能复杂。 或者,可以接受较低的分辨率,因为雪人的身体部分可能不需要非常精确的圆形。 综上,修改后的代码应该能够用中点圆算法替换原来的circle方法,尽管可能在外观上略有差异,但能展示算法的实现。</think>以下是将原代码中的画圆方法替换为**中点圆算法**的详细实现和解释。中点圆算法通过逐点计算生成圆形轮廓,替代`turtle.circle()`的直接调用。 --- ### 关键思路 1. **中点圆算法原理**: - 根据圆的对称性(八分法),只需计算$\frac{1}{8}$圆弧的点坐标,通过对称性生成完整的圆。 - 决策参数公式:$d = 1 - r$,逐点计算坐标$(x, y)$并更新$d$值。 2. **代码改造**: - 自定义函数`midpoint_circle()`生成圆上所有点的坐标。 - 用`pen.goto()`逐点绘制圆形轮廓并填充。 --- ### 修改后的代码 ```python import turtle import math # 初始化画笔 pen = turtle.Turtle() pen.speed(5) screen = turtle.Screen() screen.bgcolor("skyblue") # 中点圆算法生成圆上所有点的坐标 def midpoint_circle(xc, yc, r): points = [] x = 0 y = r d = 1 - r while x <= y: # 生成八个对称点(基于圆心坐标修正) offsets = [(x, y), (y, x), (y, -x), (x, -y), (-x, -y), (-y, -x), (-y, x), (-x, y)] for dx, dy in offsets: px = xc + dx py = yc + dy points.append((px, py)) # 更新决策参数d if d < 0: d += 2 * x + 3 else: d += 2 * (x - y) + 5 y -= 1 x += 1 # 去重并排序(按极角确保连续路径) unique_points = list(dict.fromkeys(points)) # 去重 unique_points.sort(key=lambda p: math.atan2(p[1]-yc, p[0]-xc)) # 极角排序 return unique_points # 绘制填充圆形(基于中点算法) def draw_circle(color, radius, xc, yc): pen.penup() pen.fillcolor(color) points = midpoint_circle(xc, yc, radius) # 移动到第一个点并开始填充 if points: pen.goto(points[0]) pen.pendown() pen.begin_fill() for (x, y) in points[1:]: pen.goto(x, y) pen.goto(points[0]) # 闭合路径 pen.end_fill() pen.penup() # 绘制雪人身体 draw_circle("white", 100, 0, -200) # 底层雪球 draw_circle("white", 70, 0, 0) # 中间雪球 draw_circle("white", 50, 0, 150) # 头部雪球 # 画眼睛和鼻子(代码与原版相同) pen.penup() pen.goto(-20, 200) pen.dot(15, "black") pen.goto(20, 200) pen.dot(15, "black") pen.penup() pen.goto(0, 180) pen.pendown() pen.color("red") pen.begin_fill() pen.setheading(120) pen.forward(30) pen.left(120) pen.forward(30) pen.left(120) pen.forward(30) pen.end_fill() pen.hideturtle() turtle.done() ``` --- ### 核心改动说明 1. **中点圆算法实现**: - `midpoint_circle(xc, yc, r)`生成圆心在$(x_c, y_c)$、半径为$r$的圆的所有像素点坐标。 - 通过极角排序确保点按顺时针/逆时针连续排列,形成闭合路径。 2. **绘制与填充**: - `draw_circle()`函数调用中点算法生成点集,通过`pen.goto()`逐点绘制多边形并填充。 --- ### 效果对比 | 原版`turtle.circle()` | 中点圆算法实现 | |----------------------|----------------| | 边缘平滑,效率高 | 边缘可能有轻微棱角 | | 直接调用内置方法 | 显式控制绘制过程 | --- ### 注意事项 1. **性能差异**: - 中点圆算法逐点绘制,速度明显慢于内置方法,尤其对大半径圆形。 2. **边缘平滑度**: - 可通过减小步长或增加插值点优化(需修改算法逻辑)。 如需进一步优化绘制效果,可以调整中点算法的步长或结合其他图形算法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值