2009-08-22 13 views
15

Próbuję zrozumieć zachowanie biblioteki function groupBy (z Data.List), która służy do grupowania elementów listy za pomocą funkcji "testu równości" przekazywanej jako pierwszy argument. Podpis typ sugeruje, że test równości prostu musi mieć typHaskell: zaskakujące zachowanie "groupBy"

(a -> a -> Bool) 

Jednak kiedy używam (<) jako „test równości” w GHCi 6.6, wyniki nie są, czego można oczekiwać:

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

Zamiast tego będę oczekiwać serie ściśle rosnącej liczby, na przykład:

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

Czego mi brakuje?

Odpowiedz

34

Wystarczy popatrzeć na ghc realizacji groupBy:

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

teraz porównać te dwa wyjścia:

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

w skrócie, co się dzieje, jest to: groupBy zakłada, że ​​dana funkcja (pierwszy argument) sprawdza równość, a tym samym zakłada, że ​​funkcja porównawcza to reflexive, przechodnia i symetryczny (patrz equivalence relation). Problem polega na tym, że relacja o wartości mniejszej niż nie jest zwrotna ani symetryczna.


Edit: Poniższy realizacja zakłada tylko przechodniości:

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 
+1

Dziękuję. Nie zdawałem sobie sprawy, że dokumentacja wymaga, aby test równości był relacją równoważności. – Pillsy

+0

Nie mówi, że musi to być relacja równoważności. W rzeczywistości istnieją użyteczne rzeczy, które można z nim zrobić, używając relacji nie-równoważności. na przykład http://stackoverflow.com/questions/930675/functional-paragraphs/930765#930765 – newacct

9

fakt, że "<" nie jest testem równości.

Można oczekiwać pewnych zachowań, ponieważ można je inaczej wdrożyć, ale to nie jest to, co obiecuje.

Przykładem dlaczego co wyprowadza jest rozsądna odpowiedź brzmi, czy to wymiata przez to, robi

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

Teraz ma 3 grupy równych elementów. Więc to sprawdza, czy któryś z nich są w istocie takie same:

Ponieważ zna wszystkie elementy w każdej grupie jest równa, to może wystarczy spojrzeć na pierwszy element w każdym, 1, 2 i 1.

1 > 2? Tak! Więc łączy dwie pierwsze grupy.

1> 1? Nie! Pozostaje więc ostatnia grupa.

A teraz porównywano wszystkie elementy równości.

... tylko, nie przekazałeś jej funkcji, której oczekiwałeś.

W skrócie: when it wants an equality test, give it an equality test.

9

Problem polega na tym, że implementacja referencyjna groupBy w raporcie Haskella porównuje elementy z pierwszym elementem, więc grupy nie są ściśle rosnące (po prostu muszą być większe niż pierwszy element). Zamiast tego potrzebna jest wersja groupBy, która testuje na sąsiadujących elementach, takich jak implementacja here.