Как группировать похожие элементы в списке с помощью Haskell?

Учитывая список кортежей, например:

dic = [(1,"aa"),(1,"cc"),(2,"aa"),(3,"ff"),(3,"gg"),(1,"bb")]

Как группировать элементы dic, приводящие к списку grp где,

grp  = [(1,["aa","bb","cc"]), (2, ["aa"]), (3, ["ff","gg"])]

Я на самом деле новичок в Haskell... и, кажется, влюбился в него.
Использование группы или groupBy в Data.List будет группировать только похожие смежные элементы в списке. Я написал для этого неэффективную функцию, но это приводит к сбоям памяти, поскольку мне нужно обработать очень большой кодированный список строк. Надеюсь, вы поможете мне найти более эффективный способ.

Ответы

Ответ 1

Здесь мое решение:

import Data.Function (on)
import Data.List (sortBy, groupBy)
import Data.Ord (comparing)

myGroup :: (Eq a, Ord a) => [(a, b)] -> [(a, [b])]
myGroup = map (\l -> (fst . head $ l, map snd l)) . groupBy ((==) `on` fst)
          . sortBy (comparing fst)

Это работает, сначала отсортировав список с помощью sortBy:

[(1,"aa"),(1,"cc"),(2,"aa"),(3,"ff"),(3,"gg"),(1,"bb")]     
=> [(1,"aa"),(1,"bb"),(1,"cc"),(2,"aa"),(3,"ff"),(3,"gg")]

затем группируя элементы списка с помощью связанного ключа с помощью groupBy:

[(1,"aa"),(1,"bb"),(1,"cc"),(2,"aa"),(3,"ff"),(3,"gg")] 
=> [[(1,"aa"),(1,"bb"),(1,"cc")],[(2,"aa")],[(3,"ff"),(3,"gg")]]

а затем преобразование сгруппированных элементов в кортежи с помощью map:

[[(1,"aa"),(1,"bb"),(1,"cc")],[(2,"aa")],[(3,"ff"),(3,"gg")]] 
=> [(1,["aa","bb","cc"]), (2, ["aa"]), (3, ["ff","gg"])]`)

Тестирование:

> myGroup dic
[(1,["aa","bb","cc"]),(2,["aa"]),(3,["ff","gg"])]

Ответ 2

Если возможно, повторно используйте библиотечный код.

import Data.Map
sortAndGroup assocs = fromListWith (++) [(k, [v]) | (k, v) <- assocs]

Попробуйте в ghci:

*Main> sortAndGroup [(1,"aa"),(1,"cc"),(2,"aa"),(3,"ff"),(3,"gg"),(1,"bb")]
fromList [(1,["bb","cc","aa"]),(2,["aa"]),(3,["gg","ff"])]

Ответ 3

Также вы можете использовать расширение TransformListComp, например:

Prelude> :set -XTransformListComp 
Prelude> import GHC.Exts (groupWith, the)
Prelude GHC.Exts> let dic = [ (1, "aa"), (1, "bb"), (1, "cc") , (2, "aa"), (3, "ff"), (3, "gg")]
Prelude GHC.Exts> [(the key, value) | (key, value) <- dic, then group by key using groupWith]
[(1,["aa","bb","cc"]),(2,["aa"]),(3,["ff","gg"])]

Ответ 4

  • Если список не отсортирован по первому элементу, я не думаю, что вы можете сделать лучше, чем O (nlog (n)).

    • Один простой способ - просто sort, а затем использовать что-нибудь из ответа второй части.

    • Вы можете использовать из Data.Map карту типа Map k [a] для использования первого элемента кортежа в качестве ключа и продолжать добавлять значения.

    • Вы можете написать собственную сложную функцию, которая даже после всех попыток все равно будет выполнять O (nlog (n)).

  • Если список отсортирован по первому элементу, как это имеет место в вашем примере, тогда задача тривиальна для чего-то типа groupBy, как указано в ответе @Mikhail или использует foldr, и существует множество других способов.

Пример использования foldr:

  grp :: Eq a => [(a,b)] -> [(a,[b])]
  grp = foldr f []
     where 
       f (z,s) [] = [(z,[s])] 
       f (z,s) [email protected]((x,y):xs)  | x == z = (x,s:y):xs 
                             | otherwise = (z,[s]):a

Ответ 5

{-# LANGUAGE TransformListComp #-}

import GHC.Exts
import Data.List
import Data.Function (on)

process :: [(Integer, String)] -> [(Integer, [String])]
process list = [(the a, b) |  let info = [ (x, y) | (x, y) <- list, then    sortWith by y ], (a, b) <- info, then group by a using groupWith]