Data structures 更新树中的值

Data structures 更新树中的值,data-structures,functional-programming,elm,Data Structures,Functional Programming,Elm,我有一些状态被表示为一棵树 type Tree state = Branch (List (Tree state)) | Leaf state 我有一个函数来更新给定动作的单个叶子 updateLeaf : action -> state -> state 我想要一种在某种结构中表示动作的方法 type Structure action = ... 它包含一个动作和一些方法来精确地知道树中要更新的叶 例如,假设我有以下树: Branch [ Leaf "foo"

我有一些状态被表示为一棵树

type Tree state 
  = Branch (List (Tree state))
  | Leaf state
我有一个函数来更新给定动作的单个叶子

updateLeaf : action -> state -> state
我想要一种在某种结构中表示动作的方法

type Structure action = ...
它包含一个动作和一些方法来精确地知道树中要更新的叶

例如,假设我有以下树:

Branch 
  [ Leaf "foo"
  , Leaf "bar"
  , Branch 
      [ Leaf "baz"
      , Branch []
      , Leaf "qux"
      ]
  ]
我得到一些动作,比如说“你好”,我希望它只在“baz”上应用updatelaf函数

Branch 
  [ Leaf "foo"
  , Leaf "bar"
  , Branch 
      [ Leaf "bazhello"
      , Branch []
      , Leaf "qux"
      ]
  ]
假设我的UpdateLaf函数只是字符串连接或
(++)
。此外,我需要这是非常通用的,因为在结构中,它会以某种方式跟踪它希望更新的叶子在树中的位置

本质上,我所寻找的是以下功能:

updateTree 
  : (action -> state -> state)
 -> Structure action
 -> Tree state
 -> Tree state 
这将确定树中的哪个叶应用给定的更新函数

最后,我还需要它来处理地址转发。假设树的每个叶子都表示为一个按钮

viewLeaf : Address action -> state -> Html
viewLeaf address state = 
  button 
    [ onClick address ... ]
    [ ... ]
进一步假设单击按钮发送的操作将更新状态

我希望能够定义以下函数

viewTree 
  : (Address action -> state -> Html)
 -> Address (Structure action) 
 -> Tree state
 -> Html
这样我就可以查看所有这些按钮,并且相应地转发地址,以便每个按钮只影响自身。(我只对转发方面感兴趣,对视觉效果不感兴趣)

非常感谢您的帮助

您需要的是一份工作。你要求的每件事都是关于

  • 在树中指定唯一的位置
  • 允许修改焦点,同时保留其余部分
  • 这棵树很容易重新组装
  • 如果您想将修改绑定到精确的位置,那么您只需要构建一个包含拉链和动作的类型

    里面有一个关于拉链的好章节

    一旦理解了这个概念,它就很容易适用于许多其他数据结构。

    A是一种导航和修改数据结构的方法。假设你有一棵树,你选择一根树枝沿着树走下去。经过3个步骤后,您会遇到一个叶子,您可以使用来修改它。现在你想返回修改过的树,但是你有3层深,你怎么后退?假设每次你在一棵树上走下一层,你都会留下一条面包屑的痕迹,这样你就可以反转一个步骤并重建这棵树。换句话说,你有一个元组(树,面包屑),而不仅仅是一棵树

    每次选择分支时,都会将父节点和未选择的所有其他分支合并到面包屑中。然后你选择一个新的分支,这样你就改变了它的父模式和你在面包屑中没有选择的所有新分支。因此,面包屑包含所有相关信息,以按相反顺序重建前一步。让我们实现在树中按名称向上和向上移动叶子:

    import Graphics.Element exposing (show)
    
    break : (a -> Bool) -> List a -> (List a, List a)
    break p xs = case (List.head xs, List.tail xs) of
      (Nothing, Nothing) -> ([], [])
      (Just x, Just xs') -> if p x
                            then ([], xs)
                            else let (ys,zs) = break p xs'
                                 in (x::ys,zs)
    
    treeInit : Tree state -> Zipper state
    treeInit t = (t, [])
    
    treeUp : Zipper state -> Zipper state
    treeUp (subtree, Crumb l r::bs) = (Branch <| l++[subtree]++r, bs)
    
    treeTo : state -> Zipper state -> Zipper state
    treeTo name (Branch subtrees, bs) =
      let (l, x::r) = break (\(Leaf name') -> name == name') subtrees
      in (x, Crumb l r::bs)
    
    main = tree |> treeInit |> treeTo "foo" |> show
    
    如果存在大于分支大小的索引,或者您试图从根向上,等等,那么整个函数链很容易崩溃。相反,我们应该将结果包装在
    Maybe
    中,这样就不会出现崩溃,
    不会返回任何内容。但是,既然函数返回
    可能(Zipper state)
    ,而它们仍然接受
    Zipper state
    ,我们如何链接函数呢?这是您使用的地方,它的类型是
    可能是a->(a->可能是b)->可能是b
    。拉链的完整代码如下:

    import Graphics.Element exposing (show)
    import Maybe exposing (andThen)
    
    type Tree state = Branch (List (Tree state)) | Leaf state
    -- Crumb contains 2 lists:
    -- leafs/branches to the left of your focus
    -- leafs/branches to the right of your focus
    type Crumb state = Crumb (List (Tree state)) (List (Tree state))
    type alias Zipper state = (Tree state, List (Crumb state))
    
    tree : Tree String
    tree = Branch [Leaf "foo",Leaf "bar",Branch [Leaf "baz",Branch [],Leaf "qux"]]
    
    break : (a -> Bool) -> List a -> (List a, List a)
    break p xs = case (List.head xs, List.tail xs) of
      (Nothing, Nothing) -> ([], [])
      (Just x, Just xs') -> if p x
                            then ([], xs)
                            else let (ys,zs) = break p xs'
                                 in (x::ys,zs)
    
    treeInit : Tree state -> Zipper state
    treeInit t = (t, [])
    
    treeUp : Zipper state -> Maybe (Zipper state)
    treeUp (subtree, bs) = case bs of
      [] -> Nothing
      Crumb l r::bs' -> Just (Branch <| l++[subtree]++r, bs')
    
    treeTo : state -> Zipper state -> Maybe (Zipper state)
    treeTo name node = case node of
      (Branch subtrees, bs) ->
        let (l, x::r) = break (\(Leaf name') -> name == name') subtrees
        in Just (x, Crumb l r::bs)
      _ -> Nothing
    
    (!!) : List a -> Int -> Maybe a
    xs !! i = case List.tail xs of
      Nothing -> Nothing
      Just xs' -> if i == 0
                  then List.head xs
                  else xs' !! (i-1)
    
    treeToIndex : Int -> Zipper state -> Maybe (Zipper state)
    treeToIndex i (Branch subtrees, bs) =
      let newTree = subtrees!!i
      in case newTree of
        Nothing -> Nothing
        Just newTree ->
          let newCrumb = Crumb (List.take i subtrees) (List.drop (i+1) subtrees)
          in Just (newTree, newCrumb::bs)
    
    treeReplace : state -> Zipper state -> Maybe (Zipper state)
    treeReplace new node = case node of
      (Leaf old, bs) -> Just (Leaf new, bs)
      _ -> Nothing
    
    -- the function you're interested in most likely
    treeUpdate : (state -> state) -> Zipper state -> Maybe (Zipper state)
    treeUpdate f node = case node of
      (Leaf name, bs) -> Just (Leaf (f name), bs)
      _ -> Nothing
    
    main = (tree |> treeInit |> treeToIndex 2)
           `andThen` treeTo "baz" `andThen` treeReplace "xyzzy"
           `andThen` treeUp `andThen` treeUp |> show
    
    导入图形。元素公开(显示)
    导入(第三个)
    类型树状态=分支(列表(树状态))|叶状态
    --面包屑包含两个列表:
    --焦点左侧的叶/分支
    --将叶/分支移到焦点右侧
    类型crump state=crump(列表(树状态))(列表(树状态))
    类型别名拉链状态=(树状态,列表(碎屑状态))
    树:树字符串
    树=树枝[叶子“foo”,叶子“bar”,树枝[叶子“baz”,树枝[],叶子“qux”]
    中断:(a->Bool)->列表a->(列表a,列表a)
    break p xs=的大小写(List.head xs,List.tail xs)
    (无,无)->([],[])
    (只有x,只有xs')->如果px
    然后([],xs)
    否则让(ys,zs)=中断pxs'
    in(x::ys,zs)
    树单元:树状态->拉链状态
    树单位t=(t,[])
    树:拉链状态->可能(拉链状态)
    树(子树,bs)=案例bs
    []->什么都没有
    Crumb l r::bs'->Just(Branch name==name')子树
    在Just(x,crumblr::bs)中
    _->没有
    (!!):列出一个->整数->可能是一个
    xs!!i=案例列表。案例的尾部xs
    无->无
    只要xs'->如果i==0
    然后List.head-xs
    否则xs'!!(i-1)
    树形索引:Int->zippers state->Maybe(zippers state)
    树形索引i(分支子树,bs)=
    让newTree=子树!!我
    如果是
    无->无
    只是newTree->
    设newCrumb=Crumb(List.take i子树)(List.drop(i+1)子树)
    在Just中(newTree,newcrump::bs)
    树替换:状态->拉链状态->可能(拉链状态)
    树替换新节点=案例节点
    (Leaf old,bs)->Just(Leaf new,bs)
    _->没有
    --您最感兴趣的函数很可能是
    树更新:(状态->状态)->拉链状态->可能(拉链状态)
    treeUpdate f node=案例节点
    (叶名称,bs)->Just(叶(f名称,bs)
    _->没有
    main=(树|>树单元|>树索引2)
    `然后是'treeTo“baz','treetoreplace“xyzy”
    `然后是“treeUp”和“treeUp”>秀
    

    (请随时提问和澄清,我将更新和改进此答案)

    事实证明,拉链可以让人实现这一点。我将演练解决方案

    使用以下拉链

    type alias Crumb a = 
      { left  : List (Tree a)
      , right : List (Tree a)
      }
    
    type alias Zipper a = (Tree a, List (Crumb a))
    
    我们只需要实现以下功能

    zipperMap : (Zipper a -> a -> b) -> Tree a -> Tree b 
    zipperUpdate : Zipper a -> (a -> a) -> Tree a -> Tree a
    
    这些可以实现如下

    zipperMap : (Zipper a -> a -> b) -> Tree a -> Tree b 
    zipperMap f tree = 
      let 
          applyZipper ((subtree, crumbs) as zipper) = 
            case subtree of 
              Leaf a ->
                Leaf (f zipper a)
    
              Branch subtrees ->
                subtrees 
                |> List.indexedMap (\index _ -> gotoIndex index zipper |> Maybe.map applyZipper)
                |> keepJusts
                |> Branch
    
      in 
          applyZipper (fromTree tree)
    
    
    
    zipperUpdate : Zipper a -> (a -> a) -> Tree a -> Tree a
    zipperUpdate zipper f tree = 
      zipperMap (\z a -> if z == zipper then f a else a) tree
    
    type Action action state
      = ChildAction (Zipper state) action
    
    update : (action -> state -> state)
          -> Action action state
          -> Tree state 
          -> Tree state
    update updateChild action state = 
      case action of 
        ChildAction zipper childAction -> 
          zipperUpdate zipper (updateChild childAction) state
    
    view : (Address action -> state -> Html) 
        -> Address (Action action state) 
        -> Tree state 
        -> Html
    view viewChild address state = 
      let 
          viewZ zipper child = 
            let 
                childAddress = 
                  Signal.forwardTo address (ChildAction zipper)
    
            in 
                viewChild childAddress child
    
      in 
          state
          |> zipperMap viewZ
          |> toList
          |> div []
    
    其中,
    keepJusts
    is从一个可能的列表中过滤出虚无

    keepJusts : List (Maybe a) -> List a
    
    然后
    gotoIndex
    进入拉链中的第n个子树

    gotoIndex : Int -> Zipper a -> Maybe (Zipper a)
    gotoIndex index (tree, bs) = 
      case tree of
        Leaf _ ->
          Nothing
    
        Branch subtrees ->
          case nth index subtrees of 
            Nothing ->
              Nothing 
    
            Just newTree ->
              let 
                  newCrumb = 
                    { left  = List.take index subtrees
                    , right = List.drop (index + 1) subtrees
                    }
              in 
                  Just (newTree, newCrumb :: bs) 
    
    现在,给定函数
    zipperMap<
    
    update : (action -> state -> state)
          -> Action action state
          -> Tree state 
          -> Tree state
    update updateChild action state = 
      case action of 
        ChildAction zipper childAction -> 
          zipperUpdate zipper (updateChild childAction) state
    
    view : (Address action -> state -> Html) 
        -> Address (Action action state) 
        -> Tree state 
        -> Html
    view viewChild address state = 
      let 
          viewZ zipper child = 
            let 
                childAddress = 
                  Signal.forwardTo address (ChildAction zipper)
    
            in 
                viewChild childAddress child
    
      in 
          state
          |> zipperMap viewZ
          |> toList
          |> div []