Skip to content

Commit 22f2247

Browse files
authored
Map/IntMap: Document the most common O(n²) performance mistake (#961)
1 parent 269f53e commit 22f2247

File tree

5 files changed

+222
-6
lines changed

5 files changed

+222
-6
lines changed

containers/docs/map.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ values with the same key.
202202
Map.fromListWith (++) [(1, "a"), (1, "b"), (2, "x"), (2, "y")]
203203
> fromList [(1,"ba"),(2,"yx")]
204204

205+
.. TIP::
206+
Read the documentation of :haddock_short:`/Data.Map.Strict#fromListWith`
207+
to avoid accidental ``O(n²)`` performance when writing patterns
208+
such as ``fromListWith (++)``.
205209

206210

207211
Create a list from a map

containers/src/Data/IntMap/Internal.hs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,8 @@ insert k x Nil = Tip k x
830830
-- > insertWith (++) 5 "xxx" (fromList [(5,"a"), (3,"b")]) == fromList [(3, "b"), (5, "xxxa")]
831831
-- > insertWith (++) 7 "xxx" (fromList [(5,"a"), (3,"b")]) == fromList [(3, "b"), (5, "a"), (7, "xxx")]
832832
-- > insertWith (++) 5 "xxx" empty == singleton 5 "xxx"
833+
--
834+
-- Also see the performance note on 'fromListWith'.
833835

834836
insertWith :: (a -> a -> a) -> Key -> a -> IntMap a -> IntMap a
835837
insertWith f k x t
@@ -845,6 +847,8 @@ insertWith f k x t
845847
-- > insertWithKey f 5 "xxx" (fromList [(5,"a"), (3,"b")]) == fromList [(3, "b"), (5, "5:xxx|a")]
846848
-- > insertWithKey f 7 "xxx" (fromList [(5,"a"), (3,"b")]) == fromList [(3, "b"), (5, "a"), (7, "xxx")]
847849
-- > insertWithKey f 5 "xxx" empty == singleton 5 "xxx"
850+
--
851+
-- Also see the performance note on 'fromListWith'.
848852

849853
insertWithKey :: (Key -> a -> a -> a) -> Key -> a -> IntMap a -> IntMap a
850854
insertWithKey f !k x t@(Bin p m l r)
@@ -870,6 +874,8 @@ insertWithKey _ k x Nil = Tip k x
870874
-- > let insertLookup kx x t = insertLookupWithKey (\_ a _ -> a) kx x t
871875
-- > insertLookup 5 "x" (fromList [(5,"a"), (3,"b")]) == (Just "a", fromList [(3, "b"), (5, "x")])
872876
-- > insertLookup 7 "x" (fromList [(5,"a"), (3,"b")]) == (Nothing, fromList [(3, "b"), (5, "a"), (7, "x")])
877+
--
878+
-- Also see the performance note on 'fromListWith'.
873879

874880
insertLookupWithKey :: (Key -> a -> a -> a) -> Key -> a -> IntMap a -> (Maybe a, IntMap a)
875881
insertLookupWithKey f !k x t@(Bin p m l r)
@@ -1085,6 +1091,8 @@ union m1 m2
10851091
-- | \(O(n+m)\). The union with a combining function.
10861092
--
10871093
-- > unionWith (++) (fromList [(5, "a"), (3, "b")]) (fromList [(5, "A"), (7, "C")]) == fromList [(3, "b"), (5, "aA"), (7, "C")]
1094+
--
1095+
-- Also see the performance note on 'fromListWith'.
10881096

10891097
unionWith :: (a -> a -> a) -> IntMap a -> IntMap a -> IntMap a
10901098
unionWith f m1 m2
@@ -1094,6 +1102,8 @@ unionWith f m1 m2
10941102
--
10951103
-- > let f key left_value right_value = (show key) ++ ":" ++ left_value ++ "|" ++ right_value
10961104
-- > unionWithKey f (fromList [(5, "a"), (3, "b")]) (fromList [(5, "A"), (7, "C")]) == fromList [(3, "b"), (5, "5:a|A"), (7, "C")]
1105+
--
1106+
-- Also see the performance note on 'fromListWith'.
10971107

10981108
unionWithKey :: (Key -> a -> a -> a) -> IntMap a -> IntMap a -> IntMap a
10991109
unionWithKey f m1 m2
@@ -2540,6 +2550,8 @@ mapKeys f = fromList . foldrWithKey (\k x xs -> (f k, x) : xs) []
25402550
--
25412551
-- > mapKeysWith (++) (\ _ -> 1) (fromList [(1,"b"), (2,"a"), (3,"d"), (4,"c")]) == singleton 1 "cdab"
25422552
-- > mapKeysWith (++) (\ _ -> 3) (fromList [(1,"b"), (2,"a"), (3,"d"), (4,"c")]) == singleton 3 "cdab"
2553+
--
2554+
-- Also see the performance note on 'fromListWith'.
25432555

25442556
mapKeysWith :: (a -> a -> a) -> (Key->Key) -> IntMap a -> IntMap a
25452557
mapKeysWith c f
@@ -3195,10 +3207,41 @@ fromList xs
31953207
where
31963208
ins t (k,x) = insert k x t
31973209

3198-
-- | \(O(n \min(n,W))\). Create a map from a list of key\/value pairs with a combining function. See also 'fromAscListWith'.
3210+
-- | \(O(n \min(n,W))\). Build a map from a list of key\/value pairs with a combining function. See also 'fromAscListWith'.
31993211
--
3200-
-- > fromListWith (++) [(5,"a"), (5,"b"), (3,"b"), (3,"a"), (5,"c")] == fromList [(3, "ab"), (5, "cba")]
3212+
-- > fromListWith (++) [(5,"a"), (5,"b"), (3,"x"), (5,"c")] == fromList [(3, "x"), (5, "cba")]
32013213
-- > fromListWith (++) [] == empty
3214+
--
3215+
-- Note the reverse ordering of @"cba"@ in the example.
3216+
--
3217+
-- The symmetric combining function @f@ is applied in a left-fold over the list, as @f new old@.
3218+
--
3219+
-- === Performance
3220+
--
3221+
-- You should ensure that the given @f@ is fast with this order of arguments.
3222+
--
3223+
-- Symmetric functions may be slow in one order, and fast in another.
3224+
-- For the common case of collecting values of matching keys in a list, as above:
3225+
--
3226+
-- The complexity of @(++) a b@ is \(O(a)\), so it is fast when given a short list as its first argument.
3227+
-- Thus:
3228+
--
3229+
-- > fromListWith (++) (replicate 1000000 (3, "x")) -- O(n), fast
3230+
-- > fromListWith (flip (++)) (replicate 1000000 (3, "x")) -- O(n²), extremely slow
3231+
--
3232+
-- because they evaluate as, respectively:
3233+
--
3234+
-- > fromList [(3, "x" ++ ("x" ++ "xxxxx..xxxxx"))] -- O(n)
3235+
-- > fromList [(3, ("xxxxx..xxxxx" ++ "x") ++ "x")] -- O(n²)
3236+
--
3237+
-- Thus, to get good performance with an operation like @(++)@ while also preserving
3238+
-- the same order as in the input list, reverse the input:
3239+
--
3240+
-- > fromListWith (++) (reverse [(5,"a"), (5,"b"), (5,"c")]) == fromList [(5, "abc")]
3241+
--
3242+
-- and it is always fast to combine singleton-list values @[v]@ with @fromListWith (++)@, as in:
3243+
--
3244+
-- > fromListWith (++) $ reverse $ map (\(k, v) -> (k, [v])) someListOfTuples
32023245

32033246
fromListWith :: (a -> a -> a) -> [(Key,a)] -> IntMap a
32043247
fromListWith f xs
@@ -3209,6 +3252,8 @@ fromListWith f xs
32093252
-- > let f key new_value old_value = show key ++ ":" ++ new_value ++ "|" ++ old_value
32103253
-- > fromListWithKey f [(5,"a"), (5,"b"), (3,"b"), (3,"a"), (5,"c")] == fromList [(3, "3:a|b"), (5, "5:c|5:b|a")]
32113254
-- > fromListWithKey f [] == empty
3255+
--
3256+
-- Also see the performance note on 'fromListWith'.
32123257

32133258
fromListWithKey :: (Key -> a -> a -> a) -> [(Key,a)] -> IntMap a
32143259
fromListWithKey f xs
@@ -3231,6 +3276,8 @@ fromAscList = fromMonoListWithKey Nondistinct (\_ x _ -> x)
32313276
-- /The precondition (input list is ascending) is not checked./
32323277
--
32333278
-- > fromAscListWith (++) [(3,"b"), (5,"a"), (5,"b")] == fromList [(3, "b"), (5, "ba")]
3279+
--
3280+
-- Also see the performance note on 'fromListWith'.
32343281

32353282
fromAscListWith :: (a -> a -> a) -> [(Key,a)] -> IntMap a
32363283
fromAscListWith f = fromMonoListWithKey Nondistinct (\_ x y -> f x y)
@@ -3242,6 +3289,8 @@ fromAscListWith f = fromMonoListWithKey Nondistinct (\_ x y -> f x y)
32423289
--
32433290
-- > let f key new_value old_value = (show key) ++ ":" ++ new_value ++ "|" ++ old_value
32443291
-- > fromAscListWithKey f [(3,"b"), (5,"a"), (5,"b")] == fromList [(3, "b"), (5, "5:b|a")]
3292+
--
3293+
-- Also see the performance note on 'fromListWith'.
32453294

32463295
fromAscListWithKey :: (Key -> a -> a -> a) -> [(Key,a)] -> IntMap a
32473296
fromAscListWithKey f = fromMonoListWithKey Nondistinct f
@@ -3263,6 +3312,8 @@ fromDistinctAscList = fromMonoListWithKey Distinct (\_ x _ -> x)
32633312
-- The precise conditions under which this function works are subtle:
32643313
-- For any branch mask, keys with the same prefix w.r.t. the branch
32653314
-- mask must occur consecutively in the list.
3315+
--
3316+
-- Also see the performance note on 'fromListWith'.
32663317

32673318
fromMonoListWithKey :: Distinct -> (Key -> a -> a -> a) -> [(Key,a)] -> IntMap a
32683319
fromMonoListWithKey distinct f = go

containers/src/Data/IntMap/Strict/Internal.hs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ insert !k !x t =
425425
-- > insertWith (++) 5 "xxx" (fromList [(5,"a"), (3,"b")]) == fromList [(3, "b"), (5, "xxxa")]
426426
-- > insertWith (++) 7 "xxx" (fromList [(5,"a"), (3,"b")]) == fromList [(3, "b"), (5, "a"), (7, "xxx")]
427427
-- > insertWith (++) 5 "xxx" empty == singleton 5 "xxx"
428+
--
429+
-- Also see the performance note on 'fromListWith'.
428430

429431
insertWith :: (a -> a -> a) -> Key -> a -> IntMap a -> IntMap a
430432
insertWith f k x t
@@ -443,6 +445,8 @@ insertWith f k x t
443445
--
444446
-- If the key exists in the map, this function is lazy in @value@ but strict
445447
-- in the result of @f@.
448+
--
449+
-- Also see the performance note on 'fromListWith'.
446450

447451
insertWithKey :: (Key -> a -> a -> a) -> Key -> a -> IntMap a -> IntMap a
448452
insertWithKey f !k x t =
@@ -470,6 +474,8 @@ insertWithKey f !k x t =
470474
-- > let insertLookup kx x t = insertLookupWithKey (\_ a _ -> a) kx x t
471475
-- > insertLookup 5 "x" (fromList [(5,"a"), (3,"b")]) == (Just "a", fromList [(3, "b"), (5, "x")])
472476
-- > insertLookup 7 "x" (fromList [(5,"a"), (3,"b")]) == (Nothing, fromList [(3, "b"), (5, "a"), (7, "x")])
477+
--
478+
-- Also see the performance note on 'fromListWith'.
473479

474480
insertLookupWithKey :: (Key -> a -> a -> a) -> Key -> a -> IntMap a -> (Maybe a, IntMap a)
475481
insertLookupWithKey f0 !k0 x0 t0 = toPair $ go f0 k0 x0 t0
@@ -660,6 +666,8 @@ unionsWith f ts
660666
-- | \(O(n+m)\). The union with a combining function.
661667
--
662668
-- > unionWith (++) (fromList [(5, "a"), (3, "b")]) (fromList [(5, "A"), (7, "C")]) == fromList [(3, "b"), (5, "aA"), (7, "C")]
669+
--
670+
-- Also see the performance note on 'fromListWith'.
663671

664672
unionWith :: (a -> a -> a) -> IntMap a -> IntMap a -> IntMap a
665673
unionWith f m1 m2
@@ -669,6 +677,8 @@ unionWith f m1 m2
669677
--
670678
-- > let f key left_value right_value = (show key) ++ ":" ++ left_value ++ "|" ++ right_value
671679
-- > unionWithKey f (fromList [(5, "a"), (3, "b")]) (fromList [(5, "A"), (7, "C")]) == fromList [(3, "b"), (5, "5:a|A"), (7, "C")]
680+
--
681+
-- Also see the performance note on 'fromListWith'.
672682

673683
unionWithKey :: (Key -> a -> a -> a) -> IntMap a -> IntMap a -> IntMap a
674684
unionWithKey f m1 m2
@@ -986,6 +996,8 @@ mapAccumRWithKey f0 a0 t0 = toPair $ go f0 a0 t0
986996
--
987997
-- > mapKeysWith (++) (\ _ -> 1) (fromList [(1,"b"), (2,"a"), (3,"d"), (4,"c")]) == singleton 1 "cdab"
988998
-- > mapKeysWith (++) (\ _ -> 3) (fromList [(1,"b"), (2,"a"), (3,"d"), (4,"c")]) == singleton 3 "cdab"
999+
--
1000+
-- Also see the performance note on 'fromListWith'.
9891001

9901002
mapKeysWith :: (a -> a -> a) -> (Key->Key) -> IntMap a -> IntMap a
9911003
mapKeysWith c f = fromListWith c . foldrWithKey (\k x xs -> (f k, x) : xs) []
@@ -1095,10 +1107,41 @@ fromList xs
10951107
where
10961108
ins t (k,x) = insert k x t
10971109

1098-
-- | \(O(n \min(n,W))\). Create a map from a list of key\/value pairs with a combining function. See also 'fromAscListWith'.
1110+
-- | \(O(n \min(n,W))\). Build a map from a list of key\/value pairs with a combining function. See also 'fromAscListWith'.
10991111
--
1100-
-- > fromListWith (++) [(5,"a"), (5,"b"), (3,"b"), (3,"a"), (5,"a")] == fromList [(3, "ab"), (5, "aba")]
1112+
-- > fromListWith (++) [(5,"a"), (5,"b"), (3,"x"), (5,"c")] == fromList [(3, "x"), (5, "cba")]
11011113
-- > fromListWith (++) [] == empty
1114+
--
1115+
-- Note the reverse ordering of @"cba"@ in the example.
1116+
--
1117+
-- The symmetric combining function @f@ is applied in a left-fold over the list, as @f new old@.
1118+
--
1119+
-- === Performance
1120+
--
1121+
-- You should ensure that the given @f@ is fast with this order of arguments.
1122+
--
1123+
-- Symmetric functions may be slow in one order, and fast in another.
1124+
-- For the common case of collecting values of matching keys in a list, as above:
1125+
--
1126+
-- The complexity of @(++) a b@ is \(O(a)\), so it is fast when given a short list as its first argument.
1127+
-- Thus:
1128+
--
1129+
-- > fromListWith (++) (replicate 1000000 (3, "x")) -- O(n), fast
1130+
-- > fromListWith (flip (++)) (replicate 1000000 (3, "x")) -- O(n²), extremely slow
1131+
--
1132+
-- because they evaluate as, respectively:
1133+
--
1134+
-- > fromList [(3, "x" ++ ("x" ++ "xxxxx..xxxxx"))] -- O(n)
1135+
-- > fromList [(3, ("xxxxx..xxxxx" ++ "x") ++ "x")] -- O(n²)
1136+
--
1137+
-- Thus, to get good performance with an operation like @(++)@ while also preserving
1138+
-- the same order as in the input list, reverse the input:
1139+
--
1140+
-- > fromListWith (++) (reverse [(5,"a"), (5,"b"), (5,"c")]) == fromList [(5, "abc")]
1141+
--
1142+
-- and it is always fast to combine singleton-list values @[v]@ with @fromListWith (++)@, as in:
1143+
--
1144+
-- > fromListWith (++) $ reverse $ map (\(k, v) -> (k, [v])) someListOfTuples
11021145

11031146
fromListWith :: (a -> a -> a) -> [(Key,a)] -> IntMap a
11041147
fromListWith f xs
@@ -1109,6 +1152,8 @@ fromListWith f xs
11091152
-- > let f key new_value old_value = show key ++ ":" ++ new_value ++ "|" ++ old_value
11101153
-- > fromListWithKey f [(5,"a"), (5,"b"), (3,"b"), (3,"a"), (5,"c")] == fromList [(3, "3:a|b"), (5, "5:c|5:b|a")]
11111154
-- > fromListWithKey f [] == empty
1155+
--
1156+
-- Also see the performance note on 'fromListWith'.
11121157

11131158
fromListWithKey :: (Key -> a -> a -> a) -> [(Key,a)] -> IntMap a
11141159
fromListWithKey f xs
@@ -1131,6 +1176,8 @@ fromAscList = fromMonoListWithKey Nondistinct (\_ x _ -> x)
11311176
-- /The precondition (input list is ascending) is not checked./
11321177
--
11331178
-- > fromAscListWith (++) [(3,"b"), (5,"a"), (5,"b")] == fromList [(3, "b"), (5, "ba")]
1179+
--
1180+
-- Also see the performance note on 'fromListWith'.
11341181

11351182
fromAscListWith :: (a -> a -> a) -> [(Key,a)] -> IntMap a
11361183
fromAscListWith f = fromMonoListWithKey Nondistinct (\_ x y -> f x y)
@@ -1141,6 +1188,8 @@ fromAscListWith f = fromMonoListWithKey Nondistinct (\_ x y -> f x y)
11411188
-- /The precondition (input list is ascending) is not checked./
11421189
--
11431190
-- > fromAscListWith (++) [(3,"b"), (5,"a"), (5,"b")] == fromList [(3, "b"), (5, "ba")]
1191+
--
1192+
-- Also see the performance note on 'fromListWith'.
11441193

11451194
fromAscListWithKey :: (Key -> a -> a -> a) -> [(Key,a)] -> IntMap a
11461195
fromAscListWithKey f = fromMonoListWithKey Nondistinct f
@@ -1162,6 +1211,8 @@ fromDistinctAscList = fromMonoListWithKey Distinct (\_ x _ -> x)
11621211
-- The precise conditions under which this function works are subtle:
11631212
-- For any branch mask, keys with the same prefix w.r.t. the branch
11641213
-- mask must occur consecutively in the list.
1214+
--
1215+
-- Also see the performance note on 'fromListWith'.
11651216

11661217
fromMonoListWithKey :: Distinct -> (Key -> a -> a -> a) -> [(Key,a)] -> IntMap a
11671218
fromMonoListWithKey distinct f = go

0 commit comments

Comments
 (0)