haskell分组问题

时间:2009-11-11 09:17:34

标签: haskell

group :: Ord a => [(a, [b])] -> [(a, [b])]

我想查找具有相同fst的所有对,并将它们合并,通过将所有bs列表附加在一起,它们具有相同的a并丢弃unnessecary对等等...

我得到了:

group ((s, ls):(s', ls'):ps) = 
    if s == s' 
    then group ((s, ls++ls'):ps) 
    else (s, ls) : group ((s', ls'):ps)
group p = p

但显然这不会削减它,因为它不会对所有内容进行分组。

编辑: 示例

[("a", as),("c", cs), ("c", cs3), ("b", bs),("c", cs2), ("b", bs2)]

会输出

[("a", as),("c", cs++cs2++cs3),("b", bs++bs2)]

3 个答案:

答案 0 :(得分:11)

barkmadley's answer的两种替代解决方案:

  • 在注释中注释Tirpen,解决此问题的最佳方法取决于输入列表元组中不同第一个元素的数量 m 。对于 m 的小值,barkmadley使用Data.List.partition是可行的方法。但是对于较大的值,算法的 O(n * m)的复杂度并不是那么好。在这种情况下, O(n log n)类型的输入可能会变得更快。因此,

    import Data.List (groupBy, sortBy)
    combine :: (Ord a) => [(a, [b])] -> [(a, [b])]
    combine = map mergeGroup . myGroup . mySort
      where
        mySort = sortBy (\a b -> compare (fst a) (fst b))
        myGroup = groupBy (\a b -> fst a == fst b)
        mergeGroup ((a, b):xs) = (a, b ++ concatMap snd xs)
    

    这会在barkmadley的输入上产生[("Dup",["2","3","1","5"]),("Non",["4"])]

  • 或者,我们可以在Data.Map

    的帮助下致电
    import Data.Map (assocs, fromListWith)
    combine :: (Ord a) => [(a, [b])] -> [(a, [b])]
    combine = assocs . fromListWith (++)
    

    这将产生[("Dup",["5","1","2","3"]),("Non",["4"])],这可能是也可能不是问题。如果是,那么又有两个解决方案:

    • 首先使用Data.List.reverse

      反转输入
      import Data.List (reverse)
      import Data.Map (assocs, fromListWith)
      combine :: (Ord a) => [(a, [b])] -> [(a, [b])]
      combine = assocs . fromListWith (++) . reverse
      
    • Prepend(flip (++))而不是追加((++)(感谢barkmadley;我更喜欢这个解决方案)

      import Data.Map (assocs, fromListWith)
      combine :: (Ord a) => [(a, [b])] -> [(a, [b])]
      combine = assocs . fromListWith (flip (++))
      

    这两个定义都会导致combine输出[("Dup",["2","3","1","5"]),("Non",["4"])]

作为最后一点,请注意combine的所有这些定义都要求输入列表中元组的第一个元素是类Ord的实例。 barkmadley的实现只要求这些元素是Eq的实例。因此,存在可以由他的代码处理的输入,但不是由我的处理。

答案 1 :(得分:6)

import Data.List hiding (group)

group :: (Eq a) => [(a, [b])] -> [(a, [b])]
group ((s,l):rest) = (s, l ++ concatMap snd matches) : group nonmatches
    where
        (matches, nonmatches) = partition (\x-> fst x == s) rest
group x = x

此函数产生结果:

group [("Dup", ["2", "3"]), ("Dup", ["1"]), ("Non", ["4"]), ("Dup", ["5"])]
    = [("Dup", ["2", "3", "1", "5"]), ("Non", ["4"])]

它的工作原理是将剩余的比特过滤成两个阵营,匹配的比特和不匹配的比特。然后它将那些匹配和递归的那些组合在一起。这实际上意味着输入列表中每个'key'的输出列表中将有一个元组。

答案 2 :(得分:0)

另一种解决方案,使用折叠来累积Map中的组。由于地图,这确实要求aOrd的实例(BTW,您的原始定义要求aEq的实例,而barkmadley已将其合并到其中溶液)。

import qualified Data.Map as M

group :: Ord a => [(a, [b])] -> [(a, [b])]
group = M.toList . foldr insert M.empty
  where
    insert (s, l) m = M.insertWith (++) s l m

如果您是默默无闻的忠实粉丝,请将最后一行替换为:

    insert = uncurry $ M.insertWith (++)

这省略了不必要的muncurry(s, l)对分成两个参数sl