You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell玫瑰树节点替换:实现前序索引定位Zipper焦点

用Haskell Zipper定位玫瑰树前序遍历节点并替换

需求说明

现有两棵玫瑰树(分别含m、n个节点),需要将第一棵树前序遍历从0开始的第i个节点,替换为第二棵树前序遍历从0开始的第j个节点。

示例

树1结构:

R                                                            
  _________|____________                 
  |        |           |
  A        B           C
 / \      / \         / \
D   E    F   G       H   I

树2结构:

r
  _____|____
  |         |       
  P         Q       
 / \      / | \      
S   T    U  V  W     

将树1的第7个节点(C)替换为树2的第4个节点(Q)后,结果树:

R                                                            
  _________|____________                 
  |        |           |
  A        B           Q
 / \      / \        / | \
D   E    F   G      U  V  W

问题卡点

尝试用Zipper实现,但无法写出someFunc :: Tree a -> Int -> Zipper a函数——该函数需接收树的根节点,返回聚焦于前序遍历第i个节点的Zipper(含上下文)。

已实现将树展平为前序遍历树列表的函数:

flattenToTreeList :: Tree a -> [Tree a]
flattenToTreeList t = squish t []
  where squish (Node x ts) xs = Node x ts : foldr squish xs ts

希望类似生成Zipper列表,取第i个元素即可定位,但思路陷入循环。

解决方案

1. 先明确玫瑰树与Zipper的基础定义

补充通用的玫瑰树和Zipper上下文定义(若你已有定义可跳过):

-- 玫瑰树类型
data Tree a = Node a [Tree a] deriving (Show, Eq)

-- Zipper的上下文:记录父节点值、当前节点的左兄弟、右兄弟
data Context a = Context a [Tree a] [Tree a] deriving (Show, Eq)
-- Zipper = (当前聚焦的树, 上下文栈)
type Zipper a = (Tree a, [Context a])

2. 实现前序遍历生成Zipper列表的函数

模仿flattenToTreeList的递归逻辑,在遍历过程中维护并生成每个节点对应的Zipper:

preorderZippers :: Tree a -> [Zipper a]
preorderZippers t = go t []
  where
    -- 核心递归:当前树 + 上下文栈 -> 生成Zipper列表
    go currentTree ctx =
      -- 先记录当前节点的Zipper
      (currentTree, ctx) :
      -- 递归处理所有子树,维护子树的上下文
      case currentTree of
        Node val children -> processChildren val children ctx []
        -- 叶子节点无后续Zipper
        _ -> []
    
    -- 处理子树列表:父节点值、剩余子树、父上下文、已处理的左兄弟
    processChildren parentVal remainingChildren parentCtx leftSiblings =
      case remainingChildren of
        [] -> []
        (child : rest) ->
          let childCtx = Context parentVal (reverse leftSiblings) rest
              childZippers = go child (childCtx : parentCtx)
              restZippers = processChildren parentVal rest parentCtx (child : leftSiblings)
          in childZippers ++ restZippers

3. 实现定位第i个节点的函数

基于上面的函数,直接取列表第i个元素即可,为避免索引越界,返回Maybe类型:

someFunc :: Tree a -> Int -> Maybe (Zipper a)
someFunc t i =
  let zippers = preorderZippers t
  in if i >= 0 && i < length zippers
     then Just (zippers !! i)
     else Nothing

-- 若不考虑越界风险,可简化为:
-- someFunc t i = preorderZippers t !! i

4. 实现节点替换的完整流程

结合定位函数完成替换操作:

-- 从Zipper还原为完整树
zipperToTree :: Zipper a -> Tree a
zipperToTree (t, []) = t
zipperToTree (t, Context parentVal leftSiblings rightSiblings : ctxRest) =
  let parentChildren = reverse leftSiblings ++ [t] ++ rightSiblings
      parentTree = Node parentVal parentChildren
  in zipperToTree (parentTree, ctxRest)

-- 替换函数:tree1的第i个节点替换为tree2的第j个节点
replaceNode :: Tree a -> Int -> Tree a -> Int -> Maybe (Tree a)
replaceNode tree1 i tree2 j = do
  zipper1 <- someFunc tree1 i
  let tree2Nodes = flattenToTreeList tree2
  tree2Node <- if j >=0 && j < length tree2Nodes then Just (tree2Nodes !! j) else Nothing
  let (_, ctx) = zipper1
      newZipper = (tree2Node, ctx)
  return (zipperToTree newZipper)

示例验证

用给出的示例测试:

-- 构造树1
tree1 :: Tree Char
tree1 = Node 'R'
  [ Node 'A' [Node 'D' [], Node 'E' []]
  , Node 'B' [Node 'F' [], Node 'G' []]
  , Node 'C' [Node 'H' [], Node 'I' []]
  ]

-- 构造树2
tree2 :: Tree Char
tree2 = Node 'r'
  [ Node 'P' [Node 'S' [], Node 'T' []]
  , Node 'Q' [Node 'U' [], Node 'V' [], Node 'W' []]
  ]

-- 执行替换:tree1的第7个节点替换为tree2的第4个节点
main = print $ replaceNode tree1 7 tree2 4

运行后会输出预期的结果树。

内容的提问来源于stack exchange,提问作者ayaye

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 02:56:52