Search code examples
for-looprecursionfunctional-programmingtreeelm

Elm - tree - add branch to another tree - recursive forloop


I want to move branches from tree to tree in Elm.

For example:

Tree 1:

A-1
- A-1-1
- - A-1-1-1
- - A-1-1-2
- - - A-1-1-2-1
- - - A-1-1-2-2

Tree 2

B-1
- B-1-1
- - B-1-1-1
- - B-1-1-2
- - - B-1-1-2-1
- - - B-1-1-2-2

I'd like to move A-1-1 under B-1-1-2-1 which should give

B-1
- B-1-1
- - B-1-1-1
- - B-1-1-2
- - - B-1-1-2-1
- - - - A-1-1
- - - - - A-1-1-1
- - - - - A-1-1-2
- - - - - - A-1-1-2-1
- - - - - - A-1-1-2-2
- - - B-1-1-2-2

I'm new to functional programming. I can imagine how to do this with a recursive forloop in Python but I'm stuck with Elm.

I can move one node easily but I don't see how to add the children recursively:

module Main exposing (..)

import Canopy exposing (Node, append, children, leaf, mapChildren, node, value)
import Html exposing (Html, b, div, h1, h2, li, text, ul)
import List exposing (map)


tree1 : Node String
tree1 =
    node "A-1"
        [ node "A-1-1"
            [ leaf "A-1-1-1"
            , node "A-1-1-2"
                [ leaf "A-1-1-2-1"
                , leaf "A-1-1-2-2"
                ]
            ]
        ]


tree2 : Node String
tree2 =
    node "B-1"
        [ node "B-1-1"
            [ leaf "B-1-1-1"
            , node "B-1-1-2"
                [ leaf "B-1-1-2-1"
                , leaf "B-1-1-2-2"
                ]
            ]
        ]


tree3 : Node String
tree3 =
    let
        nodeToMove =
            "A-1-1"

        newParentNode =
            "B-1-1-2-1"

        -- append the node only but not its descendants
        treeWithNewNode =
            append newParentNode nodeToMove tree2

        -- type mismatch
        --        treeWithNewNodeAndNewNodeChildren =
        --            nodeToMove |> mapChildren (\child -> append 

        -- does not do what I was hopping for
        -- newTree =
        --    mapChildrenAt
        --        nodeToMove
        --        (\child -> append newParentNode (value child) treeWithNewNode)
        --        tree2

newParentNode child tree2)
    in
    treeWithNewNode


main =
    div []
        [ h1 [] [ text "Adding a branch to another tree" ]
        , h2 [] [ text "Tree 1" ]
        , viewNode tree1
        , h2 [] [ text "Tree 2" ]
        , viewNode tree2
        , h2 [] [ text "Move A-1-1 under B-1-1-2-1" ]
        , viewNode tree3
        ]


viewNode : Node String -> Html msg
viewNode node =
    let
        subNodes =
            children node
    in
    li []
        [ b [] [ text (value node) ]
        , ul [] (List.map viewNode subNodes)
        ]

My trial is here: https://ellie-app.com/7842F8jCLpCa1

I'm using Canopy here but I could use another library if it's recommended.


Solution

  • In your code, it looks to me like you never actually extract A-1-1's children from tree1, so let's start with that:

    subtreeToMove =
        Maybe.withDefault (leaf <| "Failed to find node " ++ nodeToMove) <| get nodeToMove tree1
    

    The get function finds a node in a tree by value. Since there might not be a node with the specified value, it returns a Maybe, so we pass in a default value.

    Next you find the target node in tree2, and attach the node with its children. I'll use replaceChildrenAt here since the target node is a leaf:

    treeWithNewNode =
        tree2 |> replaceChildrenAt newParentNode [ subtreeToMove ]
    

    All done!

    Just mentioning this because you described the desired result as moving a node between trees: All data is immutable in Elm – so, after the move, tree1 and tree2 are still the same. So rather, a subtree from tree1 has been copied into a duplicate of tree2