From 7148ba951053888cf0e852d9912c6be55c9c5b3b Mon Sep 17 00:00:00 2001 From: Marcelo Glasberg <13332110+marcglasberg@users.noreply.github.com> Date: Wed, 29 May 2024 20:28:54 -0300 Subject: [PATCH] Optimized IMap.update. --- CHANGELOG.md | 4 +++ lib/src/imap/imap.dart | 59 ++++++++++++++++++++++++++-------------- pubspec.yaml | 2 +- test/imap/imap_test.dart | 30 ++++++++++++++++++++ 4 files changed, 74 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 663f99d..2f909f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 10.2.4 + +* Optimized `IMap.update()`. + ## 10.2.3 * Improved `IList.zip()` generic typing. diff --git a/lib/src/imap/imap.dart b/lib/src/imap/imap.dart index 180b868..56164c9 100644 --- a/lib/src/imap/imap.dart +++ b/lib/src/imap/imap.dart @@ -1226,19 +1226,26 @@ abstract class IMap // 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 update( @@ -1248,34 +1255,46 @@ abstract class IMap // ignore: must_be_immutable V Function()? ifAbsent, Output? previousValue, }) { - // Contains key. + // 1. If the key is present: if (containsKey(key)) { final Map 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 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; } } diff --git a/pubspec.yaml b/pubspec.yaml index 0d84a0a..0cae42d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 diff --git a/test/imap/imap_test.dart b/test/imap/imap_test.dart index eb14ae7..2f43cee 100644 --- a/test/imap/imap_test.dart +++ b/test/imap/imap_test.dart @@ -1798,6 +1798,19 @@ void main() { expect(nullableMap.get('foo'), 1); }); + test("update returns same instance", () { + // 1) Existent key + IMap scores = {"Bob": Age(36), "Mary": Age(18)}.lock; + + // Update with same instance (identical). + IMap updatedScores1 = scores.update("Bob", (Age age) => age); + expect(identical(scores, updatedScores1), isTrue); + + // Update with equal instance (equals). + IMap updatedScores2 = scores.update("Bob", (Age age) => Age(age.value)); + expect(identical(scores, updatedScores2), isFalse); + }); + test("updateAll", () { final IMap scores = {"Bob": 36, "Joe": 100}.lock; final IMap updatedScores = @@ -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; +}