Haskell: удивительное поведение "groupBy"

Я пытаюсь выяснить поведение библиотечной функции groupBy (из Data.List), которая связывает элементы списка с помощью функции проверки равенства, переданной в качестве первого аргумента. Подпись типа предполагает, что тест равенства должен иметь тип

(a -> a -> Bool)

Однако, когда я использую (<) как "тест равенства" в GHCi 6.6, результаты не являются тем, что я ожидаю:

ghci> groupBy (<) [1, 2, 3, 2, 4, 1, 5, 9]
[[1,2,3,2,4],[1,5,9]]

Вместо этого я ожидал бы бега строго возрастающего числа, например:

[[1,2,3],[2,4],[1,5,9]]

Что мне не хватает?

Ответы

Ответ 1

Посмотрите на реализацию ghc groupBy:

groupBy                 :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy _  []           =  []
groupBy eq (x:xs)       =  (x:ys) : groupBy eq zs
                           where (ys,zs) = span (eq x) xs

Теперь сравните эти два выхода:

Prelude List> groupBy (<) [1, 2, 3, 2, 4, 1, 5, 9]
[[1,2,3,2,4],[1,5,9]]
Prelude List> groupBy (<) [8, 2, 3, 2, 4, 1, 5, 9]
[[8],[2,3],[2,4],[1,5,9]]

Короче говоря, происходит следующее: groupBy предполагает, что данная функция (первый аргумент) проверяет равенство и, следовательно, предполагает, что функция сравнения рефлексивна, транзитивна и симметрична (см. отношение эквивалентности). Проблема здесь в том, что отношение меньше, чем рефлексивное, не симметричное.


Изменить. Следующая реализация предполагает только транзитивность:

groupBy' :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy' _   []                        = []
groupBy' _   [x]                       = [[x]]
groupBy' cmp (x:[email protected](x':_)) | cmp x x'  = (x:y):ys
                           | otherwise = [x]:r
  where [email protected](y:ys) = groupBy' cmp xs

Ответ 2

Тот факт, что "<" не является критерием равенства.

Вы можете ожидать некоторого поведения, потому что вы будете реализовывать его по-другому, но это не то, что он promises.

Примером того, почему он выводит, является разумный ответ, если он пробирается через него, делая

[1, 2, 3, 2, 4, 1, 5, 9] ->
[[1,2,3], [2,4], [1,5,9]]

Теперь имеет 3 группы равных элементов. Поэтому он проверяет, является ли кто-либо из них одним и тем же:

Поскольку он знает, что все элементы в каждой группе равны, он может просто посмотреть на первый элемент в каждом, 1, 2 и 1.

1 > 2? Да! Таким образом, он объединяет первые две группы.

1 > 1? Нет! Поэтому он оставляет последнюю группу.

И теперь он сравнивал все элементы для равенства.

..., вы не передали ему ту функцию, которую она ожидала.

Короче говоря, когда он хочет провести тест равенства, дайте ему тест равенства.

Ответ 3

Проблема заключается в том, что эталонная реализация groupBy в отчете Haskell сравнивает элементы с первым элементом, поэтому группы не сильно увеличиваются (они просто должны быть больше, чем первый элемент). Вместо этого вам нужна версия groupBy, которая тестирует соседние элементы, такие как реализация здесь.