我正在寻找类型
的功能 [[(a, b)]] -> [(a, [b])]
和Hoogle tells me there isn't one,所以我写了这个:
transpose :: (Eq a) => [[(a, b)]] -> [(a, [b])]
transpose alists = uncurry zip $ foldl combine ([], []) alists
where combine memo new = foldl tally memo new
tally ([], []) (k, v) = ([k], [[v]])
tally ((ksHead:ksRest), (vsHead:vsRest)) (k, v) =
if k == ksHead
then (ksHead:ksRest, (v:vsHead):vsRest)
else (ksHead:ks, vsHead:vs)
where (ks, vs) = tally (ksRest, vsRest) (k, v)
按重要性排序:
transpose
这个东西是正确的名字吗?修改
因为有人对这种味道感兴趣:
我正在编写一个堆栈排名应用,来自用户的选票以[(Candidate, Rank)]
的形式出现。例如,为了计算获胜者Borda count,我需要通过组合这些选票来计算每个Candidate
的排名。关于更普遍的问题的评论也是受欢迎的。
答案 0 :(得分:6)
您应该考虑使用地图数据结构,这样可以更容易:
import Data.Map (Map)
import qualified Data.Map as Map
transpose: Ord a => [(k, v)] -> Map k [v]
transpose = Map.fromListWith (++) [] . map (\(k, v) -> (k, [v])
实际上,这基本上是文档为fromListWith
提供的示例。
答案 1 :(得分:5)
transpose
通常用于表示沿矩阵转置线的列表操作。事实上,Data.List.transpose
就是这样做的。
我不完全确定您的代码正在做什么。说实话,这真是令人费解。你的意思是这样的吗?
import Data.List
import Data.Function
import Control.Arrow
groupByKey :: Ord a => [[(a, b)]] -> [(a, [b])]
groupByKey = map (fst . head &&& map snd) . groupBy kEq . sortBy kCmp . concat
where
kEq = (==) `on` fst
kCmp = compare `on` fst
如果这是你正在做的事情,将约束升级到Ord a
会将算法改进为O(n log n)而不是O(n ^ 2)。
答案 2 :(得分:1)
您可以通过使用单字母变量来帮助自己发现代码中的隐藏结构,而不是被您的名称选择隐含的含义分散注意力:
g :: (Eq a) => [[(a, b)]] -> [(a, [b])]
g alists = uncurry zip $ foldl (foldl f) ([], []) alists
where
f ([], []) (k, v) = ([k], [[v]])
f ((h:t), (u:s)) (k, v)
| k == h = (h:t, (v:u):s)
| otherwise = (h:q, u :r) where (q, r) = f (t, s) (k, v)
-- ((h:) *** (u:)) $ f (t,s) (k,v)
这有点不自然,处理unzip
ped临时数据并zip
将它们ping回输出。不需要,我们可以使用与输出相同类型的临时数据:
g2 :: (Eq a) => [[(a, b)]] -> [(a, [b])]
g2 alists = foldl (foldl f) [] alists
where
f [] (k, v) = [(k,[v])]
f ((a,b):t) (k, v)
| k == a = (a,v:b):t
| otherwise = (a,b):f t (k,v)
现在很明显,f
是一种"插入",paramorphism,
f xs (k, v) = para (\(a,b) t r -> if a==k then (a,v:b):t
else (a, b):r)
[(k,[v])] xs
para c z [] = z
para c z (x:t) = c x t $ para c z t
foldl (foldl f) [] alists === foldl f [] $ concat alists
,如果可以切换到(Ord a)
约束,则可以通过重新实现f
来提高效率,
g3 :: (Ord a) => [[(a, b)]] -> [(a, [b])]
g3 = foldl f [] . concat
where
f xs (k, v) = para (\(a,b) t r -> if k < a then (k,[v]):(a,b):t
else if a==k then (a,v:b):t
else (a, b):r)
[(k,[v])] xs
为了进一步提高此代码的复杂性,我们可以转到the other route(而不是concat
)并通过合并树加入输入列表,
g4 :: (Ord a) => [[(a, b)]] -> [(a, [b])]
g4 alists = foldt u []
. map (map (\(a,b) -> (a,[b])) . sortBy (comparing fst))
$ alists
where
u xs [] = xs
u [] ys = ys
u xs@((a,b):t) ys@((c,d):r) = case compare a c of
LT -> (a,b) : u t ys
EQ -> (a,b++d) : u t r
GT -> (c,d) : u xs r
foldt f z [] = z
foldt f z [x] = x
foldt f z xs = foldt f z $ pairwise f xs -- tree-like folding
pairwise f (a:b:t) = f a b : pairwise f t
pairwise f xs = xs
comparing
来自Data.Ord
。如果您的数据片段已经排序(可能在您的场景中),您可以省略sortBy
部分以获得额外的算法增益。因此,这个版本是一种mergesort
(可能只进行合并,没有排序)。