From 0e4014c468d9d975cb27b027644d0100336cb682 Mon Sep 17 00:00:00 2001 From: Rick van Voorden Date: Sun, 28 Jan 2024 13:49:26 -0800 Subject: [PATCH 1/7] tree dictionary keys equality --- .../ShareableDictionaryBenchmarks.swift | 57 +++++++++++++++++++ .../TreeDictionary/TreeDictionary+Keys.swift | 19 +++++++ .../TreeDictionary.Keys Tests.swift | 19 +++++++ 3 files changed, 95 insertions(+) diff --git a/Benchmarks/Sources/Benchmarks/ShareableDictionaryBenchmarks.swift b/Benchmarks/Sources/Benchmarks/ShareableDictionaryBenchmarks.swift index 677bff27e..5643167d9 100644 --- a/Benchmarks/Sources/Benchmarks/ShareableDictionaryBenchmarks.swift +++ b/Benchmarks/Sources/Benchmarks/ShareableDictionaryBenchmarks.swift @@ -471,5 +471,62 @@ extension Benchmark { blackHole(d) } } + + self.add( + title: "TreeDictionary equality, unique", + input: [Int].self + ) { input in + let keysAndValues = input.map { ($0, 2 * $0) } + let left = TreeDictionary(uniqueKeysWithValues: keysAndValues) + let right = TreeDictionary(uniqueKeysWithValues: keysAndValues) + return { timer in + timer.measure { + precondition(left == right) + } + } + } + + self.add( + title: "TreeDictionary equality, shared", + input: [Int].self + ) { input in + let keysAndValues = input.map { ($0, 2 * $0) } + let left = TreeDictionary(uniqueKeysWithValues: keysAndValues) + let right = left + return { timer in + timer.measure { + precondition(left == right) + } + } + } + + self.add( + title: "TreeDictionary.Keys equality, unique", + input: [Int].self + ) { input in + let keysAndValues = input.map { ($0, 2 * $0) } + let left = TreeDictionary(uniqueKeysWithValues: keysAndValues) + let right = TreeDictionary(uniqueKeysWithValues: keysAndValues) + return { timer in + timer.measure { + precondition(left.keys == right.keys) + } + } + } + + self.add( + title: "TreeDictionary.Keys equality, shared", + input: [Int].self + ) { input in + let keysAndValues = input.map { ($0, 2 * $0) } + let left = TreeDictionary(uniqueKeysWithValues: keysAndValues) + let right = left + return { timer in + timer.measure { + precondition(left.keys == right.keys) + } + } + } + } } diff --git a/Sources/HashTreeCollections/TreeDictionary/TreeDictionary+Keys.swift b/Sources/HashTreeCollections/TreeDictionary/TreeDictionary+Keys.swift index a7e22ad1a..7804a258f 100644 --- a/Sources/HashTreeCollections/TreeDictionary/TreeDictionary+Keys.swift +++ b/Sources/HashTreeCollections/TreeDictionary/TreeDictionary+Keys.swift @@ -288,3 +288,22 @@ extension TreeDictionary.Keys { return d.keys } } + +extension TreeDictionary.Keys: Equatable { + /// Returns a Boolean value indicating whether two values are equal. + /// + /// Equality is the inverse of inequality. For any values `a` and `b`, + /// `a == b` implies that `a != b` is `false`. + /// + /// - Parameter lhs: A value to compare. + /// - Parameter rhs: Another value to compare. + /// + /// - Complexity: Generally O(`count`), as long as`Element` properly + /// implements hashing. That said, the implementation is careful to take + /// every available shortcut to reduce complexity, e.g. by skipping + /// comparing elements in shared subtrees. + @inlinable + public static func == (left: Self, right: Self) -> Bool { + left._base._root.isEqualSet(to: right._base._root, by: { _, _ in true }) + } +} diff --git a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift index 663cf8cd2..bb2de79a8 100644 --- a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift @@ -105,5 +105,24 @@ class TreeDictionaryKeysTests: CollectionTestCase { } } } + + func test_isEqual_exhaustive() { + withEverySubset("a", of: testItems) { a in + let x = TreeDictionary( + uniqueKeysWithValues: a.lazy.map { ($0, 2 * $0.identity) }) + let u = Set(a) + expectEqualSets(x.keys, u) + withEverySubset("b", of: testItems) { b in + let y = TreeDictionary( + uniqueKeysWithValues: b.lazy.map { ($0, -$0.identity - 1) }) + let v = Set(b) + + let reference = u == v + print(reference) + + expectEqual(x.keys == y.keys, reference) + } + } + } } From d846bb25b1b990b320efe2db436fce2220caef99 Mon Sep 17 00:00:00 2001 From: Rick van Voorden Date: Sun, 28 Jan 2024 14:15:17 -0800 Subject: [PATCH 2/7] edit equal keys check --- Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift index bb2de79a8..72fe7c912 100644 --- a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift @@ -116,6 +116,7 @@ class TreeDictionaryKeysTests: CollectionTestCase { let y = TreeDictionary( uniqueKeysWithValues: b.lazy.map { ($0, -$0.identity - 1) }) let v = Set(b) + expectEqualSets(y.keys, v) let reference = u == v print(reference) From 3abf60adaeae6dbebc338c387b50f15ae8848d3e Mon Sep 17 00:00:00 2001 From: Rick van Voorden Date: Tue, 6 Feb 2024 14:21:29 -0800 Subject: [PATCH 3/7] wip tree dictionary keys hashable --- .../TreeDictionary/TreeDictionary+Keys.swift | 18 +++++++++++++++++ .../TreeDictionary.Keys Tests.swift | 20 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/Sources/HashTreeCollections/TreeDictionary/TreeDictionary+Keys.swift b/Sources/HashTreeCollections/TreeDictionary/TreeDictionary+Keys.swift index 7804a258f..7228fb14c 100644 --- a/Sources/HashTreeCollections/TreeDictionary/TreeDictionary+Keys.swift +++ b/Sources/HashTreeCollections/TreeDictionary/TreeDictionary+Keys.swift @@ -307,3 +307,21 @@ extension TreeDictionary.Keys: Equatable { left._base._root.isEqualSet(to: right._base._root, by: { _, _ in true }) } } + +extension TreeDictionary.Keys: Hashable { + /// Hashes the essential components of this value by feeding them into the + /// given hasher. + /// + /// Complexity: O(`count`) + @inlinable + public func hash(into hasher: inout Hasher) { + let copy = hasher + let seed = copy.finalize() + + var hash = 0 + for member in self { + hash ^= member._rawHashValue(seed: seed) + } + hasher.combine(hash) + } +} diff --git a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift index 72fe7c912..155d4d0b6 100644 --- a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift @@ -125,5 +125,25 @@ class TreeDictionaryKeysTests: CollectionTestCase { } } } + + func test_Equatable_Hashable() { + let samples: [[TreeDictionary]] = [ + [[:], [:]], + [[1: 100], [1: 100]], + [[2: 200], [2: 200]], + [[3: 300], [3: 300]], + [[100: 1], [100: 1]], + [[1: 1], [1: 1]], + [[100: 100], [100: 100]], + [[1: 100, 2: 200], [2: 200, 1: 100]], + [[1: 100, 2: 200, 3: 300], + [1: 100, 3: 300, 2: 200], + [2: 200, 1: 100, 3: 300], + [2: 200, 3: 300, 1: 100], + [3: 300, 1: 100, 2: 200], + [3: 300, 2: 200, 1: 100]] + ] + checkHashable(equivalenceClasses: samples.map { $0.map { $0.keys }}) + } } From ff73ed71bb7ba4ad770647767c940906082671c2 Mon Sep 17 00:00:00 2001 From: Rick van Voorden Date: Tue, 6 Feb 2024 15:57:54 -0800 Subject: [PATCH 4/7] wip tree dictionary keys hashable --- .../TreeDictionary.Keys Tests.swift | 42 ++++++++++++++++++- .../OrderedDictionary+Elements Tests.swift | 2 + 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift index 155d4d0b6..ec6fcaf25 100644 --- a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift @@ -126,7 +126,7 @@ class TreeDictionaryKeysTests: CollectionTestCase { } } - func test_Equatable_Hashable() { + func test_Hashable_One() { let samples: [[TreeDictionary]] = [ [[:], [:]], [[1: 100], [1: 100]], @@ -145,5 +145,45 @@ class TreeDictionaryKeysTests: CollectionTestCase { ] checkHashable(equivalenceClasses: samples.map { $0.map { $0.keys }}) } + + func test_Hashable_Two() { + let classes: [[TreeSet]] = [ + [ + [] + ], + [ + ["a"] + ], + [ + ["b"] + ], + [ + ["c"] + ], + [ + ["d"] + ], + [ + ["e"] + ], + [ + ["a", "b"], ["b", "a"], + ], + [ + ["a", "d"], ["d", "a"], + ], + [ + ["a", "b", "c"], ["a", "c", "b"], + ["b", "a", "c"], ["b", "c", "a"], + ["c", "a", "b"], ["c", "b", "a"], + ], + [ + ["a", "d", "e"], ["a", "e", "d"], + ["d", "a", "e"], ["d", "e", "a"], + ["e", "a", "d"], ["e", "d", "a"], + ], + ] + checkHashable(equivalenceClasses: classes.map { $0.map { TreeDictionary(keys: $0, valueGenerator: { $0 }).keys }}) + } } diff --git a/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Elements Tests.swift b/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Elements Tests.swift index d8c1d3950..0ac59cc0a 100644 --- a/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Elements Tests.swift +++ b/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Elements Tests.swift @@ -149,6 +149,8 @@ class OrderedDictionaryElementsTests: CollectionTestCase { [[3: 300, 2: 200, 1: 100], [3: 300, 2: 200, 1: 100]], [[3: 300, 1: 100, 2: 200], [3: 300, 1: 100, 2: 200]] ] + let x = samples.map { $0.map { $0.elements }} + print(x) checkHashable(equivalenceClasses: samples.map { $0.map { $0.elements }}) } From ffa9e65ccc6c45747ea9ea95ffe0d980e3318db2 Mon Sep 17 00:00:00 2001 From: Rick van Voorden Date: Tue, 6 Feb 2024 15:58:31 -0800 Subject: [PATCH 5/7] wip tree dictionary keys hashable --- .../OrderedDictionary/OrderedDictionary+Elements Tests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Elements Tests.swift b/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Elements Tests.swift index 0ac59cc0a..d8c1d3950 100644 --- a/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Elements Tests.swift +++ b/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Elements Tests.swift @@ -149,8 +149,6 @@ class OrderedDictionaryElementsTests: CollectionTestCase { [[3: 300, 2: 200, 1: 100], [3: 300, 2: 200, 1: 100]], [[3: 300, 1: 100, 2: 200], [3: 300, 1: 100, 2: 200]] ] - let x = samples.map { $0.map { $0.elements }} - print(x) checkHashable(equivalenceClasses: samples.map { $0.map { $0.elements }}) } From a04721360b38af71f95f54a5fcf2c324fcb86b37 Mon Sep 17 00:00:00 2001 From: Rick van Voorden Date: Tue, 6 Feb 2024 16:25:33 -0800 Subject: [PATCH 6/7] tree dictionary keys hashable cleanup --- .../TreeDictionary.Keys Tests.swift | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift index ec6fcaf25..a867769f3 100644 --- a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift @@ -126,28 +126,8 @@ class TreeDictionaryKeysTests: CollectionTestCase { } } - func test_Hashable_One() { - let samples: [[TreeDictionary]] = [ - [[:], [:]], - [[1: 100], [1: 100]], - [[2: 200], [2: 200]], - [[3: 300], [3: 300]], - [[100: 1], [100: 1]], - [[1: 1], [1: 1]], - [[100: 100], [100: 100]], - [[1: 100, 2: 200], [2: 200, 1: 100]], - [[1: 100, 2: 200, 3: 300], - [1: 100, 3: 300, 2: 200], - [2: 200, 1: 100, 3: 300], - [2: 200, 3: 300, 1: 100], - [3: 300, 1: 100, 2: 200], - [3: 300, 2: 200, 1: 100]] - ] - checkHashable(equivalenceClasses: samples.map { $0.map { $0.keys }}) - } - - func test_Hashable_Two() { - let classes: [[TreeSet]] = [ + func test_Hashable() { + let strings: [[[String]]] = [ [ [] ], @@ -183,7 +163,8 @@ class TreeDictionaryKeysTests: CollectionTestCase { ["e", "a", "d"], ["e", "d", "a"], ], ] - checkHashable(equivalenceClasses: classes.map { $0.map { TreeDictionary(keys: $0, valueGenerator: { $0 }).keys }}) + let keys = strings.map { $0.map { TreeDictionary(uniqueKeysWithValues: $0.map { ($0, UUID().uuidString) }).keys }} + checkHashable(equivalenceClasses: keys) } } From c61e5e23c89b99d26aaaec6ec6c58bda1b502ebc Mon Sep 17 00:00:00 2001 From: Rick van Voorden Date: Tue, 6 Feb 2024 17:26:21 -0800 Subject: [PATCH 7/7] tree dictionary keys hashable cleanup --- .../TreeDictionary.Keys Tests.swift | 17 ++++++++++++++++- .../TreeSet Tests.swift | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift index a867769f3..06f33a65b 100644 --- a/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeDictionary.Keys Tests.swift @@ -146,6 +146,21 @@ class TreeDictionaryKeysTests: CollectionTestCase { [ ["e"] ], + [ + ["f"], ["f"], + ], + [ + ["g"], ["g"], + ], + [ + ["h"], ["h"], + ], + [ + ["i"], ["i"], + ], + [ + ["j"], ["j"], + ], [ ["a", "b"], ["b", "a"], ], @@ -163,7 +178,7 @@ class TreeDictionaryKeysTests: CollectionTestCase { ["e", "a", "d"], ["e", "d", "a"], ], ] - let keys = strings.map { $0.map { TreeDictionary(uniqueKeysWithValues: $0.map { ($0, UUID().uuidString) }).keys }} + let keys = strings.map { $0.map { TreeDictionary(uniqueKeysWithValues: $0.map { ($0, Int.random(in: 1...100)) }).keys }} checkHashable(equivalenceClasses: keys) } diff --git a/Tests/HashTreeCollectionsTests/TreeSet Tests.swift b/Tests/HashTreeCollectionsTests/TreeSet Tests.swift index aa3f675ee..e37857d5a 100644 --- a/Tests/HashTreeCollectionsTests/TreeSet Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeSet Tests.swift @@ -681,6 +681,21 @@ class TreeSetTests: CollectionTestCase { [ ["e"] ], + [ + ["f"], ["f"], + ], + [ + ["g"], ["g"], + ], + [ + ["h"], ["h"], + ], + [ + ["i"], ["i"], + ], + [ + ["j"], ["j"], + ], [ ["a", "b"], ["b", "a"], ],