Haskell equivalente al grupo de ScalaBy
(5)
Scala tiene una función groupBy
en listas que acepta una función para extraer claves de los elementos de la lista, y devuelve otra lista donde los elementos son tuplas que consisten en la clave y la lista de elementos que producen esa clave. En otras palabras, algo como esto:
List(1,2,3,4,5,6,7,8,9).groupBy(_ % 2)
// List((0, List(2,4,6,8)), (1, List(1,3,5,7,9)))
(En realidad, parece que en las versiones actuales proporciona un Map
lugar, pero eso no es importante). C # tiene una versión aún más útil que le permite asignar los valores al mismo tiempo (muy útil si, por ejemplo, su función clave es simplemente extraer parte de una tupla).
Haskell tiene un groupBy
, pero es algo diferente: agrupa series de cosas de acuerdo con alguna función de comparación.
Antes de ir y escribirlo, ¿hay un equivalente al grupo de Scala groupBy
en Haskell? Hoogle no tiene nada de lo que yo esperaría que se viera la firma (a continuación), pero es posible que me haya equivocado.
Eq b => (a -> b) -> [a] -> [(b,[a])]
Específicamente, lo siguiente debería funcionar:
scalaGroupBy f = groupBy ((==) `on` f) . sortBy (comparing f)
Módulo: esto no le da el resultado de f
en cada grupo, pero si realmente lo necesita, siempre puede postprocesar con
map (/xs -> (f (head xs), xs)) . scalaGroupBy f
Esta no es una función en la biblioteca de listas.
Puedes escribirlo como la composición de sortBy y groupBy.
Esta solución se romperá y se agrupará en (fx), independientemente de si está ordenada o no
f = (`mod` (2::Int))
list = [1,3,4,6,8,9] :: [Int]
myGroupBy :: Eq t => (b -> t) -> [b] -> [(t, [b])]
myGroupBy f (z:zs) = reverse $ foldl (g f) [(f z,[z])] zs
where
-- folding function
g f ((tx, xs):previous) y = if (tx == ty)
then (tx, y:xs):previous
else (ty, [y]):(tx, reverse xs):previous
where ty = f y
main = print $ myGroupBy f list
resultado: [(1, [1,3]), (0, [4,6,8]), (1, [9])]
Poner una trace
en f
revela que, con la solución @Niklas, f
se evalúa 3 veces para cada elemento en cualquier lista de longitud 2 o más. Me tomé la libertad de modificarlo para que f
se aplique a cada elemento solo una vez. Sin embargo, no está claro si el costo de crear y destruir las tuplas es menor que el costo de evaluar f
varias veces (ya que f
puede ser arbitrario).
import Control.Arrow ((&&&))
import Data.List
import Data.Function
myGroupBy'' :: (Ord b) => (a -> b) -> [a] -> [(b, [a])]
myGroupBy'' f = map (fst . head &&& map snd)
. groupBy ((==) `on` fst)
. sortBy (compare `on` fst)
. map (f &&& id)
Puede escribir la función usted mismo con bastante facilidad, pero debe colocar una restricción Ord
o Hashable
en el resultado de la función clasificadora si desea una solución eficiente. Ejemplo:
import Control.Arrow ((&&&))
import Data.List
import Data.Function
myGroupBy :: (Ord b) => (a -> b) -> [a] -> [(b, [a])]
myGroupBy f = map (f . head &&& id)
. groupBy ((==) `on` f)
. sortBy (compare `on` f)
> myGroupBy (`mod` 2) [1..9]
[(0,[2,4,6,8]),(1,[1,3,5,7,9])]
También puede usar un mapa hash como Data.HashMap.Strict
lugar de ordenar el tiempo lineal esperado.