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

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

(a -> a -> Bool)

однако, когда я использую (

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]]

что я упустил?

4 ответов


посмотреть С 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:xs@(x':_)) | cmp x x'  = (x:y):ys
                           | otherwise = [x]:r
  where r@(y:ys) = groupBy' cmp xs

тот факт, что "

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

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

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

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

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

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

1 > 1? Нет! Так что остается последняя группа.

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

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

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


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


Я просто хотел бы отметить, что функция groupBy также требует, чтобы ваш список был отсортирован перед применением.

например:

equalityOp :: (a, b1) -> (a, b2) -> Bool
equalityOp x y = fst x == fst y

testData = [(1, 2), (1, 4), (2, 3)]

correctAnswer = groupBy equalityOp testData == [[(1, 2), (1, 4)], [(2, 3)]]

otherTestData = [(1, 2), (2, 3), (1, 4)]

incorrectAnswer = groupBy equalityOp otherTestData == [[(1, 2)], [(2, 3)], [(1, 4)]]

это поведение происходит потому, что groupBy использует span в своем определении. Чтобы получить разумное поведение, которое не зависит от наличия базового списка в каком-либо определенном порядке, мы можем определить функцию:

groupBy' :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy' eq []     = []
groupBy' eq (x:xs) = (x:similarResults) : (groupBy' eq differentResults)
    where similarResults   = filter (eq x) xs
          differentResults = filter (not . eq x) xs