什么是" Haskell方式"转置图表?

时间:2014-09-07 10:45:54

标签: haskell functional-programming

假设我有一个表示为父列表的树,我想反转边缘,获取每个节点的子列表。对于这棵树 - http://i.stack.imgur.com/uapqT.png - 转换看起来像:

[0,0,0,1,1,2,5,4,4] -> [[2,1],[4,3],[5],[],[8,7],[6],[],[],[]]
但是,它并不仅限于图形转置。我还有一些其他的问题,我将通过以下方式用命令式语言解决:遍历一些源数据数组并非顺序更新结果数组,因为我对它有所了解。

基本上,我的问题是"什么是Haskell解决这类问题的惯用方法?"。据我所知,我可以通过可变向量以命令的方式做到,但是不是有一些纯函数方法吗?如果没有,我将如何正确使用mutable?

最后,我需要它快速工作,即 O(n)复杂性,非标准软件包对我来说不是一个选项。

2 个答案:

答案 0 :(得分:2)

值得考虑内部使用变异的Data.VectorData.Array中的纯函数,以便更有效(两个库中的accum - s,加上construct中的展开和vector - s。

当我们在构建期间不关心阵列的中间状态时,accum - s很棒。虽然我们必须为节点键提供范围,但它们非常适用于转置图形:

{-# LANGUAGE TupleSections #-}

import qualified Data.Array as A

type Graph = [(Int, [Int])]

transpose :: (Int, Int) -> Graph -> Graph
transpose range g =
    A.assocs $ A.accumArray (flip (:)) [] range (do {(i, ns) <- g; map (,i) ns})

这里我们首先将图形展开到邻接列表中,但是使用交换的索引对,然后将它们累积到一个数组中。它大致与可变阵列上的标准命令循环一样快,并且它比ST monad更方便。

或者,我们可以使用IntMap,可能与State monad一起使用,只是按原样移植我们的命令式算法,并且性能在大多数情况下都会令人满意。

幸运的是IntMap提供了许多高阶函数,因此我们不会(总是)被迫以强制风格编程。例如,accum有一个类似物:

import qualified Data.IntMap.Strict as IM

transpose :: Graph -> Graph
transpose g =
  IM.assocs $ IM.fromListWith (++) (do {(i, ns) <- g; (i,[]) : map (,[i]) ns})

答案 1 :(得分:1)

一种纯粹的功能方式是使用地图存储信息,生成 O(n log n)算法:

import qualified Data.IntMap as IM
import Data.Maybe (fromMaybe)

childrenMap :: [Int] -> IM.IntMap [Int]
childrenMap xs = foldr addChild IM.empty $ zip xs [0..]
  where
    addChild :: (Int, Int) -> IM.IntMap [Int] -> IM.IntMap [Int]
    addChild (parent, child) = IM.alter (Just . (child :) . fromMaybe []) parent

您也可以使用命令式解决方案并使用ST monad保持纯净,这显然是 O(n),但命令性代码有点模糊了主要想法:

import Control.Monad (forM_)
import Data.Array
import Data.Array.MArray
import Data.Array.ST

childrenST :: [Int] -> [[Int]]
childrenST xs = elems $ runSTArray $ do
    let l = length xs
    arr <- newArray (0, l - 1) []
    let add (parent, child) =
          writeArray arr parent . (child :) =<< readArray arr parent
    forM_ (zip xs [0..]) add
    return arr

这种方法的一个缺点是索引超出范围,它只是失败。 另一个是你遍历列表两次。但是,如果您在任何地方使用arrays而不是列表,则无关紧要。