Skip to content

Commit f5b771e

Browse files
authored
Match nub* functions with Array (#179)
1 parent 015a518 commit f5b771e

File tree

8 files changed

+222
-43
lines changed

8 files changed

+222
-43
lines changed

CHANGELOG.md

+11-9
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,24 @@ Notable changes to this project are documented in this file. The format is based
55
## [Unreleased]
66

77
Breaking changes:
8-
- Rename `scanrLazy` to `scanlLazy` and fix parameter ordering (#161)
9-
- Rename `group'` to `groupAll` (#182)
10-
- Change `Alt ZipList` to satisfy distributivity (#150)
8+
- Converted `nub`/`nubBy` to use ordering, rather than equality (#179)
9+
- Renamed `scanrLazy` to `scanlLazy` and fixed parameter ordering (#161)
10+
- Renamed `group'` to `groupAll` (#182)
11+
- Changed `Alt ZipList` to satisfy distributivity (#150)
1112

1213
New features:
13-
- Add `groupAllBy` (#182, #191)
14+
- Added `nubEq`/`nubByEq` (#179)
15+
- Added `groupAllBy` (#182, #191)
1416
- Added `Eq1` and `Ord1` instances to `NonEmptyList` and `LazyNonEmptyList` (#188)
1517

1618
Bugfixes:
1719

1820
Other improvements:
19-
- Fix Lazy List docs where original list is returned instead of Nothing (#169)
20-
- Migrate to GitHub Actions (#177)
21-
- Change `foldM` type signature to more closely match `foldl` (#165)
22-
- Improve `foldr` performance on large lists (#180)
23-
- Generate changelog and add PR template (#187)
21+
- Fixed Lazy List docs where original list is returned instead of Nothing (#169)
22+
- Migrated to GitHub Actions (#177)
23+
- Changed `foldM` type signature to more closely match `foldl` (#165)
24+
- Improved `foldr` performance on large lists (#180)
25+
- Generated changelog and add PR template (#187)
2426

2527
## [v5.4.1](https://github.com/purescript/purescript-lists/releases/tag/v5.4.1) - 2019-05-06
2628

src/Data/List.purs

+59-13
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ module Data.List
7575

7676
, nub
7777
, nubBy
78+
, nubEq
79+
, nubByEq
7880
, union
7981
, unionBy
8082
, delete
@@ -101,22 +103,20 @@ import Control.Alt ((<|>))
101103
import Control.Alternative (class Alternative)
102104
import Control.Lazy (class Lazy, defer)
103105
import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM, tailRecM2)
104-
105106
import Data.Bifunctor (bimap)
106107
import Data.Foldable (class Foldable, foldr, any, foldl)
108+
import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports
107109
import Data.FunctorWithIndex (mapWithIndex) as FWI
110+
import Data.List.Internal (emptySet, insertAndLookupBy)
108111
import Data.List.Types (List(..), (:))
109112
import Data.List.Types (NonEmptyList(..)) as NEL
110113
import Data.Maybe (Maybe(..))
111114
import Data.Newtype (class Newtype)
112115
import Data.NonEmpty ((:|))
116+
import Data.Traversable (scanl, scanr) as Exports
113117
import Data.Traversable (sequence)
114118
import Data.Tuple (Tuple(..))
115119
import Data.Unfoldable (class Unfoldable, unfoldr)
116-
117-
import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports
118-
import Data.Traversable (scanl, scanr) as Exports
119-
120120
import Prim.TypeError (class Warn, Text)
121121

122122
-- | Convert a list into any unfoldable structure.
@@ -663,18 +663,64 @@ tails list@(Cons _ tl)= list : tails tl
663663
--------------------------------------------------------------------------------
664664

665665
-- | Remove duplicate elements from a list.
666+
-- | Keeps the first occurrence of each element in the input list,
667+
-- | in the same order they appear in the input list.
668+
-- |
669+
-- | ```purescript
670+
-- | nub 1:2:1:3:3:Nil == 1:2:3:Nil
671+
-- | ```
672+
-- |
673+
-- | Running time: `O(n log n)`
674+
nub :: forall a. Ord a => List a -> List a
675+
nub = nubBy compare
676+
677+
-- | Remove duplicate elements from a list based on the provided comparison function.
678+
-- | Keeps the first occurrence of each element in the input list,
679+
-- | in the same order they appear in the input list.
680+
-- |
681+
-- | ```purescript
682+
-- | nubBy (compare `on` Array.length) ([1]:[2]:[3,4]:Nil) == [1]:[3,4]:Nil
683+
-- | ```
684+
-- |
685+
-- | Running time: `O(n log n)`
686+
nubBy :: forall a. (a -> a -> Ordering) -> List a -> List a
687+
nubBy p = reverse <<< go emptySet Nil
688+
where
689+
go _ acc Nil = acc
690+
go s acc (a : as) =
691+
let { found, result: s' } = insertAndLookupBy p a s
692+
in if found
693+
then go s' acc as
694+
else go s' (a : acc) as
695+
696+
-- | Remove duplicate elements from a list.
697+
-- | Keeps the first occurrence of each element in the input list,
698+
-- | in the same order they appear in the input list.
699+
-- | This less efficient version of `nub` only requires an `Eq` instance.
700+
-- |
701+
-- | ```purescript
702+
-- | nubEq 1:2:1:3:3:Nil == 1:2:3:Nil
703+
-- | ```
666704
-- |
667705
-- | Running time: `O(n^2)`
668-
nub :: forall a. Eq a => List a -> List a
669-
nub = nubBy eq
706+
nubEq :: forall a. Eq a => List a -> List a
707+
nubEq = nubByEq eq
670708

671-
-- | Remove duplicate elements from a list, using the specified
672-
-- | function to determine equality of elements.
709+
-- | Remove duplicate elements from a list, using the provided equivalence function.
710+
-- | Keeps the first occurrence of each element in the input list,
711+
-- | in the same order they appear in the input list.
712+
-- | This less efficient version of `nubBy` only requires an equivalence
713+
-- | function, rather than an ordering function.
714+
-- |
715+
-- | ```purescript
716+
-- | mod3eq = eq `on` \n -> mod n 3
717+
-- | nubByEq mod3eq 1:3:4:5:6:Nil == 1:3:5:Nil
718+
-- | ```
673719
-- |
674720
-- | Running time: `O(n^2)`
675-
nubBy :: forall a. (a -> a -> Boolean) -> List a -> List a
676-
nubBy _ Nil = Nil
677-
nubBy eq' (x : xs) = x : nubBy eq' (filter (\y -> not (eq' x y)) xs)
721+
nubByEq :: forall a. (a -> a -> Boolean) -> List a -> List a
722+
nubByEq _ Nil = Nil
723+
nubByEq eq' (x : xs) = x : nubByEq eq' (filter (\y -> not (eq' x y)) xs)
678724

679725
-- | Calculate the union of two lists.
680726
-- |
@@ -687,7 +733,7 @@ union = unionBy (==)
687733
-- |
688734
-- | Running time: `O(n^2)`
689735
unionBy :: forall a. (a -> a -> Boolean) -> List a -> List a -> List a
690-
unionBy eq xs ys = xs <> foldl (flip (deleteBy eq)) (nubBy eq ys) xs
736+
unionBy eq xs ys = xs <> foldl (flip (deleteBy eq)) (nubByEq eq ys) xs
691737

692738
-- | Delete the first occurrence of an element from a list.
693739
-- |

src/Data/List/Internal.purs

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
module Data.List.Internal (Set, emptySet, insertAndLookupBy) where
2+
3+
import Prelude
4+
5+
import Data.List.Types (List(..))
6+
7+
data Set k
8+
= Leaf
9+
| Two (Set k) k (Set k)
10+
| Three (Set k) k (Set k) k (Set k)
11+
12+
emptySet :: forall k. Set k
13+
emptySet = Leaf
14+
15+
data TreeContext k
16+
= TwoLeft k (Set k)
17+
| TwoRight (Set k) k
18+
| ThreeLeft k (Set k) k (Set k)
19+
| ThreeMiddle (Set k) k k (Set k)
20+
| ThreeRight (Set k) k (Set k) k
21+
22+
fromZipper :: forall k. List (TreeContext k) -> Set k -> Set k
23+
fromZipper Nil tree = tree
24+
fromZipper (Cons x ctx) tree =
25+
case x of
26+
TwoLeft k1 right -> fromZipper ctx (Two tree k1 right)
27+
TwoRight left k1 -> fromZipper ctx (Two left k1 tree)
28+
ThreeLeft k1 mid k2 right -> fromZipper ctx (Three tree k1 mid k2 right)
29+
ThreeMiddle left k1 k2 right -> fromZipper ctx (Three left k1 tree k2 right)
30+
ThreeRight left k1 mid k2 -> fromZipper ctx (Three left k1 mid k2 tree)
31+
32+
data KickUp k = KickUp (Set k) k (Set k)
33+
34+
-- | Insert or replace a key/value pair in a map
35+
insertAndLookupBy :: forall k. (k -> k -> Ordering) -> k -> Set k -> { found :: Boolean, result :: Set k }
36+
insertAndLookupBy comp k orig = down Nil orig
37+
where
38+
down :: List (TreeContext k) -> Set k -> { found :: Boolean, result :: Set k }
39+
down ctx Leaf = { found: false, result: up ctx (KickUp Leaf k Leaf) }
40+
down ctx (Two left k1 right) =
41+
case comp k k1 of
42+
EQ -> { found: true, result: orig }
43+
LT -> down (Cons (TwoLeft k1 right) ctx) left
44+
_ -> down (Cons (TwoRight left k1) ctx) right
45+
down ctx (Three left k1 mid k2 right) =
46+
case comp k k1 of
47+
EQ -> { found: true, result: orig }
48+
c1 ->
49+
case c1, comp k k2 of
50+
_ , EQ -> { found: true, result: orig }
51+
LT, _ -> down (Cons (ThreeLeft k1 mid k2 right) ctx) left
52+
GT, LT -> down (Cons (ThreeMiddle left k1 k2 right) ctx) mid
53+
_ , _ -> down (Cons (ThreeRight left k1 mid k2) ctx) right
54+
55+
up :: List (TreeContext k) -> KickUp k -> Set k
56+
up Nil (KickUp left k' right) = Two left k' right
57+
up (Cons x ctx) kup =
58+
case x, kup of
59+
TwoLeft k1 right, KickUp left k' mid -> fromZipper ctx (Three left k' mid k1 right)
60+
TwoRight left k1, KickUp mid k' right -> fromZipper ctx (Three left k1 mid k' right)
61+
ThreeLeft k1 c k2 d, KickUp a k' b -> up ctx (KickUp (Two a k' b) k1 (Two c k2 d))
62+
ThreeMiddle a k1 k2 d, KickUp b k' c -> up ctx (KickUp (Two a k1 b) k' (Two c k2 d))
63+
ThreeRight a k1 b k2, KickUp c k' d -> up ctx (KickUp (Two a k1 b) k2 (Two c k' d))

src/Data/List/Lazy.purs

+33-6
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ module Data.List.Lazy
7272

7373
, nub
7474
, nubBy
75+
, nubEq
76+
, nubByEq
7577
, union
7678
, unionBy
7779
, delete
@@ -103,6 +105,7 @@ import Control.Monad.Rec.Class as Rec
103105
import Data.Foldable (class Foldable, foldr, any, foldl)
104106
import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports
105107
import Data.Lazy (defer)
108+
import Data.List.Internal (emptySet, insertAndLookupBy)
106109
import Data.List.Lazy.Types (List(..), Step(..), step, nil, cons, (:))
107110
import Data.List.Lazy.Types (NonEmptyList(..)) as NEL
108111
import Data.Maybe (Maybe(..), isNothing)
@@ -590,21 +593,45 @@ partition f = foldr go {yes: nil, no: nil}
590593
-- Set-like operations ---------------------------------------------------------
591594
--------------------------------------------------------------------------------
592595

596+
-- | Remove duplicate elements from a list.
597+
-- | Keeps the first occurrence of each element in the input list,
598+
-- | in the same order they appear in the input list.
599+
-- |
600+
-- | Running time: `O(n log n)`
601+
nub :: forall a. Ord a => List a -> List a
602+
nub = nubBy compare
603+
604+
-- | Remove duplicate elements from a list based on the provided comparison function.
605+
-- | Keeps the first occurrence of each element in the input list,
606+
-- | in the same order they appear in the input list.
607+
-- |
608+
-- | Running time: `O(n log n)`
609+
nubBy :: forall a. (a -> a -> Ordering) -> List a -> List a
610+
nubBy p = go emptySet
611+
where
612+
go s (List l) = List (map (goStep s) l)
613+
goStep _ Nil = Nil
614+
goStep s (Cons a as) =
615+
let { found, result: s' } = insertAndLookupBy p a s
616+
in if found
617+
then step (go s' as)
618+
else Cons a (go s' as)
619+
593620
-- | Remove duplicate elements from a list.
594621
-- |
595622
-- | Running time: `O(n^2)`
596-
nub :: forall a. Eq a => List a -> List a
597-
nub = nubBy eq
623+
nubEq :: forall a. Eq a => List a -> List a
624+
nubEq = nubByEq eq
598625

599626
-- | Remove duplicate elements from a list, using the specified
600627
-- | function to determine equality of elements.
601628
-- |
602629
-- | Running time: `O(n^2)`
603-
nubBy :: forall a. (a -> a -> Boolean) -> List a -> List a
604-
nubBy eq = List <<< map go <<< unwrap
630+
nubByEq :: forall a. (a -> a -> Boolean) -> List a -> List a
631+
nubByEq eq = List <<< map go <<< unwrap
605632
where
606633
go Nil = Nil
607-
go (Cons x xs) = Cons x (nubBy eq (filter (\y -> not (eq x y)) xs))
634+
go (Cons x xs) = Cons x (nubByEq eq (filter (\y -> not (eq x y)) xs))
608635

609636
-- | Calculate the union of two lists.
610637
-- |
@@ -617,7 +644,7 @@ union = unionBy (==)
617644
-- |
618645
-- | Running time: `O(n^2)`
619646
unionBy :: forall a. (a -> a -> Boolean) -> List a -> List a -> List a
620-
unionBy eq xs ys = xs <> foldl (flip (deleteBy eq)) (nubBy eq ys) xs
647+
unionBy eq xs ys = xs <> foldl (flip (deleteBy eq)) (nubByEq eq ys) xs
621648

622649
-- | Delete the first occurrence of an element from a list.
623650
-- |

src/Data/List/NonEmpty.purs

+10-2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ module Data.List.NonEmpty
4848
, partition
4949
, nub
5050
, nubBy
51+
, nubEq
52+
, nubByEq
5153
, union
5254
, unionBy
5355
, intersect
@@ -278,12 +280,18 @@ groupAllBy = wrappedOperation "groupAllBy" <<< L.groupAllBy
278280
partition :: forall a. (a -> Boolean) -> NonEmptyList a -> { yes :: L.List a, no :: L.List a }
279281
partition = lift <<< L.partition
280282

281-
nub :: forall a. Eq a => NonEmptyList a -> NonEmptyList a
283+
nub :: forall a. Ord a => NonEmptyList a -> NonEmptyList a
282284
nub = wrappedOperation "nub" L.nub
283285

284-
nubBy :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList a
286+
nubBy :: forall a. (a -> a -> Ordering) -> NonEmptyList a -> NonEmptyList a
285287
nubBy = wrappedOperation "nubBy" <<< L.nubBy
286288

289+
nubEq :: forall a. Eq a => NonEmptyList a -> NonEmptyList a
290+
nubEq = wrappedOperation "nubEq" L.nubEq
291+
292+
nubByEq :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList a
293+
nubByEq = wrappedOperation "nubByEq" <<< L.nubByEq
294+
287295
union :: forall a. Eq a => NonEmptyList a -> NonEmptyList a -> NonEmptyList a
288296
union = wrappedOperation2 "union" L.union
289297

test/Test/Data/List.purs

+14-5
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ module Test.Data.List (testList) where
22

33
import Prelude
44

5+
import Data.Array as Array
56
import Data.Foldable (foldMap, foldl)
67
import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex)
7-
import Data.List (List(..), (..), stripPrefix, Pattern(..), length, range, foldM, unzip, zip, zipWithA, zipWith, intersectBy, intersect, (\\), deleteBy, delete, unionBy, union, nubBy, nub, group, groupAll, groupBy, groupAllBy, partition, span, dropWhile, drop, dropEnd, takeWhile, take, takeEnd, sortBy, sort, catMaybes, mapMaybe, filterM, filter, concat, concatMap, reverse, alterAt, modifyAt, updateAt, deleteAt, insertAt, findLastIndex, findIndex, elemLastIndex, elemIndex, (!!), uncons, unsnoc, init, tail, last, head, insertBy, insert, snoc, null, singleton, fromFoldable, transpose, mapWithIndex, (:))
8+
import Data.Function (on)
9+
import Data.List (List(..), Pattern(..), alterAt, catMaybes, concat, concatMap, delete, deleteAt, deleteBy, drop, dropEnd, dropWhile, elemIndex, elemLastIndex, filter, filterM, findIndex, findLastIndex, foldM, fromFoldable, group, groupAll, groupAllBy, groupBy, head, init, insert, insertAt, insertBy, intersect, intersectBy, last, length, mapMaybe, mapWithIndex, modifyAt, nub, nubBy, nubByEq, nubEq, null, partition, range, reverse, singleton, snoc, sort, sortBy, span, stripPrefix, tail, take, takeEnd, takeWhile, transpose, uncons, union, unionBy, unsnoc, unzip, updateAt, zip, zipWith, zipWithA, (!!), (..), (:), (\\))
810
import Data.List.NonEmpty as NEL
911
import Data.Maybe (Maybe(..), isNothing, fromJust)
1012
import Data.Monoid.Additive (Additive(..))
@@ -37,7 +39,7 @@ testList = do
3739
assert $ (range 0 5) == l [0, 1, 2, 3, 4, 5]
3840
assert $ (range 2 (-3)) == l [2, 1, 0, -1, -2, -3]
3941

40-
log "replicate should produce an list containg an item a specified number of times"
42+
log "replicate should produce an list containing an item a specified number of times"
4143
assert $ replicate 3 true == l [true, true, true]
4244
assert $ replicate 1 "foo" == l ["foo"]
4345
assert $ replicate 0 "foo" == l []
@@ -281,12 +283,19 @@ testList = do
281283
assert $ partitioned.yes == l [5, 3, 4]
282284
assert $ partitioned.no == l [1, 2]
283285

284-
log "nub should remove duplicate elements from the list, keeping the first occurence"
286+
log "nub should remove duplicate elements from the list, keeping the first occurrence"
285287
assert $ nub (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4]
286288

287289
log "nubBy should remove duplicate items from the list using a supplied predicate"
288-
let nubPred = \x y -> if odd x then false else x == y
289-
assert $ nubBy nubPred (l [1, 2, 2, 3, 3, 4, 4, 1]) == l [1, 2, 3, 3, 4, 1]
290+
let nubPred = compare `on` Array.length
291+
assert $ nubBy nubPred (l [[1],[2],[3,4]]) == l [[1],[3,4]]
292+
293+
log "nubEq should remove duplicate elements from the list, keeping the first occurrence"
294+
assert $ nubEq (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4]
295+
296+
log "nubByEq should remove duplicate items from the list using a supplied predicate"
297+
let mod3eq = eq `on` \n -> mod n 3
298+
assert $ nubByEq mod3eq (l [1, 3, 4, 5, 6]) == l [1, 3, 5]
290299

291300
log "union should produce the union of two lists"
292301
assert $ union (l [1, 2, 3]) (l [2, 3, 4]) == l [1, 2, 3, 4]

0 commit comments

Comments
 (0)