Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mapKeys can be more efficient #1027

Open
meooow25 opened this issue Aug 15, 2024 · 1 comment
Open

mapKeys can be more efficient #1027

meooow25 opened this issue Aug 15, 2024 · 1 comment

Comments

@meooow25
Copy link
Contributor

mapKeys :: Ord k2 => (k1->k2) -> Map k1 a -> Map k2 a
mapKeys f = fromList . foldrWithKey (\k x xs -> (f k, x) : xs) []

mapKeysWith :: Ord k2 => (a -> a -> a) -> (k1->k2) -> Map k1 a -> Map k2 a
mapKeysWith c f = fromListWith c . foldrWithKey (\k x xs -> (f k, x) : xs) []

mapKeysWith :: Ord k2 => (a -> a -> a) -> (k1->k2) -> Map k1 a -> Map k2 a
mapKeysWith c f = fromListWith c . foldrWithKey (\k x xs -> (f k, x) : xs) []

mapKeys :: (Key->Key) -> IntMap a -> IntMap a
mapKeys f = fromList . foldrWithKey (\k x xs -> (f k, x) : xs) []

mapKeysWith :: (a -> a -> a) -> (Key->Key) -> IntMap a -> IntMap a
mapKeysWith c f
= fromListWith c . foldrWithKey (\k x xs -> (f k, x) : xs) []

mapKeysWith :: (a -> a -> a) -> (Key->Key) -> IntMap a -> IntMap a
mapKeysWith c f = fromListWith c . foldrWithKey (\k x xs -> (f k, x) : xs) []

It would be more efficient to skip the intermediate list and foldl' over the map accumulating into another map.

An exception here is Map.mapKeys, since Map.fromList is O(n) on an already sorted list.

This raises a related question of whether fromListWith should also have such an optimization.

@meooow25
Copy link
Contributor Author

meooow25 commented Aug 24, 2024

I think it is a good idea to make mapKeysWith (and by extension mapKeys) O(n) when given a non-decreasing mapping function. This will be useful if

  1. The author could have used mapKeysMonotonic but did not.
  2. Consecutive runs of values are merged. mapKeysMonotonic cannot be used here since it required a strictly increasing function.
  3. The map function is unknown, often applied on some type wrapping a Map, but it could perhaps be non-decreasing.

Hackage search can be used to examine some usages in the wild.


To do this, I am considering something like

data MapBuilder k a = ...

emptyBuilder :: MapBuilder k a
insertBuilder :: k -> a -> MapBuilder k a > MapBuilder a
insertWithBuilder :: (a -> a -> a) -> k -> a -> MapBuilder k a > MapBuilder a
finishBuilder :: MapBuilder a -> Map k a

implemented using a fromDistinctAscList-like Stack as long as elements are non-decreasing. Once an element is out-of-order, it switches to Map.insert. (This is just want fromList does today)


However, to apply a merging function to the last key and a new key, the fromDistinctAscList's MonoState won't work because it merges the stack too eagerly. I have an implementation that removes MonoState and just uses a Stack, keeping access to the last element. Just have to test how it holds up in benchmarks.
Edit: Made a PR, #1029

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant