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