Подтвердить что ты не робот

Списки списков списков

Что представляет собой хороший способ представления типа LoL a, являющегося списком списков of... of a? Уровень гнездования является произвольным, но однородным по всем элементы внешнего списка.

Случай, который я имею в виду, - это применить группировку на членах список, а затем применить следующую группировку для каждой подгруппы и т.д. Это пока не известно, сколько групп придется применять. Следовательно:

rGroupBy :: [(a -> a -> Bool)] -> [a] -> [...[a]...]

Дополнительные точки коричневого цвета для сигнатуры типа rGroupBy; -)

Пример:

Предположим, что deweyGroup i группирует элементы на основе i-го числа

rGroupBy [deweyGroup 1, deweyGroup 2] 
         ["1.1", "1.2.1", "1.2.2", "2.1", "2.2", "3"]

дает:

[ [ [ "1.1" ], [ "1.2.1", "1.2.2" ] ],
  [ [ "2.1" ], [ "2.2" ] ],
  [ [ "3" ] ]
]

Postscript

Через день у нас есть 4 отличных и дополнительных решения. Я очень доволен ответами; спасибо всем.

4b9b3361

Ответ 1

Я считаю, что следующий пример должен быть близок к тому, что вы имели в виду. Сначала мы объявляем натуральные числа на уровне типа. Затем мы определяем векторы, которые несут их длину как тип phantom (см. векторы фиксированной длины в Haskell, часть 1: Использование GADT). И затем мы определяем структуру для вложенных списков списков..., которая несет глубину как тип phantom. Наконец, мы можем правильно определить тип rGroupBy.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE EmptyDataDecls #-}

import Data.List (groupBy)

data Zero
data Succ n

data Vec n a where
    Nil  ::                 Vec Zero a
    Cons :: a -> Vec n a -> Vec (Succ n) a

data LList n a where
    Singleton :: a           -> LList Zero a
    SuccList  :: [LList n a] -> LList (Succ n) a

-- Not very efficient, but enough for this example.
instance Show a => Show (LList n a) where
    showsPrec _ (Singleton x)   = shows x
    showsPrec _ (SuccList lls)  = shows lls

rGroupBy :: Vec n (a -> a -> Bool) -> [a] -> LList (Succ n) a
rGroupBy Nil
    = SuccList . map Singleton
rGroupBy (Cons f fs)
    = SuccList . map (rGroupBy fs) . groupBy f

-- TEST ------------------------------------------------------------

main = do
    let input = ["1.1", "1.2.1", "1.2.2", "2.1", "2.2", "3"]

    -- don't split anything
    print $ rGroupBy Nil input
    -- split on 2 levels
    print $ rGroupBy (Cons (deweyGroup 1) 
                           (Cons (deweyGroup 2) Nil))
               input 
  where
    deweyGroup :: Int -> String -> String -> Bool
    deweyGroup i a b = a!!idx == b!!idx where idx = 2*(i-1)

Ответ 2

Другой способ принудительного применения ограничения, что все ветки имеют равную глубину, заключается в использовании вложенного типа данных:

data LoL a = One [a] | Many (LoL [a])

mapLoL :: ([a] -> [b]) -> LoL a -> LoL b
mapLoL f (One xs) = One (f xs)
mapLoL f (Many l) = Many $ mapLoL (map f) l

rGroupBy :: [a -> a -> Bool] -> [a] -> LoL a
rGroupBy [] xs = One xs
rGroupBy (f:fs) xs = Many $ mapLoL (groupBy f) $ rGroupBy fs xs

Разлагая определение LoL, мы видим, что неформально

LoL a = [a] | [[a]] | [[[a]]] | ...

Тогда мы можем сказать, например:

ghci> rGroupBy [(==) `on` fst, (==) `on` (fst . snd)] [ (i,(j,k)) | i<-[1..3], j<-[1..3], k<-[1..3]]

чтобы вернуться

Many (Many (One [[[(1,(1,1)),(1,(1,2)),(1,(1,3))]],[[(1,(2,1)),(1,(2,2)),(1,(2,3)), ...

Ответ 3

На самом деле у вас есть дерево. Попробуйте представить его с рекурсивной структурой данных:

data LoL a = SoL [a] | MoL [LoL a] deriving (Eq, Show)

rGroupBy :: [(a -> a -> Bool)] -> [a] -> LoL a
rGroupBy (f:fs) = MoL . map (rGroupBy fs) . groupBy f
rGroupBy []     = SoL

deweyGroup :: Int -> String -> String -> Bool
deweyGroup i a b = a!!idx == b!!idx where idx = 2*(i-1)

rGroupBy [deweyGroup 1, deweyGroup 2] ["1.1", "1.2.1", "1.2.2", "2.1", "2.2", "3.0"] дает:

MoL [MoL [SoL ["1.1"],
          SoL ["1.2.1","1.2.2"]],
     MoL [SoL ["2.1"],
          SoL ["2.2"]],
     MoL [SoL ["3.0"]]
    ]

Ответ 4

Если вы хотите обеспечить равномерную глубину, существует (справедливый) стандартный трюк, чтобы сделать это с использованием полиморфной рекурсии. То, что мы будем делать, - это позвоночник "более глубоких" конструкторов, рассказывающий, насколько глубоко вложен список, а затем окончательный конструктор "здесь" с глубоко вложенным списком:

data GroupList a = Deeper (GroupList [a]) | Here a deriving (Eq, Ord, Show, Read)

Фактически, тип, определенный как определенный, имеет один эстетический выбор, который вы можете изменить в своем коде: конструктор Here принимает один a, а не список a s. Последствия этого выбора вроде бы разбросаны по остальной части этого ответа.

Здесь пример значения этого типа, показывающий списки списков; он имеет два конструктора Deeper, соответствующих глубине-два вложения, которые он имеет:

> :t Deeper (Deeper (Here [[1,2,3], []]))
Num a => GroupList a

Вот несколько примеров функций.

instance Functor GroupList where
    fmap f (Here   a ) = Here   (f a)
    fmap f (Deeper as) = Deeper (fmap (fmap f) as)
    -- the inner fmap is at []-type

-- this type signature is not optional
flatten :: GroupList [a] -> GroupList a
flatten (Here   a ) = Deeper (Here a)
flatten (Deeper as) = Deeper (flatten as)

singleGrouping :: (a -> a -> Bool) -> GroupList [a] -> GroupList [a]
singleGrouping f = flatten . fmap (groupBy f)

rGroupBy :: [a -> a -> Bool] -> [a] -> GroupList [a]
rGroupBy fs xs = foldr singleGrouping (Here xs) fs

Ответ 5

В качестве упражнения типа хакерства это можно реализовать со стандартными списками.

Все, что нам нужно, это произвольная функция depthStringBy:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts,
  UndecidableInstances, IncoherentInstances,
  TypeFamilies, ScopedTypeVariables #-}

import Data.List
import Data.Function

class StringGroupable a b where
    groupStringBy :: Pred -> a -> b

instance (StringGroupable a b, r ~ [b]) => StringGroupable [a] r where
    groupStringBy f = map (groupStringBy f)

instance (r ~ [[String]]) => StringGroupable [String] r where
    groupStringBy p = groupBy p

Что работает следующим образом:

*Main> let lst = ["11","11","22","1","2"]
*Main> groupStringBy ((==) `on` length) lst
[["11","11","22"],["1","2"]]
*Main> groupStringBy (==) . groupStringBy ((==) `on` length) $ lst
[[["11","11"],["22"]],[["1"],["2"]]]

Таким образом, мы можем использовать эту функцию напрямую (хотя ее нужно поместить в обратном порядке):

inp = ["1.1", "1.2.1", "1.2.2", "2.1", "2.2", "3"]

deweyGroup :: Int -> String -> String -> Bool
deweyGroup i a b = a!!idx == b!!idx where idx = 2*(i-1)

-- gives: [[["1.1"],["1.2.1","1.2.2"]],[["2.1"],["2.2"]],[["3"]]]
test1 = groupStringBy (deweyGroup 2) . groupStringBy (deweyGroup 1) $ inp

Но если вы хотите использовать свой оригинальный образец, мы можем его взломать. Сначала нам нужна переменная функция аргумента, которая передает все аргументы, но последние в обратном порядке через ., а затем применяет полученную функцию к последнему аргументу:

class App a b c r where
    app :: (a -> b) -> c -> r

instance (b ~ c, App a d n r1, r ~ (n -> r1)) => App a b (c -> d) r where
    app c f = \n -> app (f . c) n

instance (a ~ c, r ~ b) => App a b c r where
    app c a = c a

Работает следующим образом:

*Main> app not not not True
False
*Main> app (+3) (*2) 2
10

Затем разверните его с помощью специального правила для нашего типа предикатов type Pred = String -> String -> Bool:

type Pred = String -> String -> Bool

instance (StringGroupable b c, App a c n r1, r ~ (n -> r1)) => App a b Pred r where
    app c p = app ((groupStringBy p :: b -> c) . c)

И, наконец, оберните его в rGroupBy (функция id будет первой в конвейере):

rGroupBy :: (App [String] [String] Pred r) => Pred -> r
rGroupBy p = app (id :: [String] -> [String]) p

Теперь он должен работать для любого числа предикатов группировки типа Pred, создавая список глубины, равный числу предикатов, которые были поставлены:

-- gives: [["1.1","1.2.1","1.2.2"],["2.1","2.2"],["3"]]
test2 = rGroupBy (deweyGroup 1) inp

-- gives: [[["1.1"],["1.2.1","1.2.2"]],[["2.1"],["2.2"]],[["3"]]]
test3 = rGroupBy (deweyGroup 1) (deweyGroup 2) inp

-- gives: [[[["1.1"]],[["1.2.1","1.2.2"]]],[[["2.1"]],[["2.2"]]],[[["3"]]]]
test4 = rGroupBy (deweyGroup 1) (deweyGroup 2) (deweyGroup 1) inp

Таким образом, это возможно (и, вероятно, может быть упрощено), но, как всегда, с подобным хакером не рекомендуется использовать ни для чего, кроме упражнения.