Skip to content

Commit fed29e4

Browse files
committed
Allow mutating values in-place
1 parent 0252db1 commit fed29e4

2 files changed

Lines changed: 64 additions & 2 deletions

File tree

Sources/KeyedArray/KeyedArray.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,49 @@ public struct KeyedArray<Key: Hashable, Element: Equatable> {
6161
}
6262
}
6363

64+
extension KeyedArray {
65+
/// Mutates an existing value without changing its position.
66+
///
67+
/// The mutation function will not be called if no value can be found for `key`.
68+
///
69+
/// - Complexity: O(n)
70+
///
71+
/// - Warning: The new element **must** produce the same key as the old value.
72+
///
73+
/// - Parameters:
74+
/// - key: The key whose value to look up and mutate.
75+
/// - function: A function that mutates the existing value.
76+
public mutating func mutateValueInPlace(for key: Key, using function: (inout Element)->Void) {
77+
guard var value = self[key], let index = self.array.firstIndex(of: value) else { return }
78+
function(&value)
79+
80+
precondition(self.keyExtractor(value) == key, "New value's key must match old value's key")
81+
82+
self.array[index] = value
83+
self.dictionary[key] = value
84+
}
85+
86+
/// Mutates an existing value without changing its position.
87+
///
88+
/// - Complexity: O(1)
89+
///
90+
/// - Warning: The new element **must** produce the same key as the old value.
91+
///
92+
/// - Parameters:
93+
/// - index: The index whose value to mutate.
94+
/// - function: A function that mutates the existing value.
95+
public mutating func mutateValueInPlace(for index: Int, using function: (inout Element)->Void) {
96+
var value = self[index]
97+
let oldKey = self.keyExtractor(value)
98+
function(&value)
99+
100+
precondition(self.keyExtractor(value) == oldKey, "New value's key must match old value's key")
101+
102+
self.array[index] = value
103+
self.dictionary[oldKey] = value
104+
}
105+
}
106+
64107
extension KeyedArray where Element: Identifiable, Key == Element.ID {
65108
public init() {
66109
self.init(keyExtractor: \.id)

Tests/KeyedArrayTests/KeyedArrayTests.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import XCTest
22
import KeyedArray
33

44
final class KeyedArrayTests: XCTestCase {
5-
struct DemoVal: Equatable, Identifiable, ExpressibleByStringLiteral {
5+
struct DemoVal: Equatable, Identifiable, ExpressibleByStringLiteral, CustomStringConvertible {
66
let str: String
77
var id: Character { str.first! }
8+
var description: String { str }
89

910
init(_ str: String) { self.str = str }
1011
init(stringLiteral str: String) { self.str = str }
@@ -47,13 +48,31 @@ final class KeyedArrayTests: XCTestCase {
4748
XCTAssertEqual(ar.array, ["alpha"])
4849
}
4950

50-
func testReplaceByIndex() {
51+
func testReplaceByAppend() {
5152
ar.append("bravo")
5253

5354
XCTAssertEqual(ar.array, ["alpha", "bravo"])
5455
XCTAssertEqual(ar.dictionary, ["a": "alpha", "b": "bravo"])
5556
}
5657

58+
func testMutateValueInPlaceByKey() {
59+
ar.mutateValueInPlace(for: "a") { existing in
60+
existing = "alpine"
61+
}
62+
63+
XCTAssertEqual(ar.array, ["alpine", "beta"])
64+
XCTAssertEqual(ar.dictionary, ["a": "alpine", "b": "beta"])
65+
}
66+
67+
func testMutateValueInPlaceByIndex() {
68+
ar.mutateValueInPlace(for: 0) { existing in
69+
existing = "alpine"
70+
}
71+
72+
XCTAssertEqual(ar.array, ["alpine", "beta"])
73+
XCTAssertEqual(ar.dictionary, ["a": "alpine", "b": "beta"])
74+
}
75+
5776
func testArrayIsArray() {
5877
XCTAssertEqual(Array(ar.array), ar.array)
5978
}

0 commit comments

Comments
 (0)