-
Notifications
You must be signed in to change notification settings - Fork 183
Description
Currently lookup is defined to fail early by checking if the prefix mask agrees with the key:
lookup :: Key -> IntMap a -> Maybe a
lookup !k = go
where
go (Bin p m l r) | nomatch k p m = Nothing
| zero k m = go l
| otherwise = go r
go (Tip kx x) | k == kx = Just x
| otherwise = Nothing
go Nil = Nothing
In the original paper they note that if we expect our lookups to be successful then we should instead prioritize testing if the kth bit is zero and failing only when we reach leaves. If I understand correctly, then this would just be the following code:
lookupSucc :: Key -> IntMap a -> Maybe a
lookupSucc !k = go
where
go (Bin _p m l r)
| zero k m = go l
| otherwise = go r
go (Tip kx x) | k == kx = Just x
| otherwise = Nothing
go Nil = Nothing
Is that correct? It seems to pass the tests in any case. This can certainly lead to large speed ups when we know we are doing many successful lookups. For example, in the lookup benchmark we get the following (this is a best case scenario as the benchmark does not test a map with misses which seems like a bad design for a benchmark):
benchmarked lookup
time 172.2 μs (170.3 μs .. 174.1 μs)
0.998 R² (0.994 R² .. 1.000 R²)
mean 176.6 μs (175.3 μs .. 181.2 μs)
std dev 7.148 μs (2.786 μs .. 15.37 μs)
benchmarked lookupSucc
time 153.0 μs (151.6 μs .. 154.0 μs)
1.000 R² (1.000 R² .. 1.000 R²)
mean 152.2 μs (151.7 μs .. 152.7 μs)
std dev 1.783 μs (1.306 μs .. 2.362 μs)
Even if this is not quite the correct implementation, containers should probably offer this sort of function to allow users to tune their code to their needs as both variants are worthwhile.