Skip to content

Commit 0e4a010

Browse files
gbaztreeowl
authored andcommitted
add nubOrd and friends (#515)
1 parent a4b7392 commit 0e4a010

File tree

3 files changed

+112
-0
lines changed

3 files changed

+112
-0
lines changed

Data/Containers/ListUtils.hs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{-# LANGUAGE CPP #-}
2+
#if !defined(TESTING) && __GLASGOW_HASKELL__ >= 703
3+
{-# LANGUAGE Safe #-}
4+
#endif
5+
6+
-----------------------------------------------------------------------------
7+
-- |
8+
-- Module : Data.Containers.ListUtils
9+
-- Copyright : (c) Gershom Bazerman 2018
10+
-- License : BSD-style
11+
-- Maintainer : [email protected]
12+
-- Portability : portable
13+
--
14+
-- This module provides efficient containers-based functions on the list type.
15+
-----------------------------------------------------------------------------
16+
17+
module Data.Containers.ListUtils (
18+
nubOrd,
19+
nubOrdOn,
20+
nubInt,
21+
nubIntOn
22+
) where
23+
24+
import qualified Data.Set as Set
25+
import qualified Data.IntSet as IntSet
26+
27+
-- | /O(n log n)/. The 'nubOrd' function removes duplicate elements from a list.
28+
-- In particular, it keeps only the first occurrence of each element. By using a 'Set' internally
29+
-- it has better asymptotics than the standard 'nub' function.
30+
nubOrd :: (Ord a) => [a] -> [a]
31+
nubOrd = go Set.empty
32+
where
33+
go _ [] = []
34+
go s (x:xs) = if x `Set.member` s then go s xs
35+
else x : go (Set.insert x s) xs
36+
37+
-- | The `nubOrdOn` function behaves just like `nubOrd` except it performs comparisons not on the
38+
-- original datatype, but a user-specified projection from that datatype.
39+
nubOrdOn :: (Ord b) => (a -> b) -> [a] -> [a]
40+
nubOrdOn f = go Set.empty
41+
where
42+
go _ [] = []
43+
go s (x:xs) = let fx = f x
44+
in if fx `Set.member` s then go s xs
45+
else x : go (Set.insert fx s) xs
46+
47+
-- | /O(n min(n,W))/. The 'nubInt' function removes duplicate elements from a list.
48+
-- In particular, it keeps only the first occurrence of each element. By using an 'IntSet' internally
49+
-- it has better asymptotics than the standard 'nub' function.
50+
nubInt :: [Int] -> [Int]
51+
nubInt = go IntSet.empty
52+
where
53+
go _ [] = []
54+
go s (x:xs) = if x `IntSet.member` s then go s xs
55+
else x : go (IntSet.insert x s) xs
56+
57+
-- | The `nubIntOn` function behaves just like 'nubInt' except it performs comparisons not on the
58+
-- original datatype, but a user-specified projection from that datatype to 'Int'.
59+
nubIntOn :: (a -> Int) -> [a] -> [a]
60+
nubIntOn f = go IntSet.empty
61+
where
62+
go _ [] = []
63+
go s (x:xs) = let fx = f x
64+
in if fx `IntSet.member` s then go s xs
65+
else x : go (IntSet.insert fx s) xs

containers.cabal

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Library
4949
other-extensions: CPP, BangPatterns
5050

5151
exposed-modules:
52+
Data.Containers.ListUtils
5253
Data.IntMap
5354
Data.IntMap.Lazy
5455
Data.IntMap.Strict
@@ -618,3 +619,22 @@ test-suite intset-strictness-properties
618619

619620
ghc-options: -Wall
620621
include-dirs: include
622+
623+
test-suite listutils-properties
624+
hs-source-dirs: tests, .
625+
main-is: listutils-properties.hs
626+
other-modules:
627+
Data.Containers.ListUtils
628+
type: exitcode-stdio-1.0
629+
630+
build-depends:
631+
base >= 4.3 && < 5,
632+
ChasingBottoms,
633+
deepseq >= 1.2 && < 1.5,
634+
QuickCheck >= 2.7.1,
635+
ghc-prim,
636+
test-framework >= 0.3.3,
637+
test-framework-quickcheck2 >= 0.2.9
638+
639+
ghc-options: -Wall
640+
include-dirs: include

tests/listutils-properties.hs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module Main where
2+
3+
import Data.List (nub, nubBy)
4+
import Data.Containers.ListUtils
5+
import Test.Framework
6+
import Test.Framework.Providers.QuickCheck2
7+
8+
main :: IO ()
9+
main = defaultMain
10+
[ testProperty "nubOrd" prop_nubOrd
11+
, testProperty "nubOrdOn" prop_nubOrdOn
12+
, testProperty "nubInt" prop_nubInt
13+
, testProperty "nubIntOn" prop_nubIntOn
14+
]
15+
16+
17+
prop_nubOrd :: [Int] -> Bool
18+
prop_nubOrd xs = nubOrd xs == nub xs
19+
20+
prop_nubInt :: [Int] -> Bool
21+
prop_nubInt xs = nubInt xs == nub xs
22+
23+
prop_nubOrdOn :: [(Int,Int)] -> Bool
24+
prop_nubOrdOn xs = nubOrdOn snd xs == nubBy (\x y -> snd x == snd y) xs
25+
26+
prop_nubIntOn :: [(Int,Int)] -> Bool
27+
prop_nubIntOn xs = nubIntOn snd xs == nubBy (\x y -> snd x == snd y) xs

0 commit comments

Comments
 (0)