Если я правильно понял вопрос, идея состоит в том, чтобы разбить входные списки на блоки, где блок - это либо один входной элемент, либо диапазон, по крайней мере, из трех последовательных элементов.
Итак, давайте начнем с определения типа данных для представления таких фрагментов:
data Chunk a = Single a | Range a a
Как видите, тип - параметр c в типе входных элементов.
Затем мы определяем функцию chunks
, чтобы фактически создать список фрагментов из списка входных элементов. Для этого нам нужна возможность сравнивать входные элементы и получать непосредственно последовательные для данного входного элемента (то есть его преемника). Следовательно, тип функции:
chunks :: (Eq a, Enum a) => [a] -> [Chunk a]
Реализация относительно проста:
chunks = foldr go []
where
go x (Single y : Single z : cs) | y == succ x && z == succ y = Range x z : cs
go x (Range y z : cs) | y == succ x = Range x z : cs
go x cs = Single x : cs
Мы просматриваем список справа налево, генерируя фрагменты, как мы go. Мы генерируем диапазон, если входной элемент предшествует двум своим непосредственным последовательным элементам (первый случай вспомогательной функции go
) или если он предшествует диапазону, который начинается с его непосредственного последующего элемента (второй случай). В противном случае мы генерируем один элемент (последний случай).
Чтобы обеспечить красивый результат, мы объявляем приложения конструктора типа Chunk
экземплярами класса Show
(при условии, что тип элементы ввода находятся в Show
):
instance Show a => Show (Chunk a) where
show (Single x ) = show x
show (Range x y) = show x ++ "-" ++ show y
Возвращаясь к примеру из вопроса, мы получаем:
> chunks [1,2,3,5,6,9,16,17,18,19]
[1-3,5,6,9,16-19]
К сожалению, все немного сложнее, если нам нужно учитывать ограниченные типы элементов; такие типы имеют самый большой элемент, для которого succ
не определено:
> chunks [maxBound, 1, 2, 3] :: [Chunk Int]
*** Exception: Prelude.Enum.succ{Int}: tried to take `succ' of maxBound
Это предполагает, что мы должны абстрагироваться от специфического c подхода для определения, следует ли один элемент за другим:
chunksBy :: (a -> a -> Bool) -> [a] -> [Chunk a]
chunksBy succeeds = foldr go []
where
go x (Single y : Single z : cs) | y `succeeds` x && z `succeeds` y =
Range x z : cs
go x (Range y z : cs) | y `succeeds` x = Range x z : cs
go x cs = Single x : cs
Теперь версия chunks
, указанная выше, может быть выражена в терминах chunksBy
, просто написав
chunks :: (Eq a, Enum a) => [a] -> [Chunk a]
chunks = chunksBy (\y x -> y == succ x)
Более того, теперь мы также можем реализовать версию для ограниченных типов ввода а также:
chunks' :: (Eq a, Enum a, Bounded a) => [a] -> [Chunk a]
chunks' = chunksBy (\y x -> x /= maxBound && y == succ x)
Это весело дает нам:
> chunks' [maxBound, 1, 2, 3] :: [Chunk Int]
[9223372036854775807,1-3]