【问题标题】:Is there a built-in for this?有内置的吗?
【发布时间】:2014-06-13 14:52:29
【问题描述】:

我正在寻找类型的函数

[[(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)

按重要性排序:

  1. 实际上是否存在 Hoogle 不知道的内置函数?
  2. transpose 是这个东西的正确名称吗?
  3. 是否有更好(更易读和/或性能更高)的编写方式?

编辑

因为有人对味道感兴趣:

我正在编写一个堆栈排名应用程序,来自用户的选票以[(Candidate, Rank)] 的形式出现。例如,为了通过Borda count 计算获胜者,我需要通过组合这些选票来计算每个Candidate 的排名。也欢迎对更普遍的问题发表评论。

【问题讨论】:

  • 我不认识这个函数,但是transpose已经是别的东西了。
  • 你到底想要这个函数做什么?您的实现执行transpose [[(1, 2), (2, 3)], [(4, 5), (6, 7)], [(4, 10)]] == [(1,[2]),(2,[3]),(4,[10,5]),(6,[7])],看起来它只是执行concat,然后执行groupBy,然后聚合第二个元素。你绝对可以比你写的简单得多,但我怀疑它已经存在了。

标签: list haskell tuples


【解决方案1】:

您应该考虑为此使用地图数据结构,这样会更容易:

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 提供的示例。

【讨论】:

  • 使用Map 解决此类问题对我来说非常有意义——这完全是正确的概念。我不明白为什么map.group.sort 会得到如此多的爱。 +1
  • 甚至还有包multimap 用于处理此类地图,其中MultiMap k vMap k [v] 同构。你有直接fromList :: Ord k => [(k, a)] -> MultiMap k a
【解决方案2】:

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)。

【讨论】:

  • +1 这是我正要发布到信中的确切解决方案(除了我保持 kEqkCmp 内联)。
  • @bheklilr 我让它们内联,但这条线真的很长。所以我在最后一刻把它们分开了。
  • 我想,考虑到这个实现仍然几乎超出了页面的边缘。它与 OP 使用的实现并不完全相同,因为它似乎颠倒了值的顺序,但在大多数情况下,我怀疑这无关紧要。
  • @Inaimathi 扩展 dfeuer 所说的内容,只需将您的数据保留为 :: [Map Candidate Rank],然后 foldl (unionWith (++)) empty 他们。
  • @WillNess - 郑重声明,你的推荐是我最终实际使用的,但我玩 on&&& 玩得很开心。
【解决方案3】:

您可以通过使用单字母变量来帮助自己发现代码中的隐藏结构,而不是被您选择的名称所暗示的含义分心:

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)

这有点不自然,处理unzipped 临时数据和zipping 它们返回输出。不需要,我们可以使用与输出相同类型的临时数据:

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(可能只做合并,没有排序)。

【讨论】:

    猜你喜欢
    • 2023-01-12
    • 2010-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-05
    • 2021-05-12
    相关资源
    最近更新 更多