- 示例工程
【免费下载链接】roadmap-retos-programacion
Ruta de estudio basada en ejercicios de código de la comunidad MoureDev para aprender y practicar lógica usando cualquier lenguaje de programación.
本文以 roadmap-retos-programacion 社区编程路线图的第 48 号挑战「ÁRBOL DE NAVIDAD(圣诞树)」为蓝本,完整拆解题目的全部规则与验收标准,并结合仓库内 Python、JavaScript、Java、Go、Rust、C++、Kotlin、Bash 等多个语言的社区实现,讲解如何用任意编程语言在终端中生成一棵"高度动态、可交互装饰"的 ASCII 圣诞树。读完本文,你将掌握字符画树的几何建模方法、随机抽样装饰算法、位置记忆型状态管理,以及一个典型命令行交互程序从输入校验到动作反馈的完整设计。
挑战概述:题目在要求什么
这是 MoureDev 路线图中的第 48 个编程挑战(难度标注为 Media 中等,发布于 2024-12-02)。题面位于 Roadmap/48 - ÁRBOL DE NAVIDAD/ejercicio.md,核心要求如下:
开发一个程序,由用户在终端中输入一个动态高度,程序据此生成一棵圣诞树。树冠由
*组成,树冠正下方永远是一模一样的两行树干|||。程序需要持续提供交互菜单,允许用户装饰或修改这棵树。
题目给出的高度为 5 的示例输出(树干固定不变):
* *** ***** ******* ********* ||| |||可以看出:树冠共height行,第i行(从 1 起)包含2*i - 1个*且整体居中;树干为两行居中的|||,与高度无关、始终保持一致。
交互动作与约束规则
题目要求用户能够通过菜单执行以下五类动作,这是实现时必须逐一满足的验收点:
| 动作 | 符号 | 规则要点 |
|---|---|---|
| 添加 / 移除树冠顶部的星星 | @ | 整棵树只能有一颗星星,且固定在树冠最顶端 |
| 添加 / 移除彩球 | o | 一次 2 个,随机选择位置 |
| 添加 / 移除彩灯 | + | 一次 3 个,随机选择位置 |
| 关闭 / 点亮彩灯 | *(灭)/+(亮) | 必须保持彩灯原有位置不变,仅切换符号 |
| 退出程序 | — | 正常结束交互循环 |
此外还有两条硬性约束:
- 一盏灯和一颗球不能占据同一位置——所有装饰物必须互斥;
- 程序必须对每次动作给出反馈——成功时提示"已添加/已移除",失败时(如空间不足、无装饰可移除)必须明确告知原因。
"只能添加一颗星星、彩灯与彩球数量上限为树冠可容纳位置数"这两条,分别对应了仓库实现中常见的两种校验:星星用单个布尔/坐标标志位控制,彩球彩灯则通过"可用位置池"的长度来判断(见下文源码分析)。
数据模型设计:用代码"画"出一棵树
不同语言的社区实现尽管风格迥异,但抽象出的数据模型基本可归为三类,这里以仓库源码为证据逐一说明。
方案一:二维矩阵(官方参考实现 mouredev.py 采用)
官方修正实现 Roadmap/48 - ÁRBOL DE NAVIDAD/python/mouredev.py(README 中第 48 号挑战的"Corrección"一列即指向该文件)用一个宽2*height-1、高height的字符矩阵self.tree表示树冠,再用一个同样宽、高为 2 的矩阵self.trunk表示树干:
self.tree = [ [" " for _ in range(2 * height - 1)] for _ in range(height) ] for i in range(height): for j in range(height - i - 1, height + i): self.tree[i][j] = "*" self.trunk = [ [" " for _ in range(2 * height - 1)] for _ in range(2) ] for i in range(2): for j in range(height - 2, height + 1): self.trunk[i][j] = "|"这里的几何规律值得记住:第i行的*从列height - i - 1开始、到列height + i结束(左闭右开),恰好覆盖2*i + 1列,天然居中。树干行则固定写|||于中央三列。矩阵方案的优点是把"某个位置放什么字符"变成了直接可寻址的状态,装饰逻辑只需维护坐标集合。
方案二:装饰字典(miguelex 系列多语言实现采用)
miguelex 在 Python、JavaScript、Java、TypeScript、PHP 中提交了结构几乎一致的实现,例如 Roadmap/48 - ÁRBOL DE NAVIDAD/python/miguelex.py。其核心是把"树"与"装饰"分离:树冠每行按公式现场生成字符串,装饰则记录在decorations字典(键为行号,值为该行内列号 → 字符的映射):
for i in range(1, height + 1): line = " " * (height - i) for j in range(2 * i - 1): if decorations.get(i, {}).get(j): line += decorations[i][j] else: line += "*" tree.append(line)由于彩球与彩灯都写入同一个decorations字典,天然满足"球与灯不重叠"的约束——add_random_decoration中if col not in decorations[row]即保证不重复占位。这一方案在 JavaScript 版、Java 版、TypeScript 版 与 PHP 版 中均有对应实现,适合作为跨语言对照阅读。
方案三:字符串行列表(hectorio23 系列)
hectorio23 在 Python 版 与 C++ 版 中把树保存为一个字符串行组成的列表,每行直接用" " * (height - i - 1) + "*" * width拼出;星星则作为独立一行放在列表头部(" " * height + "@")。优点是与终端输出天然一一对应、打印即遍历,缺点是"按位置修改字符"需要把行转成字符数组再重组字符串。
其他值得一提的模型
- Python edaagapu.py 采用数字编码矩阵:用
0空、1枝叶、2彩球、3灭灯、4亮灯、5星星、6树干共 7 个整数表示单元格状态,再通过mapper_dict(' ,*,o,*,+,@,|')统一映射为字符输出——适合希望做"状态机"式设计的读者参考。 - Python Gordo-Master.py 与 Python davidrguez98.py 在 mouredev 矩阵模型基础上改用
set维护空位、彩球、彩灯、星星四类坐标集合,用集合运算判断"是否有空位可放"。
官方参考实现源码剖析:mouredev.py
mouredev.py是 README 中指向的官方修正实现,逻辑完整、注释规范,适合作为"标准答案"通读。下面按功能模块讲解其设计。
星星:一个布尔标志位
def add_star(self): if self.star: print("Ya existe una estrella en el árbol.") else: self.star = True print("Se ha puesto la estrella en el árbol.") def remove_star(self): if not self.star: print("No existe una estrella en el árbol para quitar.") else: self.star = False print("Se ha quitado la estrella en el árbol.")星星用self.star布尔值表示,绘制时在display_tree中特判首行:print("".join(row).replace("*", "@"))——即第一行唯一的一个*在画星星时被替换成@。添加/移除都先做状态校验并给出明确提示,正是题目"通知每次动作(或告知无法执行)"要求的直接体现。
彩球与彩灯:可用位置池 + random.sample
def add_balls(self): available = self.available() if len(available) < 2: print("No hay suficiente espacio para añadir más bolas.") else: selected = random.sample(available, 2) for i, j in selected: self.tree[i][j] = "o" self.balls.append((i, j)) print("Se han añadido 2 bolas al árbol.")random.sample(pool, k)保证一次取出的k个位置互不重复,天然满足"一次 2 个 / 3 个且不重叠"。移除逻辑对称:从self.balls中随机取 2 个坐标,写回*并从列表删除。
彩灯开关:位置不变的原地翻转
def toggle_lights(self, turn_on): if not self.lights: print("No hay luces en el árbol.") self.lights_on = turn_on for i, j in self.lights: self.tree[i][j] = "+" if turn_on else "*" print(f"Las luces fueron {'encendidas' if turn_on else 'apagadas'}.")关键点在于:彩灯坐标被持久保存在self.lights列表中,开关时只改矩阵里这些坐标的字符、不动列表本身,从而保证"关闭(*)或点亮(+)时保留原位置"。另外注意灭灯状态下的彩灯在矩阵里也是*,为了让它们依然"占着位置、不能被新装饰覆盖",available()做了特殊处理:
def available(self): available_tree = [ (i, j) for i in range(1, self.height) for j in range(self.height - i - 1, self.height + i) if self.tree[i][j] == "*" ] if not self.lights_on: for i, j in self.lights: available_tree.remove((i, j)) return available_tree两点细节值得学习:其一,候选坐标的i从1开始,即树冠最顶端一行不参与放置彩球彩灯(那是星星专属的"copa");其二,灭灯时彩灯坐标显示为*会被误认为空位,因此显式从可用池中剔除,保证"灯位不可被球/新灯抢占"。这一处对"熄灭的灯仍是灯"的语义处理,正是题目"保留其位置"的深层含义。
主循环与输入校验
height = input("Introduce la altura del árbol: ") if height.isdigit() and int(height) > 0: tree = ChristmasTree(int(height)) while True: tree.display_tree() print("\nAcciones:") print("1. Añadir estrella") print("2. Quitar estrella") print("3. Añadir bolas") print("4. Quitar bolas") print("5. Añadir luces") print("6. Quitar luces") print("7. Encender luces") print("8. Apagar luces") print("9. Salir") action = input("Selecciona una acción: ") match action: case "1": tree.add_star() # ... 其余 case 依次映射到对应方法 case "9": print("¡Feliz Navidad!") break case _: print("Opción no válida.")isdigit()加上int(height) > 0双重校验拒绝负数、小数与非数字输入;match(Python 3.10+ 的结构模式匹配)把 9 个菜单项分别映射到类方法,非法输入落入_分支提示"Opción no válida"。这个"先校验输入、再分发动作、每步反馈"的循环就是完整交互程序的骨架。
交互菜单设计的多语言对比
题面没有规定菜单形式,仓库实现因此分化出三种风格:
- 数字菜单(最主流):mouredev 的 1–9 选项、miguelex 系列的 1–7 选项(将"加星/去星"合并为选项 1 的切换)、kenysdev 系列的 1–8 选项(额外提供"Luces automáticas 彩灯自动闪烁")、Gordo-Master 的二级菜单(先选装饰类型,再选"添加/移除")。
- 关键词命令:hectorio23 在 Python/C++ 中用
[estrella, bolas, luces, apagar, salir]英文词命令驱动,option.strip().lower()归一化输入后分发。 - 状态切换:Kotlin 版 Roadmap/48 - ÁRBOL DE NAVIDAD/kotlin/blackriper.kt 与 Bash 版 Roadmap/48 - ÁRBOL DE NAVIDAD/bash/arturonavas.sh 采用布尔开关语义——同一选项按一次是"加",再按一次是"减"(Bash 里直接写成
((estrella ^= 1))异或翻转,作者在注释中称之为"开关")。
菜单结构不影响功能正确性,但直接影响体验,读者可以按自己的命令行习惯任选其一。
随机放置的两种实现哲学
题目要求彩球"随机"放置,仓库中出现了两种随机化策略:
- 一次性随机抽样(mouredev、kenysdev、miguelex 等多数实现):先构造可用位置集合,再用
random.sample(pool, k)一次性选出 k 个不重复坐标。kenysdev 的 Python 版 维护了一个专门的__tree_top可用坐标池:加装饰时把坐标从池中移入__balls/__lights,移除时反向移回,用两个列表的"迁移"保证位置永不复用:
def add_balls(self) -> None: if len(self.__tree_top) < 2: print("Ya no hay espacio para poner bolas.") return random_locations = random.sample(self.__tree_top, 2) for e in random_locations: self.__balls.append(e) self.__tree_top.remove(e) self.__mtx[e[0]][e[1]] = "o"同样的思路还体现在 JavaScript 版(用Array.splice实现自实现的randomSample)与 Rust 版(用randcrate 的SliceRandom::choose+remove)。Rust 版在文件头部声明了依赖rand = "0.8.5",运行时需cargo run拉取依赖。
逐位概率尝试(hectorio23):遍历树冠每一格,以
random.choice([True, False])决定是否放置,直到凑满目标数量。这种方式可能实际放置的数量少于 2/3(程序会如实打印实际数量,如Añadido 1 de o),在"必须恰好放置 2 个/3 个"的语义下略有出入,但更贴近"随机撒点"的自然直觉。确定性模数图案:Kotlin 与 Bash 版用
j % 2 == 0、j % 3 == 0等取模规则周期性地把o/+铺满树冠。Bash 版作者在源码注释里坦诚写道"不知道如何随机添加符号",因此退而求其次用取模规律生成装饰——这恰好说明:在满足题目验收的前提下,实现路径可以自由取舍。
进阶细节与视觉增强
屏幕刷新与闪烁效果
交互程序持续打印树冠会让终端越来越长,几个实现加入了清屏:neslarra.py用system("clear")/system("cls")(按os.name判断平台),Go 版 用exec.Command("clear"),Rust 版用 ANSI 转义序列\x1B[2J\x1B[1;1H。kenysdev 与 neslarra 还实现了"彩灯自动闪烁":automatic_lights每秒翻转一次灯位字符并重绘。neslarra 的做法是循环 30 次、交替调用点亮/熄灭并sleep(0.5):
for _ in range(0, 30): show_tree(turn_on(tree, False)) sleep(0.5) show_tree(turn_on(tree, True)) sleep(0.5)彩色输出
Python javierfiestasbotella.py 借助colorama为不同元素着色:绿叶*用Fore.GREEN、树干|用红色、星星@用Fore.YELLOW、点亮彩灯+用Style.BRIGHT + Fore.YELLOW、熄灭彩灯用Style.DIM。若终端支持 ANSI 颜色,这能让最终效果直观许多(colorama 在 Windows 下同样可用)。
高度输入的范围限制
题面只要求"动态高度",各实现校验口径不同:mouredev 允许任意正整数(int(height) > 0);kenysdev 要求奇数且 ≥ 3(size % 2 != 0 && size >= 3,因为它的树冠宽度按2*size-1计算,偶数会产生不对称树干);neslarra 限定 2–15。三种口径都不违背题面,说明"约束未明确时,实现者有权自行定义合理边界并给出提示"。
运行与验证
仓库为只读资源,运行方式如下(以官方 Python 参考实现为例):
python3 "Roadmap/48 - ÁRBOL DE NAVIDAD/python/mouredev.py"程序会先询问Introduce la altura del árbol:,输入如5后进入交互循环:每次显示当前树冠与树干,再列出 1–9 的动作菜单。建议依次验证以下验收用例:
- 输入非法高度(
0、-3、abc)→ 应提示高度无效且不进入循环; - 选项
1两次 → 第二次应提示"星星已存在"; - 连续选项
3直到树冠被占满 → 应提示空间不足; - 添加若干彩灯后执行
8(熄灭)→ 树冠中对应位置由+变为*,且这些位置之后无法再放置彩球(available()已排除灯位); - 退出时打印
¡Feliz Navidad!正常结束。
JavaScript 版(Node 环境)对应运行:
node "Roadmap/48 - ÁRBOL DE NAVIDAD/javascript/kenysdev.js"注意 kenysdev 的 JS 版依赖readline-sync与clear两个 npm 包,需先npm install readline-sync clear;而 miguelex 的 JS/TS 版只用 Node 内置readline,无需额外依赖。Rust 版则需在cargo项目中声明rand = "0.8.5"后cargo run。
总结:这个挑战训练了什么
第 48 号圣诞树挑战是一个"麻雀虽小、五脏俱全"的终端交互程序:它同时考察了二维坐标建模(把几何图形映射为可编程状态)、随机性与互斥约束(不重复抽样、球灯不重叠)、状态持久化(开关灯保留位置)、输入校验与异常分支反馈,以及菜单驱动的交互循环设计。仓库中 14 余种语言的实现(从 Bash、SQL 到 Go、Rust)提供了同一需求下的不同工程取舍样本:官方修正 python/mouredev.py 适合作为入门基线,kenysdev 的"坐标池迁移"模型便于理解状态所有权,miguelex 的跨语言同构实现适合对照学习语法差异,而 hectorio23、neslarra 与 javierfiestasbotella 则在随机化、清屏刷新和彩色输出上提供了进阶思路。建议读者先读题、跑通官方版,再挑一门自己熟悉的语言仿写,最后对比目录中的社区答案,体会同一份需求在不同语言与数据模型下的表达力。
- 示例工程
【免费下载链接】roadmap-retos-programacion
Ruta de estudio basada en ejercicios de código de la comunidad MoureDev para aprender y practicar lógica usando cualquier lenguaje de programación.
相关推荐
Quadsort变体算法介绍:Blitsort、Crumsort、Fluxsort等衍生版本
Quadsort变体算法介绍:Blitsort、Crumsort、Fluxsort等衍生版本 Quadsort是一款无分支、稳定且自适应的归并排序算法,其性能超
AMD Ryzen AI Phi-3-mini-4k-instruct_rai_1.7.1_hybrid:革命性AI模型优化方案
AMD Ryzen AI Phi 3 mini 4k instruct_rai_1.7.1_hybrid:革命性AI模型优化方案 AMD Ryzen AI Ph
roadmap-retos-programacion项目:50个挑战掌握编程实战技能
roadmap retos programacion项目:50个挑战掌握编程实战技能 你是否还在为如何系统提升编程能力而烦恼?面对众多学习资源却不知从何下手?本
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考