如何在F#中基于输入动态创建递归Discriminated Union?
解决F#中基于缩进语法动态构建递归联合类型命令树的问题
首先,你的核心问题是要基于缩进层级的命令列表,递归构建出Command联合类型的嵌套结构。当前的buildProgram函数逻辑有缺陷,主要是没有正确处理嵌套层级的边界——当遇到缩进级别低于当前层级的命令时,应该停止当前Repeat块的收集,返回已构建的子命令列表,让上层继续处理。
我们可以通过调整递归函数的逻辑,让它同时返回已构建的命令列表和剩余未处理的命令项,这样就能正确划分不同缩进层级的命令块。下面是完整的解决方案:
第一步:完善基础类型和辅助函数
首先补全缺失的Direction类型,并实现几个可靠的辅助解析函数:
type Direction = Left | Right type Command = | Repeat of int * Command list | Forward of int | Turn of Direction * int // 辅助函数:将命令字符串拆分为令牌列表,自动过滤空字符串 let splitCommand (cmdStr: string) = cmdStr.Trim().Split([|' '|], System.StringSplitOptions.RemoveEmptyEntries) |> Array.toList // 辅助函数:将字符串转换为Direction类型 let parseDirection = function | "Left" -> Left | "Right" -> Right | dir -> failwith $"无效方向指令:{dir}" // 辅助函数:将字符串转换为整数,处理非法输入 let parseInt (s: string) = match System.Int32.TryParse(s) with | true, n -> n | false, _ -> failwith $"无效数字:{s}"
第二步:实现递归的命令树构建函数
核心思路是让递归函数同时返回已构建的命令列表和剩余未处理的命令项,以此精准控制每个嵌套层级的命令范围:
let buildProgram (diagram: (int * string) list) : Command list = let rec build currentLevel remainingItems = match remainingItems with | [] -> [], [] | (indent, cmdStr)::rest -> // 缩进级别低于当前,停止当前层级收集,返回剩余项给上层处理 if indent < currentLevel then [], remainingItems elif indent = currentLevel then // 解析当前同级命令 match splitCommand cmdStr with | ["Forward"; numStr] -> let cmd = Forward (parseInt numStr) let subCmds, rest' = build currentLevel rest cmd::subCmds, rest' | ["Turn"; dirStr; numStr] -> let cmd = Turn (parseDirection dirStr, parseInt numStr) let subCmds, rest' = build currentLevel rest cmd::subCmds, rest' | ["Repeat"; numStr] -> let repeatCount = parseInt numStr // 递归收集下一级缩进的所有命令作为Repeat的子列表 let subCmds, rest' = build (currentLevel + 1) rest let cmd = Repeat (repeatCount, subCmds) // 继续收集当前层级剩余的命令 let remainingCmds, rest'' = build currentLevel rest' cmd::remainingCmds, rest'' | _ -> failwith $"无法解析的命令:{cmdStr}" else // 缩进级别高于当前,属于上层Repeat的子命令,直接返回剩余项 [], remainingItems // 从缩进级别0开始构建完整命令树 let commands, _ = build 0 diagram commands
第三步:测试验证
用你给出的示例文本测试,最终生成的命令树和手动硬编码的结构完全一致:
let sampleText = """ Forward 75 Repeat 4 Forward 10 Turn Right 50 Repeat 6 Forward 20 Turn Right 60 Repeat 8 Forward 15 Turn Left 30 Turn Right 10 Forward 25 """ // 先拆分缩进层级,再构建命令树 let commandTree = sampleText |> splitIndent |> buildProgram
为什么你的原函数无法正常工作?
你的collect函数没有正确处理剩余命令的传递逻辑:当处理Repeat时,你直接调用collect (i+1) xs [],但没有把处理完子列表后的剩余命令返回给上层,导致后续的同级命令被错误地包含进子列表,或者直接被忽略。通过让递归函数返回(已构建命令列表, 剩余命令项),我们能精准控制每个层级的命令范围,完美解决嵌套递归的问题。
内容的提问来源于stack exchange,提问作者Ryan156
相关产品推荐
相关产品推荐

