Skip to content

Use foreachEntry and avoid repeated Map.updated in invert - #84

Open
cheeseng wants to merge 1 commit into
dotty-staging:stdlib/map-invertfrom
artimahub:stdlib/map-invert-artima-cs
Open

Use foreachEntry and avoid repeated Map.updated in invert#84
cheeseng wants to merge 1 commit into
dotty-staging:stdlib/map-invertfrom
artimahub:stdlib/map-invert-artima-cs

Conversation

@cheeseng

Copy link
Copy Markdown

invert had two separate inefficiencies:

  1. The first loop traversed this via a manual
    val it = iterator; while (it.hasNext) { val kv = it.next(); ... },
    destructuring kv._1/kv._2 by hand. Switched to foreachEntry, which
    for Map/HashMap-backed types walks the internal table directly and
    invokes the callback with key and value as separate arguments,
    avoiding the tuple allocation/unpacking the manual iterator version
    paid for on every entry.

  2. The second loop rebuilt the result via
    result = result.updated(v, bldr.result()) inside m.foreachEntry.
    Each .updated call on an immutable.Map reallocates trie nodes along
    the path to the changed key and discards the previous map version
    as garbage — the same anti-pattern already fixed in groupFlatMap.
    Replaced it with a single immutable.Map.newBuilder, populated via
    the same foreachEntry pass and materialized once via b.result().

No sizeHint added to either builder: the number of distinct inverted
keys, and how source entries distribute across them, is
data-dependent and unknowable before the first pass completes — a
hint sized against this would risk over-allocating whenever many
keys invert to the same value, which is a common case for this method.

No behavior changes.

The first loop consumed `this` (a Map) via a manual iterator/while
loop, destructuring each (K, V) tuple by hand. Switch to
foreachEntry, which for Map/HashMap-backed types walks the internal
table directly and passes key/value as separate arguments — no tuple
is built or unpacked per entry.

The second loop rebuilt the result via repeated result.updated(...)
calls on an immutable.Map, which reallocates trie nodes on every call
and discards the previous version each time. Replace it with a single
immutable.Map.newBuilder, staged once via the existing foreachEntry
pass over m and materialized once via b.result() — same fix already
applied in groupFlatMap.

No sizeHint added to m or b: the number of distinct inverted keys and
how the original entries distribute across them is data-dependent and
unknown before the first pass, so no reliable size estimate exists in
advance.

No behavior changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant