Skip to content

Commit

Permalink
Optimized IMap.update.
Browse files Browse the repository at this point in the history
  • Loading branch information
marcglasberg committed May 29, 2024
1 parent 81e3455 commit 7148ba9
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 21 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 10.2.4

* Optimized `IMap.update()`.

## 10.2.3

* Improved `IList.zip()` generic typing.
Expand Down
59 changes: 39 additions & 20 deletions lib/src/imap/imap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1226,19 +1226,26 @@ abstract class IMap<K, V> // ignore: must_be_immutable

/// Updates the value for the provided [key].
///
/// If the key is present, invokes [update] with the current value and stores
/// the new value in the map. However, if [ifRemove] is provided, the updated value will
/// first be tested with it and, if [ifRemove] returns true, the value will be
/// removed from the map, instead of updated.
///
/// If the key is not present and [ifAbsent] is provided, calls [ifAbsent]
/// and adds the key with the returned value to the map.
/// If the key is not present and [ifAbsent] is not provided, return the original map
/// without modification. Note: If you want [ifAbsent] to throw an error, pass it like
/// 1. If the key is present:
///
/// Invokes [update] with the current value and stores the new value in the
/// map. However, if [ifRemove] is provided, the updated value will first
/// be tested with it and, if [ifRemove] returns true, the value will be
/// removed from the map, instead of updated. Note: If [update] returns
/// the same INSTANCE as the current value, the original map instance will
/// be returned, unchanged.
///
/// 2. If the key is NOT present:
///
/// If [ifAbsent] is provided, calls [ifAbsent] and adds the key with the
/// returned value to the map. If the key is not present and [ifAbsent] is
/// not provided, return the original map instance, without modification.
/// Note: If you want [ifAbsent] to throw an error, pass it like
/// this: `ifAbsent: () => throw ArgumentError();`.
///
/// If you want to get the original value before the update, you can provide the
/// [previousValue] parameter.
/// If you want to get the original value before the update/removal,
/// you can provide the mutable [previousValue] parameter, which is
/// of type [Output].
///
@useResult
IMap<K, V> update(
Expand All @@ -1248,34 +1255,46 @@ abstract class IMap<K, V> // ignore: must_be_immutable
V Function()? ifAbsent,
Output<V>? previousValue,
}) {
// Contains key.
// 1. If the key is present:
if (containsKey(key)) {
final Map<K, V> map = unlock;

final originalValue = map[key] as V;
final updatedValue = update(originalValue);

previousValue?.save(originalValue);

// 1.1 Value removed
if (ifRemove != null && ifRemove(key, updatedValue)) {
map.remove(key);
} else {
map[key] = updatedValue;
return IMap._unsafeFromMap(map, config: config);
}
// 1.2 Value updated
else {
map[key] = updatedValue;

if (previousValue != null) previousValue.save(originalValue);
return IMap._unsafeFromMap(map, config: config);
return identical(updatedValue, originalValue)
? this
: IMap._unsafeFromMap(map, config: config);
}
}
//
// Does not contain key.
// 2. If the key is NOT present:
else {
previousValue?.save(null);

// 2.1 IfAbsent is provided
if (ifAbsent != null) {
final updatedValue = ifAbsent();
if (previousValue != null) previousValue.save(null);
final Map<K, V> map = ListMap.fromEntries(
entries.followedBy([MapEntry(key, updatedValue)]),
sort: config.sort,
);
return IMap._unsafeFromMap(map, config: config);
} else {
if (previousValue != null) previousValue.save(null);
}
//
// 2.2 IfAbsent NOT provided
else {
return this;
}
}
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: fast_immutable_collections
description: Immutable lists, sets, maps, and multimaps, which are as fast as their native mutable counterparts. Extension methods and comparators for native Dart collections.
version: 10.2.3
version: 10.2.4
homepage: https://github.com/marcglasberg/fast_immutable_collections
topics:
- collections
Expand Down
30 changes: 30 additions & 0 deletions test/imap/imap_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1798,6 +1798,19 @@ void main() {
expect(nullableMap.get('foo'), 1);
});

test("update returns same instance", () {
// 1) Existent key
IMap<String, Age> scores = {"Bob": Age(36), "Mary": Age(18)}.lock;

// Update with same instance (identical).
IMap<String, Age> updatedScores1 = scores.update("Bob", (Age age) => age);
expect(identical(scores, updatedScores1), isTrue);

// Update with equal instance (equals).
IMap<String, Age> updatedScores2 = scores.update("Bob", (Age age) => Age(age.value));
expect(identical(scores, updatedScores2), isFalse);
});

test("updateAll", () {
final IMap<String, int> scores = {"Bob": 36, "Joe": 100}.lock;
final IMap<String, int?> updatedScores =
Expand All @@ -1819,3 +1832,20 @@ void main() {
expect(() => IMap.flushFactor = -100, throwsStateError);
});
}

class Age {
final int value;

Age(this.value);

@override
String toString() => 'Age{value: $value}';

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Age && runtimeType == other.runtimeType && value == other.value;

@override
int get hashCode => value.hashCode;
}

0 comments on commit 7148ba9

Please sign in to comment.