【问题标题】:Grow a tree from parent relations in Haskell在 Haskell 中从父关系中长出一棵树
【发布时间】:2017-12-11 16:20:35
【问题描述】:

我有以下数据:

data Item a = { id :: ID
              , parentId :: Maybe ID
              , data :: a
              }

data ItemTree a = ItemTree ID a [ItemTree a]

我想拥有这个功能:buildForest :: [Item a] -> [ItemTree a],它将返回一个非平凡的森林(即尊重父母关系)。我可以假设每个项目的 ID 都是唯一的,并且所有项目都有其父列表,如果这很重要的话。

这几天我一直在努力解决这个问题。我通过改变对象(实际上是它们的副本)在 javascript 中实现了类似的代码,但我很想知道 Haskell 是如何做到这一点的。

到目前为止,我对该主题的最佳想法是:

toRelations :: Item a -> ((ID, ID), Item a)
toRelations it@{id, parentId} = ((parentId, id), it)

所以现在我可以有一个从父母到孩子的关系列表,所以箭头指向正确的方向。我仍然需要从中构造一棵树,同时避免进行缓慢的查找。这有意义吗?

【问题讨论】:

  • 我将分两步解决这个问题:首先,通过识别所有从未用作parentIdIDs 来找到结果林的所有根。获得该列表后,通过扫描输入项以查找当前节点的子节点,从每个根递归地构造树。每个步骤都是 O(n^2) 天真地完成,但可以通过提取适当的数据类型(如 Data.Map)来改进到 O(n log n)。
  • 哎呀。我对如何处理根的描述实际上是如何找到叶子。稍微后退。至少根更容易找到。即使在幼稚的情况下,也只需 O(n)。

标签: haskell tree functional-programming


【解决方案1】:

在函数式语言中,您可以通过保持作为函数参数传递的状态来编写这样的程序。在这种情况下,状态是您正在查看的“当前”项目。

import Data.Maybe (isNothing)
import Data.Tree

type ID = Int

data Item a = Item
  { _id :: ID
  , _parentId :: Maybe ID
  , _value :: a
  } deriving (Show)

type Items a = [Item a]
type ItemTree a = Tree (ID, a)

buildTreeFrom :: Items a -> Item a -> ItemTree a
buildTreeFrom m (Item i _ v) = Node (i,v) (map (buildTreeFrom m) . filter ((== Just i) . _parentId) $ m)

请注意,这取决于您陈述的假设。算法简单,直接从函数的要求出发:

  • 当前节点的值是给定Item的ID/值对
  • 当前节点的子林是根节点的父ID为当前节点ID的所有树

然后您想要的函数只需在每个 Item 上调用 buildTreeFrom,这是一个根节点(即没有父节点):

buildForest :: Items a -> Forest (ID, a)
buildForest m = map (buildTreeFrom m) . filter (isNothing . _parentId) $ m

还有一个简单的测试(使用非常方便的Data.Tree.drawForest):

>test0 = [ Item 0 Nothing 'a', Item 1 (Just 0) 'b', Item 2 (Just 0) 'c', Item 3 (Just 1) 'd'
        , Item 4 Nothing 'a', Item 5 (Just 4) 'b', Item 6 (Just 5) 'c', Item 7 (Just 6) 'd' ]
>putStrLn $ drawForest $ (fmap.fmap) show $ buildForest test0
(0,'a')
|
+- (1,'b')
|  |
|  `- (3,'d')
|
`- (2,'c')

(4,'a')
|
`- (5,'b')
   |
   `- (6,'c')
      |
      `- (7,'d')

请注意,我没有努力优化此程序。如果您认为它太慢,请先配置文件!

【讨论】:

  • 如果你使用Map.fromListWith将项目列表组织成一个从每个id到其子列表的映射,然后使用该映射到buildTreeFrom而不是过滤,你会得到一个渐近加速——我认为从 n^2 到 n log n。
【解决方案2】:

将您的 [Item a] 转换为 Map (Maybe ID) [Item a],键入每个项目的父 ID。

现在您将拥有 Nothing 下的根,对于每个根 ID,您可以使用 lookup 从映射 (dict) 中提取其子级,并以这种方式递归地填充树的下一层。

要用作Map 中的键,ID 必须在Ord 中。我认为这是一个合理的假设。我将在这里使用Int

import qualified Data.Map.Strict as Map
import Data.Maybe (maybeToList)

type ID = Int

data Item a = Item { id :: ID
                   , parentId :: Maybe ID
                   , payload :: a
                   } deriving Show

data ItemTree a = ItemTree ID a [ItemTree a] deriving Show

buildForest :: [Item a] -> [ItemTree a]
buildForest items = map mkTree roots
   where
   -- dict :: Map.Map (Maybe ID) [Item a]
      dict  = Map.fromListWith (++) [ (parentId i, [i]) | i <- items ]
      itemsUnder k = join . maybeToList . Map.lookup k $ dict
      roots = itemsUnder Nothing 
      mkTree item =                 -- using `dict`, recursively build the tree
        ItemTree (id item) (payload item) 
               . map mkTree $ itemsUnder (Just $ id item) 

这里不是传递字典,而是在(共享,内部)范围内。

【讨论】:

    猜你喜欢
    • 2014-11-01
    • 2020-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多