榆树:如果结构是动态的(有不同的深度),如何在Dict中更新Dict?

时间:2016-11-15 16:34:08

标签: elm

给出以下fileSystem结构:

type alias FolderName = String

type Folder
    = Folder
        { folderName : String
        , childFolders : Dict FolderName Folder
        }

type alias FileSystem =
    Dict FolderName Folder


fileSystem : FildeSystem
fileSystem =
    Dict.formList
        [ ( "parent1"
          , Folder
                { folderName = "parent1"
                , childFolders =
                    Dict.fromList
                        [ ( "child1", Folder { folderName = "child1", childFolders = Dict.empty } )
                        , ( "child2", Folder { folderName = "child2", childFolders = Dict.empty } )
                        , ( "child3"
                          , Folder
                                { folderName = "child3"
                                , childFolders =
                                    Dict.formList
                                        [ ( "sub-child_of_child3", Folder { folderName = "sub-child_of_child3", childFolders = Dict.empty } )
                                        ]
                                }
                          )
                        ]
                }
          )
        ]

我希望能够 动态创建新文件夹 ,方法是调用一个函数并传入位置,我想要新文件夹到创建.. 文件夹的名称以及 fileSystem Dict 。像这样:

createFolder: String -> FileSystem -> FolderName -> FileSystem
createFolder "parent1/child3/sub-child_of_child3" fileSystem "DynamicallyCreated"

因为没有办法预先知道fileSystem Dict的样子,并且因为这是elm(no for loops) - 我认为唯一的方法就是使用递归。

代码:

createFolder location fileSystem newFolderName=
    let 
        locationAsArray = String.split "/" location
    in
                     // first find the dict value. (that is the value of 'sub-child_of_child3' key, inside 'child3' Dict.)

        findDictValueBasedOnLocation locationAsArray fileSystem

                     // then update the 'sub-child_of_child3' by inserting the newFolder. 
            |> (\ x -> { x | childFolders = Dict.insert newFolderName Folder { folderName = newFolderName, childFolders = Dict.empty } x.childFolders

                     // but how to reconsturct the child3, partent1 and finally the fileSystem now? Because this function it's supose to return a new fileSystem that contains the newly created folder. 

使用递归查找相应的dict:

findDictValueBasedOnLocation listDictKeys currentDict =
    let
        currentKey =
            List.head listDictKeys

        remainingKeys =
            List.tail listDictKeys
    in
        -- when there is only one element in the listDictKeys  that is: ["sub-child_of_child3"]-  the recursive call stops/
        if List.length listDictKeys == 1 then
           Dict.get currentKey currentDict
                |> Maybe.withDefault -- what to add here?
        else
            let
                nextDict =
                    Dict.get currentKey currentDict
                        |> Maybe.withDefault --what to add here ?-  don't know the type ..
            in
                -- recursive call with the remaining listDictKeys and nextDict which is in fact the current Dict value.
                findDictValueBasedOnContext remainingKeys nextDict

你可以在这里看到两大问题:

  1. Dict.get返回一个Maybe,我不知道如何在递归中处理它。
  2. 即使我设法找到Dict的相应部分并通过创建新文件夹来更新它; - 如何在parent1等更高级别更新我现在拥有的内容?例如: - 请记住,此更新可能会发生在20级以上的深度。如何告知级别3 2,1关于此更新?
  3. 我不一定试图使这段代码有效。我有另一种方法,它甚至更好。

    无法找到动态更新或创建Dicts中的Dicts的示例。

    我现在正在与这个问题作斗争2-3天。

    首先我尝试使用记录而不是Dicts - 因为它们允许在其中使用不同的类型。但我不能使用record."someString"来访问它的价值 - 就像在javascript中一样。所以记录没有运气。 Dicts似乎更有希望..希望有人知道如何解决这个问题。谢谢:))

2 个答案:

答案 0 :(得分:2)

这是一个很好的挑战! 首先,您正在处理递归类型(more on those here)。 Folder包含Dict FolderName Folder,所以你确实需要一些强类型。

您希望对Dict内的Dict进行递归更新。

您可以在下面找到示例解决方案代码,您可以将其复制/粘贴到http://elm-lang.org/try

不同功能的内部工作原理在代码本身中进行了解释。

一些意见:

  • 要更新Dict,常见的模式是为整个DictDict.update上的文档see here)提供密钥+更新功能。这比以下3步方法更有效:1)检索要更新的记录,2)更改记录和3)将其放回Dict
  • 如果提供的路径中的任何节点失败,则该函数将返回未更改的FileSystem
  • 如果newFolderName已存在,则将替换包含该名称的整个现有文件夹(包括所有子文件夹

希望这有助于理解榆树中的一些功能。

代码示例:

import Html exposing (text)
import Dict exposing (Dict)

---- TYPES
type alias FolderName = String

type Folder
    = Folder
        { folderName : FolderName
        , childFolders : FileSystem
        }

type alias FileSystem =
    Dict FolderName Folder


{- MAIN FUNCTION: 
takes the first element in the path
and tries to do a recursive update on the children of the fileSystem
-}
insertFolder: String -> FolderName -> FileSystem -> FileSystem
insertFolder path newFolderName fileSystem =
  let
    nodeList = String.split "/" path
  in
    case nodeList of
      node :: rest ->
        -- if we have nodes, do recursive update on folders
        fileSystem
        |> Dict.update node (Maybe.map <| updateNestedFolder newFolderName rest)

      [] ->
        -- no path, so the new folder must be a root folder
        fileSystem
        |> Dict.inset newFolderName (newFolder newFolderName)

{- Recursive update function where the magic happens
-}
updateNestedFolder : FolderName -> List FolderName -> Folder -> Folder
updateNestedFolder newFolderName nodeList (Folder { folderName, childFolders }) =
  case nodeList of
    nextLevel :: rest ->
      -- as long as there is a nodelist, we try to find deeper level
      let
        -- do recursive update on the children
        newChildFolders =
          childFolders
          |> Dict.update nextLevel (Maybe.map <| updateNestedFolder newFolderName rest)
      in
        -- return the updated folder
        Folder
          { folderName = folderName
          , childFolders = newChildFolders
          }

    [] ->
      -- this is the lowest level, so we need to add to this FileSystem
      let
        -- add newFolderName to the child folders
        newChildFolders =
          childFolders
          |> Dict.insert newFolderName (newFolder newFolderName)
      in
        -- return the folder
        Folder
          { folderName = folderName
          , childFolders = newChildFolders
          }

---- HELPERS

{- Create a new folder, without any children -}
newFolder : String -> Folder
newFolder folderName =
  Folder
    { folderName = folderName
    , childFolders = Dict.empty
    }

------ EXAMPLE


fileSystem =
  Dict.fromList
    [ ( "parent1"
      , Folder
        { folderName = "parent1"
        , childFolders =
            Dict.fromList
              [ ( "child1"
                , Folder 
                  { folderName = "child1", childFolders = Dict.empty }
                )
              , ( "child2"
                , Folder 
                  { folderName = "child2", childFolders = Dict.empty }
                )
              , ( "child3"
                , Folder
                  { folderName = "child3"
                  , childFolders =
                      Dict.fromList
                        [ ( "sub-child_of_child3"
                          , Folder 
                            { folderName = "sub-child_of_child3"
                            , childFolders = Dict.empty } 
                          )
                        ]
                  }
                )
              ]
          }
        )
      ]

main =
  text <| toString <| 
    insertFolder 
      "parent1/child3/sub-child_of_child3" 
      "DynamicallyCreated" 
      fileSystem

答案 1 :(得分:0)

上面给出的答案有效 - 但是如果你有一个围绕这个想法的问题(不一定用dicts) - 那么值得研究how zippers work

拉链是在dicts,list,trees上进行这种行走的推荐方法。更多信息:Learn You A Haskel - Zippers - 最后是文件系统实现。