Skip to content

Commit f967d33

Browse files
authored
Add/correct some time complexities (#889)
Added: * Set: take, drop, powerSet, disjointUnion * Map: take, drop, splitMember Corrected: * Set: cartesianProduct, showTree, showTreeWith * Map: showTree, showTreeWith * IntSet: lookupLT, lookupGT, lookupLE, lookupGE, showTree, showTreeWith * IntMap: lookupLT, lookupGT, lookupLE, lookupGE, alterF, showTree, showTreeWith * Update comments with complexity analysis
1 parent 468aa9d commit f967d33

File tree

5 files changed

+62
-32
lines changed

5 files changed

+62
-32
lines changed

containers/src/Data/IntMap/Internal.hs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ findWithDefault def !k = go
616616
| otherwise = def
617617
go Nil = def
618618

619-
-- | \(O(\log n)\). Find largest key smaller than the given one and return the
619+
-- | \(O(\min(n,W))\). Find largest key smaller than the given one and return the
620620
-- corresponding (key, value) pair.
621621
--
622622
-- > lookupLT 3 (fromList [(3,'a'), (5,'b')]) == Nothing
@@ -637,7 +637,7 @@ lookupLT !k t = case t of
637637
| otherwise = Just (ky, y)
638638
go def Nil = unsafeFindMax def
639639

640-
-- | \(O(\log n)\). Find smallest key greater than the given one and return the
640+
-- | \(O(\min(n,W))\). Find smallest key greater than the given one and return the
641641
-- corresponding (key, value) pair.
642642
--
643643
-- > lookupGT 4 (fromList [(3,'a'), (5,'b')]) == Just (5, 'b')
@@ -658,7 +658,7 @@ lookupGT !k t = case t of
658658
| otherwise = Just (ky, y)
659659
go def Nil = unsafeFindMin def
660660

661-
-- | \(O(\log n)\). Find largest key smaller or equal to the given one and return
661+
-- | \(O(\min(n,W))\). Find largest key smaller or equal to the given one and return
662662
-- the corresponding (key, value) pair.
663663
--
664664
-- > lookupLE 2 (fromList [(3,'a'), (5,'b')]) == Nothing
@@ -680,7 +680,7 @@ lookupLE !k t = case t of
680680
| otherwise = Just (ky, y)
681681
go def Nil = unsafeFindMax def
682682

683-
-- | \(O(\log n)\). Find smallest key greater or equal to the given one and return
683+
-- | \(O(\min(n,W))\). Find smallest key greater or equal to the given one and return
684684
-- the corresponding (key, value) pair.
685685
--
686686
-- > lookupGE 3 (fromList [(3,'a'), (5,'b')]) == Just (3, 'a')
@@ -1010,7 +1010,7 @@ alter f k Nil = case f Nothing of
10101010
Just x -> Tip k x
10111011
Nothing -> Nil
10121012

1013-
-- | \(O(\log n)\). The expression (@'alterF' f k map@) alters the value @x@ at
1013+
-- | \(O(\min(n,W))\). The expression (@'alterF' f k map@) alters the value @x@ at
10141014
-- @k@, or absence thereof. 'alterF' can be used to inspect, insert, delete,
10151015
-- or update a value in an 'IntMap'. In short : @'lookup' k <$> 'alterF' f k m = f
10161016
-- ('lookup' k m)@.
@@ -3550,14 +3550,14 @@ splitRoot orig =
35503550
Debugging
35513551
--------------------------------------------------------------------}
35523552

3553-
-- | \(O(n)\). Show the tree that implements the map. The tree is shown
3553+
-- | \(O(n \min(n,W))\). Show the tree that implements the map. The tree is shown
35543554
-- in a compressed, hanging format.
35553555
showTree :: Show a => IntMap a -> String
35563556
showTree s
35573557
= showTreeWith True False s
35583558

35593559

3560-
{- | \(O(n)\). The expression (@'showTreeWith' hang wide map@) shows
3560+
{- | \(O(n \min(n,W))\). The expression (@'showTreeWith' hang wide map@) shows
35613561
the tree that implements the map. If @hang@ is
35623562
'True', a /hanging/ tree is shown otherwise a rotated tree is shown. If
35633563
@wide@ is 'True', an extra wide version is shown.

containers/src/Data/IntSet/Internal.hs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ member !x = go
352352
notMember :: Key -> IntSet -> Bool
353353
notMember k = not . member k
354354

355-
-- | \(O(\log n)\). Find largest element smaller than the given one.
355+
-- | \(O(\min(n,W))\). Find largest element smaller than the given one.
356356
--
357357
-- > lookupLT 3 (fromList [3, 5]) == Nothing
358358
-- > lookupLT 5 (fromList [3, 5]) == Just 3
@@ -373,7 +373,7 @@ lookupLT !x t = case t of
373373
go def Nil = unsafeFindMax def
374374

375375

376-
-- | \(O(\log n)\). Find smallest element greater than the given one.
376+
-- | \(O(\min(n,W))\). Find smallest element greater than the given one.
377377
--
378378
-- > lookupGT 4 (fromList [3, 5]) == Just 5
379379
-- > lookupGT 5 (fromList [3, 5]) == Nothing
@@ -394,7 +394,7 @@ lookupGT !x t = case t of
394394
go def Nil = unsafeFindMin def
395395

396396

397-
-- | \(O(\log n)\). Find largest element smaller or equal to the given one.
397+
-- | \(O(\min(n,W))\). Find largest element smaller or equal to the given one.
398398
--
399399
-- > lookupLE 2 (fromList [3, 5]) == Nothing
400400
-- > lookupLE 4 (fromList [3, 5]) == Just 3
@@ -416,7 +416,7 @@ lookupLE !x t = case t of
416416
go def Nil = unsafeFindMax def
417417

418418

419-
-- | \(O(\log n)\). Find smallest element greater or equal to the given one.
419+
-- | \(O(\min(n,W))\). Find smallest element greater or equal to the given one.
420420
--
421421
-- > lookupGE 3 (fromList [3, 5]) == Just 3
422422
-- > lookupGE 4 (fromList [3, 5]) == Just 5
@@ -1353,14 +1353,14 @@ instance NFData IntSet where rnf x = seq x ()
13531353
{--------------------------------------------------------------------
13541354
Debugging
13551355
--------------------------------------------------------------------}
1356-
-- | \(O(n)\). Show the tree that implements the set. The tree is shown
1356+
-- | \(O(n \min(n,W))\). Show the tree that implements the set. The tree is shown
13571357
-- in a compressed, hanging format.
13581358
showTree :: IntSet -> String
13591359
showTree s
13601360
= showTreeWith True False s
13611361

13621362

1363-
{- | \(O(n)\). The expression (@'showTreeWith' hang wide map@) shows
1363+
{- | \(O(n \min(n,W))\). The expression (@'showTreeWith' hang wide map@) shows
13641364
the tree that implements the set. If @hang@ is
13651365
'True', a /hanging/ tree is shown otherwise a rotated tree is shown. If
13661366
@wide@ is 'True', an extra wide version is shown.

containers/src/Data/Map/Internal.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,7 +1489,7 @@ elemAt i (Bin _ kx x l r)
14891489
where
14901490
sizeL = size l
14911491

1492-
-- | Take a given number of entries in key order, beginning
1492+
-- | \(O(\log n)\). Take a given number of entries in key order, beginning
14931493
-- with the smallest keys.
14941494
--
14951495
-- @
@@ -1511,7 +1511,7 @@ take i0 m0 = go i0 m0
15111511
EQ -> l
15121512
where sizeL = size l
15131513

1514-
-- | Drop a given number of entries in key order, beginning
1514+
-- | \(O(\log n)\). Drop a given number of entries in key order, beginning
15151515
-- with the smallest keys.
15161516
--
15171517
-- @
@@ -3831,7 +3831,7 @@ splitLookup k0 m = case go k0 m of
38313831
{-# INLINABLE splitLookup #-}
38323832
#endif
38333833

3834-
-- | A variant of 'splitLookup' that indicates only whether the
3834+
-- | \(O(\log n)\). A variant of 'splitLookup' that indicates only whether the
38353835
-- key was present, rather than producing its value. This is used to
38363836
-- implement 'intersection' to avoid allocating unnecessary 'Just'
38373837
-- constructors.

containers/src/Data/Map/Internal/Debug.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module Data.Map.Internal.Debug where
66
import Data.Map.Internal (Map (..), size, delta)
77
import Control.Monad (guard)
88

9-
-- | \(O(n)\). Show the tree that implements the map. The tree is shown
9+
-- | \(O(n \log n)\). Show the tree that implements the map. The tree is shown
1010
-- in a compressed, hanging format. See 'showTreeWith'.
1111
showTree :: (Show k,Show a) => Map k a -> String
1212
showTree m
@@ -15,7 +15,7 @@ showTree m
1515
showElem k x = show k ++ ":=" ++ show x
1616

1717

18-
{- | \(O(n)\). The expression (@'showTreeWith' showelem hang wide map@) shows
18+
{- | \(O(n \log n)\). The expression (@'showTreeWith' showelem hang wide map@) shows
1919
the tree that implements the map. Elements are shown using the @showElem@ function. If @hang@ is
2020
'True', a /hanging/ tree is shown otherwise a rotated tree is shown. If
2121
@wide@ is 'True', an extra wide version is shown.

containers/src/Data/Set/Internal.hs

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,7 +1407,7 @@ deleteAt !i t =
14071407
where
14081408
sizeL = size l
14091409

1410-
-- | Take a given number of elements in order, beginning
1410+
-- | \(O(\log n)\). Take a given number of elements in order, beginning
14111411
-- with the smallest ones.
14121412
--
14131413
-- @
@@ -1428,7 +1428,7 @@ take i0 m0 = go i0 m0
14281428
EQ -> l
14291429
where sizeL = size l
14301430

1431-
-- | Drop a given number of elements in order, beginning
1431+
-- | \(O(\log n)\). Drop a given number of elements in order, beginning
14321432
-- with the smallest ones.
14331433
--
14341434
-- @
@@ -1809,7 +1809,7 @@ splitRoot orig =
18091809
{-# INLINE splitRoot #-}
18101810

18111811

1812-
-- | Calculate the power set of a set: the set of all its subsets.
1812+
-- | \(O(2^n \log n)\). Calculate the power set of a set: the set of all its subsets.
18131813
--
18141814
-- @
18151815
-- t ``member`` powerSet s == t ``isSubsetOf`` s
@@ -1823,11 +1823,22 @@ splitRoot orig =
18231823
-- @
18241824
--
18251825
-- @since 0.5.11
1826+
1827+
-- Proof of complexity: step executes n times. At the ith step,
1828+
-- "insertMin x `mapMonotonic` pxs" takes O(2^i log i) time since pxs has size
1829+
-- 2^i - 1 and we insertMin into its elements which are sets of size <= i.
1830+
-- "insertMin (singleton x)" and "`glue` pxs" are cheaper operations that both
1831+
-- take O(i) time. Over n steps, we have a total cost of
1832+
--
1833+
-- O(\sum_{i=1}^{n-1} 2^i log i)
1834+
-- = O(log n * \sum_{i=1}^{n-1} 2^i)
1835+
-- = O(2^n log n)
1836+
18261837
powerSet :: Set a -> Set (Set a)
18271838
powerSet xs0 = insertMin empty (foldr' step Tip xs0) where
18281839
step x pxs = insertMin (singleton x) (insertMin x `mapMonotonic` pxs) `glue` pxs
18291840

1830-
-- | \(O(mn)\) (conjectured). Calculate the Cartesian product of two sets.
1841+
-- | \(O(nm)\). Calculate the Cartesian product of two sets.
18311842
--
18321843
-- @
18331844
-- cartesianProduct xs ys = fromList $ liftA2 (,) (toList xs) (toList ys)
@@ -1842,11 +1853,7 @@ powerSet xs0 = insertMin empty (foldr' step Tip xs0) where
18421853
--
18431854
-- @since 0.5.11
18441855
cartesianProduct :: Set a -> Set b -> Set (a, b)
1845-
-- I don't know for sure if this implementation (slightly modified from one
1846-
-- that Edward Kmett hacked together) is optimal. TODO: try to prove or
1847-
-- refute it.
1848-
--
1849-
-- We could definitely get big-O optimal (O(m * n)) in a rather simple way:
1856+
-- The obvious big-O optimal (O(nm)) implementation would be
18501857
--
18511858
-- cartesianProduct _as Tip = Tip
18521859
-- cartesianProduct as bs = fromDistinctAscList
@@ -1855,8 +1862,31 @@ cartesianProduct :: Set a -> Set b -> Set (a, b)
18551862
-- Unfortunately, this is much slower in practice, at least when the sets are
18561863
-- constructed from ascending lists. I tried doing the same thing using a
18571864
-- known-length (perfect balancing) variant of fromDistinctAscList, but it
1858-
-- still didn't come close to the performance of Kmett's version in my very
1859-
-- informal tests.
1865+
-- still didn't come close to the performance of the implementation we use in my
1866+
-- very informal tests.
1867+
--
1868+
-- The implementation we use (slightly modified from one that Edward Kmett
1869+
-- hacked together) is also optimal but performs better in practice. We map
1870+
-- each element a in as to a set made up of (a,b) for every element b in bs,
1871+
-- taking O(nm) overall. Then we merge these sets up the tree of as, which takes
1872+
-- O(n log m). A brief sketch of proof for the latter:
1873+
--
1874+
-- Consider all nodes in the tree at the same distance from the root to be at
1875+
-- the same "level". The nodes farthest from the root are at level 0, with
1876+
-- levels increasing by 1 towards the root. Being a balanced tree, there are
1877+
-- O(n/2^i) nodes at level i. At every node at level i, we merge the merged left
1878+
-- set, current set, and merged right set into a set of size O(2^i*m) in
1879+
-- O(log (2^i*m)) = O(i + log m) time. Over all levels, we do a total work of
1880+
--
1881+
-- O(\sum_{i=0}^{root_level} n * (i + log m) / 2^i)
1882+
-- = O( \sum_{i=0}^{root_level} n * i / 2^i
1883+
-- + \sum_{i=0}^{root_level} n * log m / 2^i)
1884+
-- = O( n * \sum_{i=0}^{root_level} i/2^i
1885+
-- + n * log m * \sum_{i=0}^{root_level} 1/2^i)
1886+
-- = O( n * \sum_{i=0}^{inf} i/2^i
1887+
-- + n * log m * \sum_{i=0}^{inf} 1/2^i)
1888+
--
1889+
-- The sum terms converge, and we get O(n log m).
18601890

18611891
-- When the second argument has at most one element, we can be a little
18621892
-- clever.
@@ -1879,7 +1909,7 @@ instance Monoid (MergeSet a) where
18791909

18801910
mappend = (<>)
18811911

1882-
-- | Calculate the disjoint union of two sets.
1912+
-- | \(O(n+m)\). Calculate the disjoint union of two sets.
18831913
--
18841914
-- @ disjointUnion xs ys = map Left xs ``union`` map Right ys @
18851915
--
@@ -1897,14 +1927,14 @@ disjointUnion as bs = merge (mapMonotonic Left as) (mapMonotonic Right bs)
18971927
{--------------------------------------------------------------------
18981928
Debugging
18991929
--------------------------------------------------------------------}
1900-
-- | \(O(n)\). Show the tree that implements the set. The tree is shown
1930+
-- | \(O(n \log n)\). Show the tree that implements the set. The tree is shown
19011931
-- in a compressed, hanging format.
19021932
showTree :: Show a => Set a -> String
19031933
showTree s
19041934
= showTreeWith True False s
19051935

19061936

1907-
{- | \(O(n)\). The expression (@showTreeWith hang wide map@) shows
1937+
{- | \(O(n \log n)\). The expression (@showTreeWith hang wide map@) shows
19081938
the tree that implements the set. If @hang@ is
19091939
@True@, a /hanging/ tree is shown otherwise a rotated tree is shown. If
19101940
@wide@ is 'True', an extra wide version is shown.

0 commit comments

Comments
 (0)