From 1998cd5502f28808d70e81e701082cf064ead1bb Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Thu, 17 Dec 2020 18:40:20 -0800 Subject: [PATCH 01/21] Add Eq suffix to original nub/nubBy functions --- src/Data/List.purs | 16 ++++++++-------- src/Data/List/Lazy.purs | 16 ++++++++-------- src/Data/List/NonEmpty.purs | 12 ++++++------ test/Test/Data/List.purs | 10 +++++----- test/Test/Data/List/Lazy.purs | 10 +++++----- test/Test/Data/List/NonEmpty.purs | 8 ++++---- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/Data/List.purs b/src/Data/List.purs index a6cdc36..9d5b7e8 100644 --- a/src/Data/List.purs +++ b/src/Data/List.purs @@ -71,8 +71,8 @@ module Data.List , groupBy , partition - , nub - , nubBy + , nubEq + , nubByEq , union , unionBy , delete @@ -634,16 +634,16 @@ tails list@(Cons _ tl)= list : tails tl -- | Remove duplicate elements from a list. -- | -- | Running time: `O(n^2)` -nub :: forall a. Eq a => List a -> List a -nub = nubBy eq +nubEq :: forall a. Eq a => List a -> List a +nubEq = nubByEq eq -- | Remove duplicate elements from a list, using the specified -- | function to determine equality of elements. -- | -- | Running time: `O(n^2)` -nubBy :: forall a. (a -> a -> Boolean) -> List a -> List a -nubBy _ Nil = Nil -nubBy eq' (x : xs) = x : nubBy eq' (filter (\y -> not (eq' x y)) xs) +nubByEq :: forall a. (a -> a -> Boolean) -> List a -> List a +nubByEq _ Nil = Nil +nubByEq eq' (x : xs) = x : nubByEq eq' (filter (\y -> not (eq' x y)) xs) -- | Calculate the union of two lists. -- | @@ -656,7 +656,7 @@ union = unionBy (==) -- | -- | Running time: `O(n^2)` unionBy :: forall a. (a -> a -> Boolean) -> List a -> List a -> List a -unionBy eq xs ys = xs <> foldl (flip (deleteBy eq)) (nubBy eq ys) xs +unionBy eq xs ys = xs <> foldl (flip (deleteBy eq)) (nubByEq eq ys) xs -- | Delete the first occurrence of an element from a list. -- | diff --git a/src/Data/List/Lazy.purs b/src/Data/List/Lazy.purs index 3aab94b..4ec20c6 100644 --- a/src/Data/List/Lazy.purs +++ b/src/Data/List/Lazy.purs @@ -70,8 +70,8 @@ module Data.List.Lazy , groupBy , partition - , nub - , nubBy + , nubEq + , nubByEq , union , unionBy , delete @@ -593,18 +593,18 @@ partition f = foldr go {yes: nil, no: nil} -- | Remove duplicate elements from a list. -- | -- | Running time: `O(n^2)` -nub :: forall a. Eq a => List a -> List a -nub = nubBy eq +nubEq :: forall a. Eq a => List a -> List a +nubEq = nubByEq eq -- | Remove duplicate elements from a list, using the specified -- | function to determine equality of elements. -- | -- | Running time: `O(n^2)` -nubBy :: forall a. (a -> a -> Boolean) -> List a -> List a -nubBy eq = List <<< map go <<< unwrap +nubByEq :: forall a. (a -> a -> Boolean) -> List a -> List a +nubByEq eq = List <<< map go <<< unwrap where go Nil = Nil - go (Cons x xs) = Cons x (nubBy eq (filter (\y -> not (eq x y)) xs)) + go (Cons x xs) = Cons x (nubByEq eq (filter (\y -> not (eq x y)) xs)) -- | Calculate the union of two lists. -- | @@ -617,7 +617,7 @@ union = unionBy (==) -- | -- | Running time: `O(n^2)` unionBy :: forall a. (a -> a -> Boolean) -> List a -> List a -> List a -unionBy eq xs ys = xs <> foldl (flip (deleteBy eq)) (nubBy eq ys) xs +unionBy eq xs ys = xs <> foldl (flip (deleteBy eq)) (nubByEq eq ys) xs -- | Delete the first occurrence of an element from a list. -- | diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 7fe595c..6750b0d 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -44,8 +44,8 @@ module Data.List.NonEmpty , group' , groupBy , partition - , nub - , nubBy + , nubEq + , nubByEq , union , unionBy , intersect @@ -268,11 +268,11 @@ groupBy = wrappedOperation "groupBy" <<< L.groupBy partition :: forall a. (a -> Boolean) -> NonEmptyList a -> { yes :: L.List a, no :: L.List a } partition = lift <<< L.partition -nub :: forall a. Eq a => NonEmptyList a -> NonEmptyList a -nub = wrappedOperation "nub" L.nub +nubEq :: forall a. Eq a => NonEmptyList a -> NonEmptyList a +nubEq = wrappedOperation "nubEq" L.nubEq -nubBy :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList a -nubBy = wrappedOperation "nubBy" <<< L.nubBy +nubByEq :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList a +nubByEq = wrappedOperation "nubByEq" <<< L.nubByEq union :: forall a. Eq a => NonEmptyList a -> NonEmptyList a -> NonEmptyList a union = wrappedOperation2 "union" L.union diff --git a/test/Test/Data/List.purs b/test/Test/Data/List.purs index 764196d..b680edd 100644 --- a/test/Test/Data/List.purs +++ b/test/Test/Data/List.purs @@ -4,7 +4,7 @@ import Prelude import Data.Foldable (foldMap, foldl) import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex) -import Data.List (List(..), (..), stripPrefix, Pattern(..), length, range, foldM, unzip, zip, zipWithA, zipWith, intersectBy, intersect, (\\), deleteBy, delete, unionBy, union, nubBy, nub, groupBy, group', group, 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, (:)) +import Data.List (List(..), (..), stripPrefix, Pattern(..), length, range, foldM, unzip, zip, zipWithA, zipWith, intersectBy, intersect, (\\), deleteBy, delete, unionBy, union, nubByEq, nubEq, groupBy, group', group, 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, (:)) import Data.List.NonEmpty as NEL import Data.Maybe (Maybe(..), isNothing, fromJust) import Data.Monoid.Additive (Additive(..)) @@ -278,12 +278,12 @@ testList = do assert $ partitioned.yes == l [5, 3, 4] assert $ partitioned.no == l [1, 2] - log "nub should remove duplicate elements from the list, keeping the first occurence" - assert $ nub (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] + log "nubEq should remove duplicate elements from the list, keeping the first occurence" + assert $ nubEq (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] - log "nubBy should remove duplicate items from the list using a supplied predicate" + log "nubByEq should remove duplicate items from the list using a supplied predicate" let nubPred = \x y -> if odd x then false else x == y - assert $ nubBy nubPred (l [1, 2, 2, 3, 3, 4, 4, 1]) == l [1, 2, 3, 3, 4, 1] + assert $ nubByEq nubPred (l [1, 2, 2, 3, 3, 4, 4, 1]) == l [1, 2, 3, 3, 4, 1] log "union should produce the union of two lists" assert $ union (l [1, 2, 3]) (l [2, 3, 4]) == l [1, 2, 3, 4] diff --git a/test/Test/Data/List/Lazy.purs b/test/Test/Data/List/Lazy.purs index 5b4812e..b1c3643 100644 --- a/test/Test/Data/List/Lazy.purs +++ b/test/Test/Data/List/Lazy.purs @@ -6,7 +6,7 @@ import Control.Lazy (defer) import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex) import Data.FunctorWithIndex (mapWithIndex) import Data.Lazy as Z -import Data.List.Lazy (List, Pattern(..), alterAt, catMaybes, concat, concatMap, cons, delete, deleteAt, deleteBy, drop, dropWhile, elemIndex, elemLastIndex, filter, filterM, findIndex, findLastIndex, foldM, foldMap, foldl, foldr, foldrLazy, fromFoldable, group, groupBy, head, init, insert, insertAt, insertBy, intersect, intersectBy, iterate, last, length, mapMaybe, modifyAt, nil, nub, nubBy, null, partition, range, repeat, replicate, replicateM, reverse, scanrLazy, singleton, slice, snoc, span, stripPrefix, tail, take, takeWhile, transpose, uncons, union, unionBy, unzip, updateAt, zip, zipWith, zipWithA, (!!), (..), (:), (\\)) +import Data.List.Lazy (List, Pattern(..), alterAt, catMaybes, concat, concatMap, cons, delete, deleteAt, deleteBy, drop, dropWhile, elemIndex, elemLastIndex, filter, filterM, findIndex, findLastIndex, foldM, foldMap, foldl, foldr, foldrLazy, fromFoldable, group, groupBy, head, init, insert, insertAt, insertBy, intersect, intersectBy, iterate, last, length, mapMaybe, modifyAt, nil, nubEq, nubByEq, null, partition, range, repeat, replicate, replicateM, reverse, scanrLazy, singleton, slice, snoc, span, stripPrefix, tail, take, takeWhile, transpose, uncons, union, unionBy, unzip, updateAt, zip, zipWith, zipWithA, (!!), (..), (:), (\\)) import Data.List.Lazy.NonEmpty as NEL import Data.Maybe (Maybe(..), isNothing, fromJust) import Data.Monoid.Additive (Additive(..)) @@ -328,12 +328,12 @@ testListLazy = do log "iterate on nonempty lazy list should apply supplied function correctly" assert $ (take 3 $ NEL.toList $ NEL.iterate (_ + 1) 0) == l [0, 1, 2] - log "nub should remove duplicate elements from the list, keeping the first occurence" - assert $ nub (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] + log "nubEq should remove duplicate elements from the list, keeping the first occurence" + assert $ nubEq (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] - log "nubBy should remove duplicate items from the list using a supplied predicate" + log "nubByEq should remove duplicate items from the list using a supplied predicate" let nubPred = \x y -> if odd x then false else x == y - assert $ nubBy nubPred (l [1, 2, 2, 3, 3, 4, 4, 1]) == l [1, 2, 3, 3, 4, 1] + assert $ nubByEq nubPred (l [1, 2, 2, 3, 3, 4, 4, 1]) == l [1, 2, 3, 3, 4, 1] log "union should produce the union of two lists" assert $ union (l [1, 2, 3]) (l [2, 3, 4]) == l [1, 2, 3, 4] diff --git a/test/Test/Data/List/NonEmpty.purs b/test/Test/Data/List/NonEmpty.purs index e3cd1ee..4d83931 100644 --- a/test/Test/Data/List/NonEmpty.purs +++ b/test/Test/Data/List/NonEmpty.purs @@ -179,12 +179,12 @@ testNonEmptyList = do assert $ partitioned.yes == l [5, 3, 4] assert $ partitioned.no == l [1, 2] - log "nub should remove duplicate elements from the list, keeping the first occurence" - assert $ NEL.nub (nel 1 [2, 2, 3, 4, 1]) == nel 1 [2, 3, 4] + log "nubEq should remove duplicate elements from the list, keeping the first occurence" + assert $ NEL.nubEq (nel 1 [2, 2, 3, 4, 1]) == nel 1 [2, 3, 4] - log "nubBy should remove duplicate items from the list using a supplied predicate" + log "nubByEq should remove duplicate items from the list using a supplied predicate" let nubPred = \x y -> if odd x then false else x == y - assert $ NEL.nubBy nubPred (nel 1 [2, 2, 3, 3, 4, 4, 1]) == nel 1 [2, 3, 3, 4, 1] + assert $ NEL.nubByEq nubPred (nel 1 [2, 2, 3, 3, 4, 4, 1]) == nel 1 [2, 3, 3, 4, 1] log "union should produce the union of two lists" assert $ NEL.union (nel 1 [2, 3]) (nel 2 [3, 4]) == nel 1 [2, 3, 4] From 381a7e51ed2568dee58f5a88bc5668519dbacd81 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Thu, 17 Dec 2020 20:14:26 -0800 Subject: [PATCH 02/21] Add nub/nubBy functions --- src/Data/List.purs | 121 +++++++++++++++++++++++++++++++++++++-- test/Test/Data/List.purs | 36 +++++++++++- 2 files changed, 149 insertions(+), 8 deletions(-) diff --git a/src/Data/List.purs b/src/Data/List.purs index 9d5b7e8..e083091 100644 --- a/src/Data/List.purs +++ b/src/Data/List.purs @@ -71,6 +71,8 @@ module Data.List , groupBy , partition + , nub + , nubBy , nubEq , nubByEq , union @@ -90,6 +92,10 @@ module Data.List , foldM + , mapReverse + , addIndexReverse + , nubByAdjacentReverse + , module Exports ) where @@ -99,22 +105,21 @@ import Control.Alt ((<|>)) import Control.Alternative (class Alternative) import Control.Lazy (class Lazy, defer) import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM, tailRecM2) - import Data.Bifunctor (bimap) import Data.Foldable (class Foldable, foldr, any, foldl) +import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports +import Data.Function (on) import Data.FunctorWithIndex (mapWithIndex) as FWI import Data.List.Types (List(..), (:)) import Data.List.Types (NonEmptyList(..)) as NEL import Data.Maybe (Maybe(..)) import Data.Newtype (class Newtype) import Data.NonEmpty ((:|)) +import Data.Traversable (scanl, scanr) as Exports import Data.Traversable (sequence) -import Data.Tuple (Tuple(..)) +import Data.Tuple (Tuple(..), fst, snd) import Data.Unfoldable (class Unfoldable, unfoldr) -import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports -import Data.Traversable (scanl, scanr) as Exports - -- | Convert a list into any unfoldable structure. -- | -- | Running time: `O(n)` @@ -632,6 +637,45 @@ tails list@(Cons _ tl)= list : tails tl -------------------------------------------------------------------------------- -- | Remove duplicate elements from a list. +-- | Keeps the first duplicates found and preserves ordering otherwise. +-- | +-- | ```purescript +-- | nub 1:2:1:3:3:Nil = 1:2:3:Nil +-- | ``` +-- | +-- | Running time: `O(n log n)` +nub :: forall a. Ord a => List a -> List a +nub = nubBy compare + +-- | Remove duplicate elements from a list based on the provided ordering function. +-- | Keeps the first duplicates found and preserves ordering otherwise. +-- | +-- | ```purescript +-- | nubBy (compare `on` Array.length) ([1]:[2]:[3,4]:Nil) == [1]:[3,4]:Nil +-- | ``` +-- | +-- | Running time: `O(n log n)` +nubBy :: forall a. (a -> a -> Ordering) -> List a -> List a +nubBy p = + -- Add indices so we can recover original order after deduplicating. + addIndexReverse + -- Sort by original values to cluster duplicates. + >>> sortBy (p `on` snd) + -- Removing neighboring duplicates. + >>> nubByAdjacentReverse (\a b -> (p `on` snd) a b == EQ) + -- Sort by index to recover original order. + -- Use `flip` to sort in reverse order in anticipation of final `mapReverse`. + >>> sortBy (flip compare `on` fst) + -- Discard indicies, just keep original values. + >>> mapReverse snd + +-- | Remove duplicate elements from a list. +-- | Keeps the first duplicates found and preserves ordering otherwise. +-- | This less efficient version of `nub` only requires an `Eq` instance. +-- | +-- | ```purescript +-- | nubEq 1:2:1:3:3:Nil = 1:2:3:Nil +-- | ``` -- | -- | Running time: `O(n^2)` nubEq :: forall a. Eq a => List a -> List a @@ -639,6 +683,14 @@ nubEq = nubByEq eq -- | Remove duplicate elements from a list, using the specified -- | function to determine equality of elements. +-- | Keeps the first duplicates found and preserves ordering otherwise. +-- | This less efficient version of `nubBy` only requires an equality +-- | function, rather than an ordering function. +-- | +-- | ```purescript +-- | mod3eq a b = a `mod` 3 == b `mod` 3 +-- | nubByEq mod3eq 1:3:4:5:6:Nil == 1:3:5:Nil +-- | ``` -- | -- | Running time: `O(n^2)` nubByEq :: forall a. (a -> a -> Boolean) -> List a -> List a @@ -763,3 +815,62 @@ transpose ((x : xs) : xss) = foldM :: forall m a b. Monad m => (b -> a -> m b) -> b -> List a -> m b foldM _ b Nil = pure b foldM f b (a : as) = f b a >>= \b' -> foldM f b' as + +-------------------------------------------------------------------------------- +-- Fast operations which also reverse the list --------------------------------- +-------------------------------------------------------------------------------- + +-- | Maps a function to each element in a list +-- | and reverses the result, but faster than +-- | running each separately. Equivalent to: +-- | +-- | ```purescript +-- | \f l = map f l # reverse +-- | ``` +-- | +-- | Running time: `O(n)` +mapReverse :: forall a b. (a -> b) -> List a -> List b +mapReverse f = go Nil + where + go :: List b -> List a -> List b + go acc Nil = acc + go acc (x : xs) = go (f x : acc) xs + +-- | Converts each element to a Tuple containing its index, +-- | and reverses the result, but faster than running separately. +-- | Equivalent to: +-- | +-- | ```purescript +-- | mapWithIndex Tuple >>> reverse +-- | ``` +-- | +-- | Running time: `O(n)` +addIndexReverse :: forall a. List a -> List (Tuple Int a) +addIndexReverse = go 0 Nil + where + go :: Int -> List (Tuple Int a) -> List a -> List (Tuple Int a) + go i acc Nil = acc + go i acc (x : xs) = go (i + 1) ((Tuple i x) : acc) xs + +-- | Removes neighboring duplicate items from a list +-- | based on an equality predicate. +-- | Keeps the LAST element if duplicates are encountered. +-- | Returned list is reversed (this is to improve performance). +-- | +-- | ```purescript +-- | nubByAdjacentReverse (on eq length) ([1]:[2]:[3,4]:Nil) == [3,4]:[2]:Nil` +-- | ``` +-- | +-- | Running time: `O(n)` +nubByAdjacentReverse :: forall a. (a -> a -> Boolean) -> List a -> List a +nubByAdjacentReverse p = go Nil + where + go :: List a -> List a -> List a + -- empty output + go Nil (x : xs) = go (x : Nil) xs + -- checking for duplicates + go acc@(a : as) (x : xs) + | p a x = go (x : as) xs + | otherwise = go (x : acc) xs + -- empty input + go acc Nil = acc diff --git a/test/Test/Data/List.purs b/test/Test/Data/List.purs index b680edd..66b3968 100644 --- a/test/Test/Data/List.purs +++ b/test/Test/Data/List.purs @@ -2,9 +2,11 @@ module Test.Data.List (testList) where import Prelude +import Data.Array as Array import Data.Foldable (foldMap, foldl) import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex) -import Data.List (List(..), (..), stripPrefix, Pattern(..), length, range, foldM, unzip, zip, zipWithA, zipWith, intersectBy, intersect, (\\), deleteBy, delete, unionBy, union, nubByEq, nubEq, groupBy, group', group, 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, (:)) +import Data.Function (on) +import Data.List (List(..), Pattern(..), addIndexReverse, alterAt, catMaybes, concat, concatMap, delete, deleteAt, deleteBy, drop, dropEnd, dropWhile, elemIndex, elemLastIndex, filter, filterM, findIndex, findLastIndex, foldM, fromFoldable, group, group', groupBy, head, init, insert, insertAt, insertBy, intersect, intersectBy, last, length, mapMaybe, mapReverse, mapWithIndex, modifyAt, nub, nubBy, nubByAdjacentReverse, 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, (!!), (..), (:), (\\)) import Data.List.NonEmpty as NEL import Data.Maybe (Maybe(..), isNothing, fromJust) import Data.Monoid.Additive (Additive(..)) @@ -278,12 +280,19 @@ testList = do assert $ partitioned.yes == l [5, 3, 4] assert $ partitioned.no == l [1, 2] + log "nub should remove duplicate elements from the list, keeping the first occurence" + assert $ nub (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] + + log "nubBy should remove duplicate items from the list using a supplied predicate" + let nubPred = compare `on` Array.length + assert $ nubBy nubPred (l [[1],[2],[3,4]]) == l [[1],[3,4]] + log "nubEq should remove duplicate elements from the list, keeping the first occurence" assert $ nubEq (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] log "nubByEq should remove duplicate items from the list using a supplied predicate" - let nubPred = \x y -> if odd x then false else x == y - assert $ nubByEq nubPred (l [1, 2, 2, 3, 3, 4, 4, 1]) == l [1, 2, 3, 3, 4, 1] + let nubEqPred = \x y -> if odd x then false else x == y + assert $ nubByEq nubEqPred (l [1, 2, 2, 3, 3, 4, 4, 1]) == l [1, 2, 3, 3, 4, 1] log "union should produce the union of two lists" assert $ union (l [1, 2, 3]) (l [2, 3, 4]) == l [1, 2, 3, 4] @@ -398,6 +407,27 @@ testList = do log "append should be stack-safe" void $ pure $ xs <> xs + log "mapReverse should work the same as slower naive version" + --quickCheck mapReverseLaw + + log "naiveAddIndexReverse should work the same as slower naive version" + --quickCheck naiveAddIndexReverse + + log "nubByAdjacentReverse should be correct" + assert $ nubByAdjacentReverse (on eq Array.length) ([1]:[2]:[3,4]:Nil) == [3,4]:[2]:Nil + +naiveMapReverse :: forall a b. (a -> b) -> List a -> List b +naiveMapReverse f l = map f l # reverse + +mapReverseLaw :: (Int -> Int) -> List Int -> Boolean +mapReverseLaw f l = naiveMapReverse f l == mapReverse f l + +naiveAddIndexReverse :: forall a. List a -> List (Tuple Int a) +naiveAddIndexReverse = mapWithIndex Tuple >>> reverse + +addIndexReverseLaw :: List Int -> Boolean +addIndexReverseLaw l = naiveAddIndexReverse l == addIndexReverse l + step :: Int -> Maybe (Tuple Int Int) step 6 = Nothing step n = Just (Tuple n (n + 1)) From 02f2b381700983f3ba00d00f63aaf33559aeb6fc Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Thu, 17 Dec 2020 21:12:53 -0800 Subject: [PATCH 03/21] install quickcheck --- bower.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 1d7ed93..f33c774 100644 --- a/bower.json +++ b/bower.json @@ -37,6 +37,7 @@ "purescript-assert": "master", "purescript-console": "master", "purescript-math": "master", - "purescript-minibench": "master" + "purescript-minibench": "master", + "purescript-quickcheck": "master" } } From 300bd31683aba36c2cc19ea37b327f795755d001 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Fri, 18 Dec 2020 12:21:28 -0800 Subject: [PATCH 04/21] Revert "install quickcheck" This reverts commit 02f2b381700983f3ba00d00f63aaf33559aeb6fc. --- bower.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bower.json b/bower.json index f33c774..1d7ed93 100644 --- a/bower.json +++ b/bower.json @@ -37,7 +37,6 @@ "purescript-assert": "master", "purescript-console": "master", "purescript-math": "master", - "purescript-minibench": "master", - "purescript-quickcheck": "master" + "purescript-minibench": "master" } } From 25d4f490879a1327948a9a01e17e29123a0de03f Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Fri, 18 Dec 2020 12:24:17 -0800 Subject: [PATCH 05/21] review feedback - reduce exports, improve docs --- src/Data/List.purs | 31 +++++++++++++++---------------- test/Test/Data/List.purs | 27 +++------------------------ 2 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/Data/List.purs b/src/Data/List.purs index e083091..bc31b40 100644 --- a/src/Data/List.purs +++ b/src/Data/List.purs @@ -92,10 +92,6 @@ module Data.List , foldM - , mapReverse - , addIndexReverse - , nubByAdjacentReverse - , module Exports ) where @@ -637,18 +633,20 @@ tails list@(Cons _ tl)= list : tails tl -------------------------------------------------------------------------------- -- | Remove duplicate elements from a list. --- | Keeps the first duplicates found and preserves ordering otherwise. +-- | Keeps the first occurrence of each element in the input list, +-- | in the same order they appear in the input list. -- | -- | ```purescript --- | nub 1:2:1:3:3:Nil = 1:2:3:Nil +-- | nub 1:2:1:3:3:Nil == 1:2:3:Nil -- | ``` -- | -- | Running time: `O(n log n)` nub :: forall a. Ord a => List a -> List a nub = nubBy compare --- | Remove duplicate elements from a list based on the provided ordering function. --- | Keeps the first duplicates found and preserves ordering otherwise. +-- | Remove duplicate elements from a list based on the provided comparison function. +-- | Keeps the first occurrence of each element in the input list, +-- | in the same order they appear in the input list. -- | -- | ```purescript -- | nubBy (compare `on` Array.length) ([1]:[2]:[3,4]:Nil) == [1]:[3,4]:Nil @@ -666,29 +664,30 @@ nubBy p = -- Sort by index to recover original order. -- Use `flip` to sort in reverse order in anticipation of final `mapReverse`. >>> sortBy (flip compare `on` fst) - -- Discard indicies, just keep original values. + -- Discard indices, just keep original values. >>> mapReverse snd -- | Remove duplicate elements from a list. --- | Keeps the first duplicates found and preserves ordering otherwise. +-- | Keeps the first occurrence of each element in the input list, +-- | in the same order they appear in the input list. -- | This less efficient version of `nub` only requires an `Eq` instance. -- | -- | ```purescript --- | nubEq 1:2:1:3:3:Nil = 1:2:3:Nil +-- | nubEq 1:2:1:3:3:Nil == 1:2:3:Nil -- | ``` -- | -- | Running time: `O(n^2)` nubEq :: forall a. Eq a => List a -> List a nubEq = nubByEq eq --- | Remove duplicate elements from a list, using the specified --- | function to determine equality of elements. --- | Keeps the first duplicates found and preserves ordering otherwise. --- | This less efficient version of `nubBy` only requires an equality +-- | Remove duplicate elements from a list, using the provided equivalence function. +-- | Keeps the first occurrence of each element in the input list, +-- | in the same order they appear in the input list. +-- | This less efficient version of `nubBy` only requires an equivalence -- | function, rather than an ordering function. -- | -- | ```purescript --- | mod3eq a b = a `mod` 3 == b `mod` 3 +-- | mod3eq = eq `on` \n -> mod n 3 -- | nubByEq mod3eq 1:3:4:5:6:Nil == 1:3:5:Nil -- | ``` -- | diff --git a/test/Test/Data/List.purs b/test/Test/Data/List.purs index 66b3968..4069ce4 100644 --- a/test/Test/Data/List.purs +++ b/test/Test/Data/List.purs @@ -6,7 +6,7 @@ import Data.Array as Array import Data.Foldable (foldMap, foldl) import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex) import Data.Function (on) -import Data.List (List(..), Pattern(..), addIndexReverse, alterAt, catMaybes, concat, concatMap, delete, deleteAt, deleteBy, drop, dropEnd, dropWhile, elemIndex, elemLastIndex, filter, filterM, findIndex, findLastIndex, foldM, fromFoldable, group, group', groupBy, head, init, insert, insertAt, insertBy, intersect, intersectBy, last, length, mapMaybe, mapReverse, mapWithIndex, modifyAt, nub, nubBy, nubByAdjacentReverse, 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, (!!), (..), (:), (\\)) +import Data.List (List(..), Pattern(..), alterAt, catMaybes, concat, concatMap, delete, deleteAt, deleteBy, drop, dropEnd, dropWhile, elemIndex, elemLastIndex, filter, filterM, findIndex, findLastIndex, foldM, fromFoldable, group, group', 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, (!!), (..), (:), (\\)) import Data.List.NonEmpty as NEL import Data.Maybe (Maybe(..), isNothing, fromJust) import Data.Monoid.Additive (Additive(..)) @@ -291,8 +291,8 @@ testList = do assert $ nubEq (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] log "nubByEq should remove duplicate items from the list using a supplied predicate" - let nubEqPred = \x y -> if odd x then false else x == y - assert $ nubByEq nubEqPred (l [1, 2, 2, 3, 3, 4, 4, 1]) == l [1, 2, 3, 3, 4, 1] + let mod3eq = eq `on` \n -> mod n 3 + assert $ nubByEq mod3eq (l [1, 3, 4, 5, 6]) == l [1, 3, 5] log "union should produce the union of two lists" assert $ union (l [1, 2, 3]) (l [2, 3, 4]) == l [1, 2, 3, 4] @@ -407,27 +407,6 @@ testList = do log "append should be stack-safe" void $ pure $ xs <> xs - log "mapReverse should work the same as slower naive version" - --quickCheck mapReverseLaw - - log "naiveAddIndexReverse should work the same as slower naive version" - --quickCheck naiveAddIndexReverse - - log "nubByAdjacentReverse should be correct" - assert $ nubByAdjacentReverse (on eq Array.length) ([1]:[2]:[3,4]:Nil) == [3,4]:[2]:Nil - -naiveMapReverse :: forall a b. (a -> b) -> List a -> List b -naiveMapReverse f l = map f l # reverse - -mapReverseLaw :: (Int -> Int) -> List Int -> Boolean -mapReverseLaw f l = naiveMapReverse f l == mapReverse f l - -naiveAddIndexReverse :: forall a. List a -> List (Tuple Int a) -naiveAddIndexReverse = mapWithIndex Tuple >>> reverse - -addIndexReverseLaw :: List Int -> Boolean -addIndexReverseLaw l = naiveAddIndexReverse l == addIndexReverse l - step :: Int -> Maybe (Tuple Int Int) step 6 = Nothing step n = Just (Tuple n (n + 1)) From 0871480cfb8a77a2eda2eebb3cab20e478661f8f Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Wed, 6 Jan 2021 15:16:52 -0800 Subject: [PATCH 06/21] Use <<< instead of >>> --- src/Data/List.purs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Data/List.purs b/src/Data/List.purs index f0c9d04..d1c693c 100644 --- a/src/Data/List.purs +++ b/src/Data/List.purs @@ -686,17 +686,17 @@ nub = nubBy compare -- | Running time: `O(n log n)` nubBy :: forall a. (a -> a -> Ordering) -> List a -> List a nubBy p = - -- Add indices so we can recover original order after deduplicating. - addIndexReverse - -- Sort by original values to cluster duplicates. - >>> sortBy (p `on` snd) - -- Removing neighboring duplicates. - >>> nubByAdjacentReverse (\a b -> (p `on` snd) a b == EQ) + -- Discard indices, just keep original values. + mapReverse snd -- Sort by index to recover original order. -- Use `flip` to sort in reverse order in anticipation of final `mapReverse`. - >>> sortBy (flip compare `on` fst) - -- Discard indices, just keep original values. - >>> mapReverse snd + <<< sortBy (flip compare `on` fst) + -- Removing neighboring duplicates. + <<< nubByAdjacentReverse (\a b -> (p `on` snd) a b == EQ) + -- Sort by original values to cluster duplicates. + <<< sortBy (p `on` snd) + -- Add indices so we can recover original order after deduplicating. + <<< addIndexReverse -- | Remove duplicate elements from a list. -- | Keeps the first occurrence of each element in the input list, @@ -871,7 +871,7 @@ mapReverse f = go Nil -- | Equivalent to: -- | -- | ```purescript --- | mapWithIndex Tuple >>> reverse +-- | reverse <<< mapWithIndex Tuple -- | ``` -- | -- | Running time: `O(n)` From db10d08aafe6f0478a52f147ba1e422f9cd91515 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Wed, 6 Jan 2021 15:44:15 -0800 Subject: [PATCH 07/21] Add nub/nubBy for NonEmptyList --- src/Data/List/NonEmpty.purs | 8 ++++++++ test/Test/Data/List/NonEmpty.purs | 13 +++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 653090d..49eace0 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -46,6 +46,8 @@ module Data.List.NonEmpty , groupBy , groupAllBy , partition + , nub + , nubBy , nubEq , nubByEq , union @@ -278,6 +280,12 @@ groupAllBy = wrappedOperation "groupAllBy" <<< L.groupAllBy partition :: forall a. (a -> Boolean) -> NonEmptyList a -> { yes :: L.List a, no :: L.List a } partition = lift <<< L.partition +nub :: forall a. Ord a => NonEmptyList a -> NonEmptyList a +nub = wrappedOperation "nub" L.nub + +nubBy :: forall a. (a -> a -> Ordering) -> NonEmptyList a -> NonEmptyList a +nubBy = wrappedOperation "nubBy" <<< L.nubBy + nubEq :: forall a. Eq a => NonEmptyList a -> NonEmptyList a nubEq = wrappedOperation "nubEq" L.nubEq diff --git a/test/Test/Data/List/NonEmpty.purs b/test/Test/Data/List/NonEmpty.purs index 2b6cca6..f6e4753 100644 --- a/test/Test/Data/List/NonEmpty.purs +++ b/test/Test/Data/List/NonEmpty.purs @@ -2,8 +2,10 @@ module Test.Data.List.NonEmpty (testNonEmptyList) where import Prelude +import Data.Array as Array import Data.Foldable (class Foldable, foldM, foldMap, foldl, length) import Data.FoldableWithIndex (foldlWithIndex, foldrWithIndex, foldMapWithIndex) +import Data.Function (on) import Data.List as L import Data.List.NonEmpty as NEL import Data.Maybe (Maybe(..)) @@ -182,12 +184,19 @@ testNonEmptyList = do assert $ partitioned.yes == l [5, 3, 4] assert $ partitioned.no == l [1, 2] + log "nub should remove duplicate elements from the list, keeping the first occurence" + assert $ NEL.nub (nel 1 [2, 2, 3, 4, 1]) == nel 1 [2, 3, 4] + + log "nubBy should remove duplicate items from the list using a supplied predicate" + let nubPred = compare `on` Array.length + assert $ NEL.nubBy nubPred (nel [1] [[2],[3,4]]) == nel [1] [[3,4]] + log "nubEq should remove duplicate elements from the list, keeping the first occurence" assert $ NEL.nubEq (nel 1 [2, 2, 3, 4, 1]) == nel 1 [2, 3, 4] log "nubByEq should remove duplicate items from the list using a supplied predicate" - let nubPred = \x y -> if odd x then false else x == y - assert $ NEL.nubByEq nubPred (nel 1 [2, 2, 3, 3, 4, 4, 1]) == nel 1 [2, 3, 3, 4, 1] + let mod3eq = eq `on` \n -> mod n 3 + assert $ NEL.nubByEq mod3eq (nel 1 [3, 4, 5, 6]) == nel 1 [3, 5] log "union should produce the union of two lists" assert $ NEL.union (nel 1 [2, 3]) (nel 2 [3, 4]) == nel 1 [2, 3, 4] From 6a84b72338780e0aab7ddf0400a2233e339ca778 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Wed, 6 Jan 2021 15:47:58 -0800 Subject: [PATCH 08/21] Fix CI by updating to rc5 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55efa3d..f4f44e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: - uses: purescript-contrib/setup-purescript@main with: - purescript: "0.14.0-rc3" + purescript: "0.14.0-rc5" - uses: actions/setup-node@v1 with: From ef26a93286ab00bc7a3984faee886e2c4d29b422 Mon Sep 17 00:00:00 2001 From: milesfrain Date: Mon, 11 Jan 2021 12:15:57 -0800 Subject: [PATCH 09/21] Apply indentation feedback Co-authored-by: Thomas Honeyman --- src/Data/List.purs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Data/List.purs b/src/Data/List.purs index d1c693c..b5bcc79 100644 --- a/src/Data/List.purs +++ b/src/Data/List.purs @@ -688,15 +688,15 @@ nubBy :: forall a. (a -> a -> Ordering) -> List a -> List a nubBy p = -- Discard indices, just keep original values. mapReverse snd - -- Sort by index to recover original order. - -- Use `flip` to sort in reverse order in anticipation of final `mapReverse`. - <<< sortBy (flip compare `on` fst) - -- Removing neighboring duplicates. - <<< nubByAdjacentReverse (\a b -> (p `on` snd) a b == EQ) - -- Sort by original values to cluster duplicates. - <<< sortBy (p `on` snd) - -- Add indices so we can recover original order after deduplicating. - <<< addIndexReverse + -- Sort by index to recover original order. + -- Use `flip` to sort in reverse order in anticipation of final `mapReverse`. + <<< sortBy (flip compare `on` fst) + -- Removing neighboring duplicates. + <<< nubByAdjacentReverse (\a b -> (p `on` snd) a b == EQ) + -- Sort by original values to cluster duplicates. + <<< sortBy (p `on` snd) + -- Add indices so we can recover original order after deduplicating. + <<< addIndexReverse -- | Remove duplicate elements from a list. -- | Keeps the first occurrence of each element in the input list, @@ -895,12 +895,12 @@ addIndexReverse = go 0 Nil nubByAdjacentReverse :: forall a. (a -> a -> Boolean) -> List a -> List a nubByAdjacentReverse p = go Nil where - go :: List a -> List a -> List a - -- empty output - go Nil (x : xs) = go (x : Nil) xs - -- checking for duplicates - go acc@(a : as) (x : xs) - | p a x = go (x : as) xs - | otherwise = go (x : acc) xs - -- empty input - go acc Nil = acc + go :: List a -> List a -> List a + -- empty output + go Nil (x : xs) = go (x : Nil) xs + -- checking for duplicates + go acc@(a : as) (x : xs) + | p a x = go (x : as) xs + | otherwise = go (x : acc) xs + -- empty input + go acc Nil = acc From 8a6cc411c5f2c6f388bab8724fe442a8f2bbfe0f Mon Sep 17 00:00:00 2001 From: Nicholas Scheel Date: Tue, 12 Jan 2021 14:00:53 -0600 Subject: [PATCH 10/21] Copy Map from Data.Map.Internal --- src/Data/List/Internal.purs | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/Data/List/Internal.purs diff --git a/src/Data/List/Internal.purs b/src/Data/List/Internal.purs new file mode 100644 index 0000000..ddfcb33 --- /dev/null +++ b/src/Data/List/Internal.purs @@ -0,0 +1,63 @@ +module Data.List.Internal where + +import Prelude + +import Data.List.Types (List(..)) + +data Map k v + = Leaf + | Two (Map k v) k v (Map k v) + | Three (Map k v) k v (Map k v) k v (Map k v) + +data TreeContext k v + = TwoLeft k v (Map k v) + | TwoRight (Map k v) k v + | ThreeLeft k v (Map k v) k v (Map k v) + | ThreeMiddle (Map k v) k v k v (Map k v) + | ThreeRight (Map k v) k v (Map k v) k v + +fromZipper :: forall k v. Ord k => List (TreeContext k v) -> Map k v -> Map k v +fromZipper Nil tree = tree +fromZipper (Cons x ctx) tree = + case x of + TwoLeft k1 v1 right -> fromZipper ctx (Two tree k1 v1 right) + TwoRight left k1 v1 -> fromZipper ctx (Two left k1 v1 tree) + ThreeLeft k1 v1 mid k2 v2 right -> fromZipper ctx (Three tree k1 v1 mid k2 v2 right) + ThreeMiddle left k1 v1 k2 v2 right -> fromZipper ctx (Three left k1 v1 tree k2 v2 right) + ThreeRight left k1 v1 mid k2 v2 -> fromZipper ctx (Three left k1 v1 mid k2 v2 tree) + +data KickUp k v = KickUp (Map k v) k v (Map k v) + +-- | Insert or replace a key/value pair in a map +insert :: forall k v. Ord k => k -> v -> Map k v -> Map k v +insert k v = down Nil + where + comp :: k -> k -> Ordering + comp = compare + + down :: List (TreeContext k v) -> Map k v -> Map k v + down ctx Leaf = up ctx (KickUp Leaf k v Leaf) + down ctx (Two left k1 v1 right) = + case comp k k1 of + EQ -> fromZipper ctx (Two left k v right) + LT -> down (Cons (TwoLeft k1 v1 right) ctx) left + _ -> down (Cons (TwoRight left k1 v1) ctx) right + down ctx (Three left k1 v1 mid k2 v2 right) = + case comp k k1 of + EQ -> fromZipper ctx (Three left k v mid k2 v2 right) + c1 -> + case c1, comp k k2 of + _ , EQ -> fromZipper ctx (Three left k1 v1 mid k v right) + LT, _ -> down (Cons (ThreeLeft k1 v1 mid k2 v2 right) ctx) left + GT, LT -> down (Cons (ThreeMiddle left k1 v1 k2 v2 right) ctx) mid + _ , _ -> down (Cons (ThreeRight left k1 v1 mid k2 v2) ctx) right + + up :: List (TreeContext k v) -> KickUp k v -> Map k v + up Nil (KickUp left k' v' right) = Two left k' v' right + up (Cons x ctx) kup = + case x, kup of + TwoLeft k1 v1 right, KickUp left k' v' mid -> fromZipper ctx (Three left k' v' mid k1 v1 right) + TwoRight left k1 v1, KickUp mid k' v' right -> fromZipper ctx (Three left k1 v1 mid k' v' right) + ThreeLeft k1 v1 c k2 v2 d, KickUp a k' v' b -> up ctx (KickUp (Two a k' v' b) k1 v1 (Two c k2 v2 d)) + ThreeMiddle a k1 v1 k2 v2 d, KickUp b k' v' c -> up ctx (KickUp (Two a k1 v1 b) k' v' (Two c k2 v2 d)) + ThreeRight a k1 v1 b k2 v2, KickUp c k' v' d -> up ctx (KickUp (Two a k1 v1 b) k2 v2 (Two c k' v' d)) From 178f1a29c9b8499d0a3e4ec6e2895eaa4bfe007d Mon Sep 17 00:00:00 2001 From: Nicholas Scheel Date: Tue, 12 Jan 2021 14:03:36 -0600 Subject: [PATCH 11/21] Specialize to Set --- src/Data/List/Internal.purs | 74 ++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Data/List/Internal.purs b/src/Data/List/Internal.purs index ddfcb33..786c849 100644 --- a/src/Data/List/Internal.purs +++ b/src/Data/List/Internal.purs @@ -4,60 +4,60 @@ import Prelude import Data.List.Types (List(..)) -data Map k v +data Set k = Leaf - | Two (Map k v) k v (Map k v) - | Three (Map k v) k v (Map k v) k v (Map k v) + | Two (Set k) k (Set k) + | Three (Set k) k (Set k) k (Set k) -data TreeContext k v - = TwoLeft k v (Map k v) - | TwoRight (Map k v) k v - | ThreeLeft k v (Map k v) k v (Map k v) - | ThreeMiddle (Map k v) k v k v (Map k v) - | ThreeRight (Map k v) k v (Map k v) k v +data TreeContext k + = TwoLeft k (Set k) + | TwoRight (Set k) k + | ThreeLeft k (Set k) k (Set k) + | ThreeMiddle (Set k) k k (Set k) + | ThreeRight (Set k) k (Set k) k -fromZipper :: forall k v. Ord k => List (TreeContext k v) -> Map k v -> Map k v +fromZipper :: forall k. Ord k => List (TreeContext k) -> Set k -> Set k fromZipper Nil tree = tree fromZipper (Cons x ctx) tree = case x of - TwoLeft k1 v1 right -> fromZipper ctx (Two tree k1 v1 right) - TwoRight left k1 v1 -> fromZipper ctx (Two left k1 v1 tree) - ThreeLeft k1 v1 mid k2 v2 right -> fromZipper ctx (Three tree k1 v1 mid k2 v2 right) - ThreeMiddle left k1 v1 k2 v2 right -> fromZipper ctx (Three left k1 v1 tree k2 v2 right) - ThreeRight left k1 v1 mid k2 v2 -> fromZipper ctx (Three left k1 v1 mid k2 v2 tree) + TwoLeft k1 right -> fromZipper ctx (Two tree k1 right) + TwoRight left k1 -> fromZipper ctx (Two left k1 tree) + ThreeLeft k1 mid k2 right -> fromZipper ctx (Three tree k1 mid k2 right) + ThreeMiddle left k1 k2 right -> fromZipper ctx (Three left k1 tree k2 right) + ThreeRight left k1 mid k2 -> fromZipper ctx (Three left k1 mid k2 tree) -data KickUp k v = KickUp (Map k v) k v (Map k v) +data KickUp k = KickUp (Set k) k (Set k) -- | Insert or replace a key/value pair in a map -insert :: forall k v. Ord k => k -> v -> Map k v -> Map k v -insert k v = down Nil +insert :: forall k. Ord k => k -> Set k -> Set k +insert k = down Nil where comp :: k -> k -> Ordering comp = compare - down :: List (TreeContext k v) -> Map k v -> Map k v - down ctx Leaf = up ctx (KickUp Leaf k v Leaf) - down ctx (Two left k1 v1 right) = + down :: List (TreeContext k) -> Set k -> Set k + down ctx Leaf = up ctx (KickUp Leaf k Leaf) + down ctx (Two left k1 right) = case comp k k1 of - EQ -> fromZipper ctx (Two left k v right) - LT -> down (Cons (TwoLeft k1 v1 right) ctx) left - _ -> down (Cons (TwoRight left k1 v1) ctx) right - down ctx (Three left k1 v1 mid k2 v2 right) = + EQ -> fromZipper ctx (Two left k right) + LT -> down (Cons (TwoLeft k1 right) ctx) left + _ -> down (Cons (TwoRight left k1) ctx) right + down ctx (Three left k1 mid k2 right) = case comp k k1 of - EQ -> fromZipper ctx (Three left k v mid k2 v2 right) + EQ -> fromZipper ctx (Three left k mid k2 right) c1 -> case c1, comp k k2 of - _ , EQ -> fromZipper ctx (Three left k1 v1 mid k v right) - LT, _ -> down (Cons (ThreeLeft k1 v1 mid k2 v2 right) ctx) left - GT, LT -> down (Cons (ThreeMiddle left k1 v1 k2 v2 right) ctx) mid - _ , _ -> down (Cons (ThreeRight left k1 v1 mid k2 v2) ctx) right + _ , EQ -> fromZipper ctx (Three left k1 mid k right) + LT, _ -> down (Cons (ThreeLeft k1 mid k2 right) ctx) left + GT, LT -> down (Cons (ThreeMiddle left k1 k2 right) ctx) mid + _ , _ -> down (Cons (ThreeRight left k1 mid k2) ctx) right - up :: List (TreeContext k v) -> KickUp k v -> Map k v - up Nil (KickUp left k' v' right) = Two left k' v' right + up :: List (TreeContext k) -> KickUp k -> Set k + up Nil (KickUp left k' right) = Two left k' right up (Cons x ctx) kup = case x, kup of - TwoLeft k1 v1 right, KickUp left k' v' mid -> fromZipper ctx (Three left k' v' mid k1 v1 right) - TwoRight left k1 v1, KickUp mid k' v' right -> fromZipper ctx (Three left k1 v1 mid k' v' right) - ThreeLeft k1 v1 c k2 v2 d, KickUp a k' v' b -> up ctx (KickUp (Two a k' v' b) k1 v1 (Two c k2 v2 d)) - ThreeMiddle a k1 v1 k2 v2 d, KickUp b k' v' c -> up ctx (KickUp (Two a k1 v1 b) k' v' (Two c k2 v2 d)) - ThreeRight a k1 v1 b k2 v2, KickUp c k' v' d -> up ctx (KickUp (Two a k1 v1 b) k2 v2 (Two c k' v' d)) + TwoLeft k1 right, KickUp left k' mid -> fromZipper ctx (Three left k' mid k1 right) + TwoRight left k1, KickUp mid k' right -> fromZipper ctx (Three left k1 mid k' right) + ThreeLeft k1 c k2 d, KickUp a k' b -> up ctx (KickUp (Two a k' b) k1 (Two c k2 d)) + ThreeMiddle a k1 k2 d, KickUp b k' c -> up ctx (KickUp (Two a k1 b) k' (Two c k2 d)) + ThreeRight a k1 b k2, KickUp c k' d -> up ctx (KickUp (Two a k1 b) k2 (Two c k' d)) From 7a39c9c497fda12220c46566b52a0f0f0ad40f99 Mon Sep 17 00:00:00 2001 From: Nicholas Scheel Date: Tue, 12 Jan 2021 14:06:48 -0600 Subject: [PATCH 12/21] Change to insertAndLookup function --- src/Data/List/Internal.purs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Data/List/Internal.purs b/src/Data/List/Internal.purs index 786c849..4dfc33d 100644 --- a/src/Data/List/Internal.purs +++ b/src/Data/List/Internal.purs @@ -1,4 +1,4 @@ -module Data.List.Internal where +module Data.List.Internal (Set, insertAndLookup) where import Prelude @@ -29,25 +29,25 @@ fromZipper (Cons x ctx) tree = data KickUp k = KickUp (Set k) k (Set k) -- | Insert or replace a key/value pair in a map -insert :: forall k. Ord k => k -> Set k -> Set k -insert k = down Nil +insertAndLookup :: forall k. Ord k => k -> Set k -> { found :: Boolean, result :: Set k } +insertAndLookup k orig = down Nil orig where comp :: k -> k -> Ordering comp = compare - down :: List (TreeContext k) -> Set k -> Set k - down ctx Leaf = up ctx (KickUp Leaf k Leaf) + down :: List (TreeContext k) -> Set k -> { found :: Boolean, result :: Set k } + down ctx Leaf = { found: false, result: up ctx (KickUp Leaf k Leaf) } down ctx (Two left k1 right) = case comp k k1 of - EQ -> fromZipper ctx (Two left k right) + EQ -> { found: true, result: orig } LT -> down (Cons (TwoLeft k1 right) ctx) left _ -> down (Cons (TwoRight left k1) ctx) right down ctx (Three left k1 mid k2 right) = case comp k k1 of - EQ -> fromZipper ctx (Three left k mid k2 right) + EQ -> { found: true, result: orig } c1 -> case c1, comp k k2 of - _ , EQ -> fromZipper ctx (Three left k1 mid k right) + _ , EQ -> { found: true, result: orig } LT, _ -> down (Cons (ThreeLeft k1 mid k2 right) ctx) left GT, LT -> down (Cons (ThreeMiddle left k1 k2 right) ctx) mid _ , _ -> down (Cons (ThreeRight left k1 mid k2) ctx) right From 7be77fdf5674ffa92302f3f53de32de61cd625dd Mon Sep 17 00:00:00 2001 From: Nicholas Scheel Date: Tue, 12 Jan 2021 14:26:53 -0600 Subject: [PATCH 13/21] Implement nub using Set --- src/Data/List.purs | 22 +++++++++------------- src/Data/List/Internal.purs | 14 +++++++------- src/Data/List/Lazy.purs | 25 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/Data/List.purs b/src/Data/List.purs index b5bcc79..fde1e62 100644 --- a/src/Data/List.purs +++ b/src/Data/List.purs @@ -108,6 +108,7 @@ import Data.Foldable (class Foldable, foldr, any, foldl) import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports import Data.Function (on) import Data.FunctorWithIndex (mapWithIndex) as FWI +import Data.List.Internal (emptySet, insertAndLookupBy) import Data.List.Types (List(..), (:)) import Data.List.Types (NonEmptyList(..)) as NEL import Data.Maybe (Maybe(..)) @@ -117,7 +118,6 @@ import Data.Traversable (scanl, scanr) as Exports import Data.Traversable (sequence) import Data.Tuple (Tuple(..), fst, snd) import Data.Unfoldable (class Unfoldable, unfoldr) - import Prim.TypeError (class Warn, Text) -- | Convert a list into any unfoldable structure. @@ -685,18 +685,14 @@ nub = nubBy compare -- | -- | Running time: `O(n log n)` nubBy :: forall a. (a -> a -> Ordering) -> List a -> List a -nubBy p = - -- Discard indices, just keep original values. - mapReverse snd - -- Sort by index to recover original order. - -- Use `flip` to sort in reverse order in anticipation of final `mapReverse`. - <<< sortBy (flip compare `on` fst) - -- Removing neighboring duplicates. - <<< nubByAdjacentReverse (\a b -> (p `on` snd) a b == EQ) - -- Sort by original values to cluster duplicates. - <<< sortBy (p `on` snd) - -- Add indices so we can recover original order after deduplicating. - <<< addIndexReverse +nubBy p = go emptySet + where + go _ Nil = Nil + go s (a : as) = + let { found, result: s' } = insertAndLookupBy p a s + in if found + then go s' as + else a : go s' as -- | Remove duplicate elements from a list. -- | Keeps the first occurrence of each element in the input list, diff --git a/src/Data/List/Internal.purs b/src/Data/List/Internal.purs index 4dfc33d..5f5c950 100644 --- a/src/Data/List/Internal.purs +++ b/src/Data/List/Internal.purs @@ -1,4 +1,4 @@ -module Data.List.Internal (Set, insertAndLookup) where +module Data.List.Internal (Set, emptySet, insertAndLookupBy) where import Prelude @@ -9,6 +9,9 @@ data Set k | Two (Set k) k (Set k) | Three (Set k) k (Set k) k (Set k) +emptySet :: forall k. Set k +emptySet = Leaf + data TreeContext k = TwoLeft k (Set k) | TwoRight (Set k) k @@ -16,7 +19,7 @@ data TreeContext k | ThreeMiddle (Set k) k k (Set k) | ThreeRight (Set k) k (Set k) k -fromZipper :: forall k. Ord k => List (TreeContext k) -> Set k -> Set k +fromZipper :: forall k. List (TreeContext k) -> Set k -> Set k fromZipper Nil tree = tree fromZipper (Cons x ctx) tree = case x of @@ -29,12 +32,9 @@ fromZipper (Cons x ctx) tree = data KickUp k = KickUp (Set k) k (Set k) -- | Insert or replace a key/value pair in a map -insertAndLookup :: forall k. Ord k => k -> Set k -> { found :: Boolean, result :: Set k } -insertAndLookup k orig = down Nil orig +insertAndLookupBy :: forall k. (k -> k -> Ordering) -> k -> Set k -> { found :: Boolean, result :: Set k } +insertAndLookupBy comp k orig = down Nil orig where - comp :: k -> k -> Ordering - comp = compare - down :: List (TreeContext k) -> Set k -> { found :: Boolean, result :: Set k } down ctx Leaf = { found: false, result: up ctx (KickUp Leaf k Leaf) } down ctx (Two left k1 right) = diff --git a/src/Data/List/Lazy.purs b/src/Data/List/Lazy.purs index 9e28a4d..3b6ff84 100644 --- a/src/Data/List/Lazy.purs +++ b/src/Data/List/Lazy.purs @@ -103,6 +103,7 @@ import Control.Monad.Rec.Class as Rec import Data.Foldable (class Foldable, foldr, any, foldl) import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports import Data.Lazy (defer) +import Data.List.Internal (emptySet, insertAndLookupBy) import Data.List.Lazy.Types (List(..), Step(..), step, nil, cons, (:)) import Data.List.Lazy.Types (NonEmptyList(..)) as NEL import Data.Maybe (Maybe(..), isNothing) @@ -590,6 +591,30 @@ partition f = foldr go {yes: nil, no: nil} -- Set-like operations --------------------------------------------------------- -------------------------------------------------------------------------------- +-- | Remove duplicate elements from a list. +-- | Keeps the first occurrence of each element in the input list, +-- | in the same order they appear in the input list. +-- | +-- | Running time: `O(n log n)` +nub :: forall a. Ord a => List a -> List a +nub = nubBy compare + +-- | Remove duplicate elements from a list based on the provided comparison function. +-- | Keeps the first occurrence of each element in the input list, +-- | in the same order they appear in the input list. +-- | +-- | Running time: `O(n log n)` +nubBy :: forall a. (a -> a -> Ordering) -> List a -> List a +nubBy p = go emptySet + where + go s (List l) = List (map (goStep s) l) + goStep _ Nil = Nil + goStep s (Cons a as) = + let { found, result: s' } = insertAndLookupBy p a s + in if found + then step (go s' as) + else Cons a (go s' as) + -- | Remove duplicate elements from a list. -- | -- | Running time: `O(n^2)` From 12732828140e663a17c85e28c762ef24c71a378d Mon Sep 17 00:00:00 2001 From: Nicholas Scheel Date: Tue, 12 Jan 2021 14:43:06 -0600 Subject: [PATCH 14/21] Remove unused functions --- src/Data/List.purs | 62 +--------------------------------------------- 1 file changed, 1 insertion(+), 61 deletions(-) diff --git a/src/Data/List.purs b/src/Data/List.purs index fde1e62..edd6a61 100644 --- a/src/Data/List.purs +++ b/src/Data/List.purs @@ -106,7 +106,6 @@ import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM, tailRecM2) import Data.Bifunctor (bimap) import Data.Foldable (class Foldable, foldr, any, foldl) import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports -import Data.Function (on) import Data.FunctorWithIndex (mapWithIndex) as FWI import Data.List.Internal (emptySet, insertAndLookupBy) import Data.List.Types (List(..), (:)) @@ -116,7 +115,7 @@ import Data.Newtype (class Newtype) import Data.NonEmpty ((:|)) import Data.Traversable (scanl, scanr) as Exports import Data.Traversable (sequence) -import Data.Tuple (Tuple(..), fst, snd) +import Data.Tuple (Tuple(..)) import Data.Unfoldable (class Unfoldable, unfoldr) import Prim.TypeError (class Warn, Text) @@ -841,62 +840,3 @@ transpose ((x : xs) : xss) = foldM :: forall m a b. Monad m => (b -> a -> m b) -> b -> List a -> m b foldM _ b Nil = pure b foldM f b (a : as) = f b a >>= \b' -> foldM f b' as - --------------------------------------------------------------------------------- --- Fast operations which also reverse the list --------------------------------- --------------------------------------------------------------------------------- - --- | Maps a function to each element in a list --- | and reverses the result, but faster than --- | running each separately. Equivalent to: --- | --- | ```purescript --- | \f l = map f l # reverse --- | ``` --- | --- | Running time: `O(n)` -mapReverse :: forall a b. (a -> b) -> List a -> List b -mapReverse f = go Nil - where - go :: List b -> List a -> List b - go acc Nil = acc - go acc (x : xs) = go (f x : acc) xs - --- | Converts each element to a Tuple containing its index, --- | and reverses the result, but faster than running separately. --- | Equivalent to: --- | --- | ```purescript --- | reverse <<< mapWithIndex Tuple --- | ``` --- | --- | Running time: `O(n)` -addIndexReverse :: forall a. List a -> List (Tuple Int a) -addIndexReverse = go 0 Nil - where - go :: Int -> List (Tuple Int a) -> List a -> List (Tuple Int a) - go i acc Nil = acc - go i acc (x : xs) = go (i + 1) ((Tuple i x) : acc) xs - --- | Removes neighboring duplicate items from a list --- | based on an equality predicate. --- | Keeps the LAST element if duplicates are encountered. --- | Returned list is reversed (this is to improve performance). --- | --- | ```purescript --- | nubByAdjacentReverse (on eq length) ([1]:[2]:[3,4]:Nil) == [3,4]:[2]:Nil` --- | ``` --- | --- | Running time: `O(n)` -nubByAdjacentReverse :: forall a. (a -> a -> Boolean) -> List a -> List a -nubByAdjacentReverse p = go Nil - where - go :: List a -> List a -> List a - -- empty output - go Nil (x : xs) = go (x : Nil) xs - -- checking for duplicates - go acc@(a : as) (x : xs) - | p a x = go (x : as) xs - | otherwise = go (x : acc) xs - -- empty input - go acc Nil = acc From 214abe18cd338467035774aa4db42b18f20e8684 Mon Sep 17 00:00:00 2001 From: Nicholas Scheel Date: Wed, 13 Jan 2021 03:11:12 -0600 Subject: [PATCH 15/21] Make stack-safe --- src/Data/List.purs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Data/List.purs b/src/Data/List.purs index edd6a61..b697202 100644 --- a/src/Data/List.purs +++ b/src/Data/List.purs @@ -684,14 +684,14 @@ nub = nubBy compare -- | -- | Running time: `O(n log n)` nubBy :: forall a. (a -> a -> Ordering) -> List a -> List a -nubBy p = go emptySet +nubBy p = reverse <<< go emptySet Nil where - go _ Nil = Nil - go s (a : as) = + go _ acc Nil = acc + go s acc (a : as) = let { found, result: s' } = insertAndLookupBy p a s in if found - then go s' as - else a : go s' as + then go s' acc as + else go s' (a : acc) as -- | Remove duplicate elements from a list. -- | Keeps the first occurrence of each element in the input list, From 531eac4a75b7ea497ac596f297c528e8a069718a Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Wed, 13 Jan 2021 20:43:50 -0800 Subject: [PATCH 16/21] Add Lazy nub and nubBy tests --- src/Data/List/Lazy.purs | 2 ++ test/Test/Data/List/Lazy.purs | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Data/List/Lazy.purs b/src/Data/List/Lazy.purs index 3b6ff84..24ed8b9 100644 --- a/src/Data/List/Lazy.purs +++ b/src/Data/List/Lazy.purs @@ -70,6 +70,8 @@ module Data.List.Lazy , groupBy , partition + , nub + , nubBy , nubEq , nubByEq , union diff --git a/test/Test/Data/List/Lazy.purs b/test/Test/Data/List/Lazy.purs index e243439..7b99093 100644 --- a/test/Test/Data/List/Lazy.purs +++ b/test/Test/Data/List/Lazy.purs @@ -3,10 +3,12 @@ module Test.Data.List.Lazy (testListLazy) where import Prelude import Control.Lazy (defer) +import Data.Array as Array import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex) +import Data.Function (on) import Data.FunctorWithIndex (mapWithIndex) import Data.Lazy as Z -import Data.List.Lazy (List, Pattern(..), alterAt, catMaybes, concat, concatMap, cons, delete, deleteAt, deleteBy, drop, dropWhile, elemIndex, elemLastIndex, filter, filterM, findIndex, findLastIndex, foldM, foldMap, foldl, foldr, foldrLazy, fromFoldable, group, groupBy, head, init, insert, insertAt, insertBy, intersect, intersectBy, iterate, last, length, mapMaybe, modifyAt, nil, nubEq, nubByEq, null, partition, range, repeat, replicate, replicateM, reverse, scanlLazy, singleton, slice, snoc, span, stripPrefix, tail, take, takeWhile, transpose, uncons, union, unionBy, unzip, updateAt, zip, zipWith, zipWithA, (!!), (..), (:), (\\)) +import Data.List.Lazy (List, Pattern(..), alterAt, catMaybes, concat, concatMap, cons, delete, deleteAt, deleteBy, drop, dropWhile, elemIndex, elemLastIndex, filter, filterM, findIndex, findLastIndex, foldM, foldMap, foldl, foldr, foldrLazy, fromFoldable, group, groupBy, head, init, insert, insertAt, insertBy, intersect, intersectBy, iterate, last, length, mapMaybe, modifyAt, nil, nub, nubBy, nubEq, nubByEq, null, partition, range, repeat, replicate, replicateM, reverse, scanlLazy, singleton, slice, snoc, span, stripPrefix, tail, take, takeWhile, transpose, uncons, union, unionBy, unzip, updateAt, zip, zipWith, zipWithA, (!!), (..), (:), (\\)) import Data.List.Lazy.NonEmpty as NEL import Data.Maybe (Maybe(..), isNothing, fromJust) import Data.Monoid.Additive (Additive(..)) @@ -328,12 +330,19 @@ testListLazy = do log "iterate on nonempty lazy list should apply supplied function correctly" assert $ (take 3 $ NEL.toList $ NEL.iterate (_ + 1) 0) == l [0, 1, 2] + log "nub should remove duplicate elements from the list, keeping the first occurence" + assert $ nub (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] + + log "nubBy should remove duplicate items from the list using a supplied predicate" + let nubPred = compare `on` Array.length + assert $ nubBy nubPred (l [[1],[2],[3,4]]) == l [[1],[3,4]] + log "nubEq should remove duplicate elements from the list, keeping the first occurence" assert $ nubEq (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] log "nubByEq should remove duplicate items from the list using a supplied predicate" - let nubPred = \x y -> if odd x then false else x == y - assert $ nubByEq nubPred (l [1, 2, 2, 3, 3, 4, 4, 1]) == l [1, 2, 3, 3, 4, 1] + let mod3eq = eq `on` \n -> mod n 3 + assert $ nubByEq mod3eq (l [1, 3, 4, 5, 6]) == l [1, 3, 5] log "union should produce the union of two lists" assert $ union (l [1, 2, 3]) (l [2, 3, 4]) == l [1, 2, 3, 4] From 0a3b0518275b67047de85096172a16f7474d2ae0 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Wed, 13 Jan 2021 20:56:44 -0800 Subject: [PATCH 17/21] Fix spelling --- test/Test/Data/List.purs | 6 +++--- test/Test/Data/List/NonEmpty.purs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/Test/Data/List.purs b/test/Test/Data/List.purs index c930306..5ac2db8 100644 --- a/test/Test/Data/List.purs +++ b/test/Test/Data/List.purs @@ -39,7 +39,7 @@ testList = do assert $ (range 0 5) == l [0, 1, 2, 3, 4, 5] assert $ (range 2 (-3)) == l [2, 1, 0, -1, -2, -3] - log "replicate should produce an list containg an item a specified number of times" + log "replicate should produce an list containing an item a specified number of times" assert $ replicate 3 true == l [true, true, true] assert $ replicate 1 "foo" == l ["foo"] assert $ replicate 0 "foo" == l [] @@ -283,14 +283,14 @@ testList = do assert $ partitioned.yes == l [5, 3, 4] assert $ partitioned.no == l [1, 2] - log "nub should remove duplicate elements from the list, keeping the first occurence" + log "nub should remove duplicate elements from the list, keeping the first occurrence" assert $ nub (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] log "nubBy should remove duplicate items from the list using a supplied predicate" let nubPred = compare `on` Array.length assert $ nubBy nubPred (l [[1],[2],[3,4]]) == l [[1],[3,4]] - log "nubEq should remove duplicate elements from the list, keeping the first occurence" + log "nubEq should remove duplicate elements from the list, keeping the first occurrence" assert $ nubEq (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] log "nubByEq should remove duplicate items from the list using a supplied predicate" diff --git a/test/Test/Data/List/NonEmpty.purs b/test/Test/Data/List/NonEmpty.purs index f6e4753..b12380a 100644 --- a/test/Test/Data/List/NonEmpty.purs +++ b/test/Test/Data/List/NonEmpty.purs @@ -184,14 +184,14 @@ testNonEmptyList = do assert $ partitioned.yes == l [5, 3, 4] assert $ partitioned.no == l [1, 2] - log "nub should remove duplicate elements from the list, keeping the first occurence" + log "nub should remove duplicate elements from the list, keeping the first occurrence" assert $ NEL.nub (nel 1 [2, 2, 3, 4, 1]) == nel 1 [2, 3, 4] log "nubBy should remove duplicate items from the list using a supplied predicate" let nubPred = compare `on` Array.length assert $ NEL.nubBy nubPred (nel [1] [[2],[3,4]]) == nel [1] [[3,4]] - log "nubEq should remove duplicate elements from the list, keeping the first occurence" + log "nubEq should remove duplicate elements from the list, keeping the first occurrence" assert $ NEL.nubEq (nel 1 [2, 2, 3, 4, 1]) == nel 1 [2, 3, 4] log "nubByEq should remove duplicate items from the list using a supplied predicate" From caf19f4abe7b31a7db80933b3098cbde5bcac89c Mon Sep 17 00:00:00 2001 From: milesfrain Date: Thu, 14 Jan 2021 10:54:17 -0800 Subject: [PATCH 18/21] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1b2aed..81e05f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,10 @@ Notable changes to this project are documented in this file. The format is based ## [Unreleased] Breaking changes: +- Convert `nub`/`nubBy` to use ordering, rather than equality (#179) New features: +- Add `nubEq`/`nubByEq` (#179) Bugfixes: From 6318e765250260237250684d24244bb38bba2692 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Sat, 16 Jan 2021 10:09:32 -0800 Subject: [PATCH 19/21] Review feedback - test nub on infinite list --- test/Test/Data/List/Lazy.purs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/Test/Data/List/Lazy.purs b/test/Test/Data/List/Lazy.purs index 7b99093..3bcbb64 100644 --- a/test/Test/Data/List/Lazy.purs +++ b/test/Test/Data/List/Lazy.purs @@ -8,7 +8,7 @@ import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex) import Data.Function (on) import Data.FunctorWithIndex (mapWithIndex) import Data.Lazy as Z -import Data.List.Lazy (List, Pattern(..), alterAt, catMaybes, concat, concatMap, cons, delete, deleteAt, deleteBy, drop, dropWhile, elemIndex, elemLastIndex, filter, filterM, findIndex, findLastIndex, foldM, foldMap, foldl, foldr, foldrLazy, fromFoldable, group, groupBy, head, init, insert, insertAt, insertBy, intersect, intersectBy, iterate, last, length, mapMaybe, modifyAt, nil, nub, nubBy, nubEq, nubByEq, null, partition, range, repeat, replicate, replicateM, reverse, scanlLazy, singleton, slice, snoc, span, stripPrefix, tail, take, takeWhile, transpose, uncons, union, unionBy, unzip, updateAt, zip, zipWith, zipWithA, (!!), (..), (:), (\\)) +import Data.List.Lazy (List, Pattern(..), alterAt, catMaybes, concat, concatMap, cycle, cons, delete, deleteAt, deleteBy, drop, dropWhile, elemIndex, elemLastIndex, filter, filterM, findIndex, findLastIndex, foldM, foldMap, foldl, foldr, foldrLazy, fromFoldable, group, groupBy, head, init, insert, insertAt, insertBy, intersect, intersectBy, iterate, last, length, mapMaybe, modifyAt, nil, nub, nubBy, nubEq, nubByEq, null, partition, range, repeat, replicate, replicateM, reverse, scanlLazy, singleton, slice, snoc, span, stripPrefix, tail, take, takeWhile, transpose, uncons, union, unionBy, unzip, updateAt, zip, zipWith, zipWithA, (!!), (..), (:), (\\)) import Data.List.Lazy.NonEmpty as NEL import Data.Maybe (Maybe(..), isNothing, fromJust) import Data.Monoid.Additive (Additive(..)) @@ -333,6 +333,9 @@ testListLazy = do log "nub should remove duplicate elements from the list, keeping the first occurence" assert $ nub (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] + log "nub should not consume more of the input list than necessary" + assert $ (take 3 $ nub $ cycle $ l [1,2,3]) == l [1,2,3] + log "nubBy should remove duplicate items from the list using a supplied predicate" let nubPred = compare `on` Array.length assert $ nubBy nubPred (l [[1],[2],[3,4]]) == l [[1],[3,4]] From 1711f13c95dd0df65379905150e5d4c61d757c7b Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Sat, 16 Jan 2021 10:09:46 -0800 Subject: [PATCH 20/21] Spelling fixups --- test/Test/Data/List/Lazy.purs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Test/Data/List/Lazy.purs b/test/Test/Data/List/Lazy.purs index 3bcbb64..0daa38b 100644 --- a/test/Test/Data/List/Lazy.purs +++ b/test/Test/Data/List/Lazy.purs @@ -104,7 +104,7 @@ testListLazy = do log "range should be lazy" assert $ head (range 0 100000000) == Just 0 - log "replicate should produce an list containg an item a specified number of times" + log "replicate should produce an list containing an item a specified number of times" assert $ replicate 3 true == l [true, true, true] assert $ replicate 1 "foo" == l ["foo"] assert $ replicate 0 "foo" == l [] @@ -330,7 +330,7 @@ testListLazy = do log "iterate on nonempty lazy list should apply supplied function correctly" assert $ (take 3 $ NEL.toList $ NEL.iterate (_ + 1) 0) == l [0, 1, 2] - log "nub should remove duplicate elements from the list, keeping the first occurence" + log "nub should remove duplicate elements from the list, keeping the first occurrence" assert $ nub (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] log "nub should not consume more of the input list than necessary" @@ -340,7 +340,7 @@ testListLazy = do let nubPred = compare `on` Array.length assert $ nubBy nubPred (l [[1],[2],[3,4]]) == l [[1],[3,4]] - log "nubEq should remove duplicate elements from the list, keeping the first occurence" + log "nubEq should remove duplicate elements from the list, keeping the first occurrence" assert $ nubEq (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] log "nubByEq should remove duplicate items from the list using a supplied predicate" From a76bcdab5cb45a03ac386e76cc856545d9c26096 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Sat, 16 Jan 2021 11:37:24 -0800 Subject: [PATCH 21/21] Test nubEq on infinite list --- test/Test/Data/List/Lazy.purs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Test/Data/List/Lazy.purs b/test/Test/Data/List/Lazy.purs index 0daa38b..5eef700 100644 --- a/test/Test/Data/List/Lazy.purs +++ b/test/Test/Data/List/Lazy.purs @@ -343,6 +343,9 @@ testListLazy = do log "nubEq should remove duplicate elements from the list, keeping the first occurrence" assert $ nubEq (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] + log "nubEq should not consume more of the input list than necessary" + assert $ (take 3 $ nubEq $ cycle $ l [1,2,3]) == l [1,2,3] + log "nubByEq should remove duplicate items from the list using a supplied predicate" let mod3eq = eq `on` \n -> mod n 3 assert $ nubByEq mod3eq (l [1, 3, 4, 5, 6]) == l [1, 3, 5]