Haskell实现列表元素交换(模拟汉诺塔移动)报错求解
问题核心错误点
- 列表操作符
:的用法理解错误
Haskell中a : b的作用是将单个元素a追加到列表b的头部,要求a的类型和b的元素类型完全一致。你原来的代码中:newY = tail ys : [[e1]]里,tail ys是[String]类型,[[e1]]是[[String]]类型,二者拼接后newY变成了包含两个元素的[[String]],最后你返回newX : newY,结果自然就有3个子列表,不符合预期的2个。- 你后续修改为
newY = tail ys : e1时,tail ys是[String],e1是String,类型完全不匹配,所以触发了类型错误。
- 列表修改逻辑不符合需求
你需要的是修改列表指定位置的元素,而不是裁剪列表后拼接新元素。你当前的写法会直接改变两个杆的字符串数量,和汉诺塔单杆长度固定的要求冲突。 - 元素定位逻辑不完善
你当前默认xs的第一个#一定在列表头部,直接用tail xs裁剪,如果xs顶部有空位(也就是开头有多个|),逻辑就会直接出错。
修正思路
- 先给工具函数补充查找元素索引的能力,而不是只返回元素本身
- 处理第一个杆:找到第一个
#的索引,将该位置替换为|字符串 - 处理第二个杆:找到最后一个
|的索引(如果有#就是第一个#的前一位,如果没有就是列表最后一位),将该位置替换为取出的#字符串 - 最后返回两个修改后的杆组成的列表即可
修正后参考代码
import Data.Maybe (fromJust, listToMaybe) -- 查找第一个包含指定字符的元素索引 findIndex :: Char -> [String] -> Maybe Int findIndex c xs = listToMaybe [i | (i, s) <- zip [0..] xs, c `elem` s] -- 查找最后一个包含指定字符的元素索引 findLastIndex :: Char -> [String] -> Maybe Int findLastIndex c xs = listToMaybe [i | (i, s) <- zip (reverse [0..length xs - 1]) (reverse xs), c `elem` s] -- 修改列表指定位置的元素 setElem :: Int -> a -> [a] -> [a] setElem i v xs = take i xs ++ [v] ++ drop (i+1) xs switch :: [String] -> [String] -> [[String]] switch xs ys = let -- 从第一个杆取最顶部的圆盘 diskIdx = fromJust $ findIndex '#' xs disk = xs !! diskIdx -- 空杆占位符 empty = fromJust $ findStr '|' ys -- 处理第一个杆:把取走圆盘的位置改成空位 newX = setElem diskIdx empty xs -- 处理第二个杆:找到放圆盘的位置(最后一个空位) targetIdx = fromJust $ findLastIndex '|' ys newY = setElem targetIdx disk ys in [newX, newY] -- 原有的查找元素方法保留 findStr :: Char -> [String] -> Maybe String findStr c xs = listToMaybe [s | s <- xs, c `elem` s]
验证结果
用你给出的输入测试:
switch [" # " ," # # " ," # # # " ," # # # # " ," # # # # #"] [" | "," | "," | "," | "," | "]
输出与你的预期一致:
[[" | "," # # " ," # # # " ," # # # # " ," # # # # #"], [" | "," | "," | "," | "," # "]]
内容的提问来源于stack exchange,提问作者Mampenda
相关产品推荐
相关产品推荐

