假设我有一个表示为父列表的树,我想反转边缘,获取每个节点的子列表。对于这棵树 - 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)复杂性,非标准软件包对我来说不是一个选项。
答案 0 :(得分:2)
值得考虑内部使用变异的Data.Vector
或Data.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而不是列表,则无关紧要。