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
这样我就可以查看所有这些按钮,并且相应地转发地址,以便每个按钮只影响自身。(我只对转发方面感兴趣,对视觉效果不感兴趣)
非常感谢您的帮助您需要的是一份工作。你要求的每件事都是关于
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 []