Skip to content

[OrderedCollections] Add OrderedDictionary.replaceElement(at:withKey:…#616

Open
inju2403 wants to merge 1 commit intoapple:mainfrom
inju2403:feature/ordered-dictionary-replace-element-at-index
Open

[OrderedCollections] Add OrderedDictionary.replaceElement(at:withKey:…#616
inju2403 wants to merge 1 commit intoapple:mainfrom
inju2403:feature/ordered-dictionary-replace-element-at-index

Conversation

@inju2403
Copy link
Copy Markdown

@inju2403 inju2403 commented Apr 3, 2026

Summary

Add replaceElement(at:withKey:value:) to OrderedDictionary and OrderedDictionary.Elements.

This method replaces the key-value pair at a given index with a new pair in expected amortized O(1) time.

var dict: OrderedDictionary = ["a": 1, "b": 2, "c": 3]
let old = dict.replaceElement(at: 1, withKey: "d", value: 4)
// old == (key: "b", value: 2)
// dict is now ["a": 1, "d": 4, "c": 3]

Motivation

OrderedDictionary provides O(1) primitives for updating a value by key (updateValue(_:forKey:)), replacing a value at an index (values[index]), and swapping elements (swapAt). However, there is no single operation to replace a key-value pair at a given index when the new key has a different hash than the old one.

Existing approach Issue
dict[newKey] = value Appends to the end; does not replace at the target index
remove(at:) + reinsertion Correct, but O(count) due to element shifting
updateValue(_:forKey:) Updates the value for an existing key; cannot change the key itself

Callers can compose appendswapAtremoveLast to work around this, but the pattern is non-obvious and requires careful duplicate-key validation and bounds checking that is easy to omit. replaceElement(at:withKey:value:) fills this gap as a first-class operation, consistent with how swapAt already exposes an index-based mutation primitive.

Note: If the caller only needs to update the value for the same key at a known index, values[index] = newValue provides a direct O(1) alternative.

Detailed design

Signature:

@discardableResult
public mutating func replaceElement(
  at index: Int,
  withKey key: Key,
  value: Value
) -> Element
  • Added to both OrderedDictionary and OrderedDictionary.Elements, matching the existing pattern for index-based mutations.
  • Returns the replaced key-value pair (@discardableResult).
  • Traps if index is out of bounds or key already exists.
  • Complexity: Expected amortized O(1), if Key implements high-quality hashing.

Implementation: Appends the new pair, swaps it into the target position, and removes the old element from the end — each step is O(1).

Testing

Tests added in both OrderedDictionary Tests.swift and OrderedDictionary+Elements Tests.swift, covering:

  • Sizes 1 through 19 with every valid index position
  • Both unique and shared (copy-on-write) storage via withHiddenCopies
  • Lifetime tracking to verify no memory leaks
  • Return value correctness (old key and value)
  • Forward lookup (dict[newKey] == newValue)
  • Reverse lookup (dict[oldKey] == nil)
  • Internal invariants via _checkInvariants()

Checklist

  • I've read the Contribution Guidelines
  • My contributions are licensed under the Swift license.
  • I've followed the coding style of the rest of the project.
  • I've added tests covering all new code paths my change adds to the project (if appropriate).
  • I've added benchmarks covering new functionality (if appropriate).
  • I've verified that my change does not break any existing tests or introduce unexplained benchmark regressions.
  • I've updated the documentation if necessary.

@inju2403 inju2403 requested a review from lorentey as a code owner April 3, 2026 08:18
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