From 9d9687f0dd3afae4c30169f27dcfdca7a214dd36 Mon Sep 17 00:00:00 2001 From: Matt Zanchelli Date: Tue, 21 Dec 2021 10:55:38 -0500 Subject: [PATCH 1/7] Update `XCTAssertUnorderedEqualSequences` documentation --- Tests/SwiftAlgorithmsTests/TestUtilities.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/SwiftAlgorithmsTests/TestUtilities.swift b/Tests/SwiftAlgorithmsTests/TestUtilities.swift index 68870bba..24c57955 100644 --- a/Tests/SwiftAlgorithmsTests/TestUtilities.swift +++ b/Tests/SwiftAlgorithmsTests/TestUtilities.swift @@ -84,7 +84,8 @@ func XCTAssertEqualSequences( message(), file: file, line: line) } -// Two sequences contains exactly the same element but not necessarily in the same order. +/// Asserts two sequences contain exactly the same elements but not necessarily +/// in the same order. func XCTAssertUnorderedEqualSequences( _ expression1: @autoclosure () throws -> S1, _ expression2: @autoclosure () throws -> S2, From e1b2b0a9634bcd49fec48f7e84e51fd4521738cb Mon Sep 17 00:00:00 2001 From: Matt Zanchelli Date: Tue, 21 Dec 2021 11:26:26 -0500 Subject: [PATCH 2/7] Allow providing a custom message to `XCTAssertUnorderedEqualSequences` --- .../SwiftAlgorithmsTests/TestUtilities.swift | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Tests/SwiftAlgorithmsTests/TestUtilities.swift b/Tests/SwiftAlgorithmsTests/TestUtilities.swift index 24c57955..147ab4be 100644 --- a/Tests/SwiftAlgorithmsTests/TestUtilities.swift +++ b/Tests/SwiftAlgorithmsTests/TestUtilities.swift @@ -89,8 +89,15 @@ func XCTAssertEqualSequences( func XCTAssertUnorderedEqualSequences( _ expression1: @autoclosure () throws -> S1, _ expression2: @autoclosure () throws -> S2, + _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line ) rethrows where S1.Element: Equatable, S1.Element == S2.Element { + func fail(_ reason: String) { + let message = message() + XCTFail(message.isEmpty ? reason : "\(message) - \(reason)", + file: file, line: line) + } + var s1 = Array(try expression1()) var missing: [S1.Element] = [] for elt in try expression2() { @@ -101,15 +108,14 @@ func XCTAssertUnorderedEqualSequences( s1.remove(at: idx) } - XCTAssertTrue( - missing.isEmpty, "first sequence missing '\(missing)' elements from second sequence", - file: file, line: line - ) + if !missing.isEmpty { + fail("first sequence missing '\(missing)' elements from second sequence") + } + + if !s1.isEmpty { + fail("first sequence contains \(s1) missing from second sequence") + } - XCTAssertTrue( - s1.isEmpty, "first sequence contains \(s1) missing from second sequence", - file: file, line: line - ) } func XCTAssertEqualSequences( From 57953247f2ec68f365885d02688fbd308b753eb5 Mon Sep 17 00:00:00 2001 From: Matt Zanchelli Date: Tue, 21 Dec 2021 11:34:32 -0500 Subject: [PATCH 3/7] Add documentation on algorithmic complexity --- Tests/SwiftAlgorithmsTests/TestUtilities.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/SwiftAlgorithmsTests/TestUtilities.swift b/Tests/SwiftAlgorithmsTests/TestUtilities.swift index 147ab4be..68edf938 100644 --- a/Tests/SwiftAlgorithmsTests/TestUtilities.swift +++ b/Tests/SwiftAlgorithmsTests/TestUtilities.swift @@ -86,6 +86,8 @@ func XCTAssertEqualSequences( /// Asserts two sequences contain exactly the same elements but not necessarily /// in the same order. +/// - Complexity: O(*n* * *m*) where *n* is the number of elements in the first +/// sequence and *m* is the number of elements in the second sequence func XCTAssertUnorderedEqualSequences( _ expression1: @autoclosure () throws -> S1, _ expression2: @autoclosure () throws -> S2, From 7517cd25a2cd8cfb83011a62fe23f4f0708dc177 Mon Sep 17 00:00:00 2001 From: Matt Zanchelli Date: Tue, 21 Dec 2021 11:35:00 -0500 Subject: [PATCH 4/7] Add an algorithmically faster variant of `XCTAssertUnorderedEqualSequences` that works on elements that are `Hashable` --- .../SwiftAlgorithmsTests/TestUtilities.swift | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Tests/SwiftAlgorithmsTests/TestUtilities.swift b/Tests/SwiftAlgorithmsTests/TestUtilities.swift index 68edf938..9a26617c 100644 --- a/Tests/SwiftAlgorithmsTests/TestUtilities.swift +++ b/Tests/SwiftAlgorithmsTests/TestUtilities.swift @@ -117,7 +117,52 @@ func XCTAssertUnorderedEqualSequences( if !s1.isEmpty { fail("first sequence contains \(s1) missing from second sequence") } +} +/// Asserts two sequences contain exactly the same elements but not necessarily +/// in the same order. +/// - Complexity: O(*n* + *m*) where *n* is the number of elements in the first +/// sequence and *m* is the number of elements in the second sequence +func XCTAssertUnorderedEqualSequences( + _ expression1: @autoclosure () throws -> S1, + _ expression2: @autoclosure () throws -> S2, + _ message: @autoclosure () -> String = "", + file: StaticString = #file, line: UInt = #line +) rethrows where S1.Element: Hashable, S1.Element == S2.Element { + func fail(_ reason: String) { + let message = message() + XCTFail(message.isEmpty ? reason : "\(message) - \(reason)", + file: file, line: line) + } + + // Map the elements of the first sequence to the number of times it appeared + // in the sequence + var s1 = [S1.Element: Int](try expression1().lazy.map({ + ($0, 1) + }), uniquingKeysWith: { + $0 + $1 + }) + + var missing: [S1.Element] = [] + for elt in try expression2() { + if let count = s1[elt] { + if count > 1 { + s1[elt] = (count - 1) + } else { + s1[elt] = nil + } + } else { + missing.append(elt) + } + } + + if !missing.isEmpty { + fail("first sequence missing '\(missing)' elements from second sequence") + } + + if !s1.isEmpty { + fail("second sequence missing one or more occurrences of '\(s1.keys)' elements from first sequence") + } } func XCTAssertEqualSequences( From a8c33d575793b14b6657c97a7babe616b51248f1 Mon Sep 17 00:00:00 2001 From: Matt Zanchelli Date: Wed, 22 Dec 2021 16:52:24 -0500 Subject: [PATCH 5/7] Improve performance when removing an element from the middle of the array Co-authored-by: Luciano Almeida --- Tests/SwiftAlgorithmsTests/TestUtilities.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Tests/SwiftAlgorithmsTests/TestUtilities.swift b/Tests/SwiftAlgorithmsTests/TestUtilities.swift index 9a26617c..17880aa3 100644 --- a/Tests/SwiftAlgorithmsTests/TestUtilities.swift +++ b/Tests/SwiftAlgorithmsTests/TestUtilities.swift @@ -107,7 +107,14 @@ func XCTAssertUnorderedEqualSequences( missing.append(elt) continue } - s1.remove(at: idx) + // Since ordering does not matter, this is a small + // optmization that avoid removing a index in the middle + // of the array and having to slide all elements. + let lastIdx = s1.index(before: s1.endIndex) + if lastIdx != idx { + s1.swapAt(idx, lastIdx) + } + s1.remove(at: lastIdx) } if !missing.isEmpty { From e6b7cc6fde09259a99cb0eb69fee816adcc2dbfa Mon Sep 17 00:00:00 2001 From: Matt Zanchelli Date: Wed, 22 Dec 2021 16:54:08 -0500 Subject: [PATCH 6/7] Update comment --- Tests/SwiftAlgorithmsTests/TestUtilities.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/SwiftAlgorithmsTests/TestUtilities.swift b/Tests/SwiftAlgorithmsTests/TestUtilities.swift index 17880aa3..ad56f08b 100644 --- a/Tests/SwiftAlgorithmsTests/TestUtilities.swift +++ b/Tests/SwiftAlgorithmsTests/TestUtilities.swift @@ -107,9 +107,9 @@ func XCTAssertUnorderedEqualSequences( missing.append(elt) continue } - // Since ordering does not matter, this is a small - // optmization that avoid removing a index in the middle - // of the array and having to slide all elements. + // Since ordering does not matter, avoid the performance costs of removing + // an element at an index in the middle of the array, which requires + // shifting all subsequent elements down. let lastIdx = s1.index(before: s1.endIndex) if lastIdx != idx { s1.swapAt(idx, lastIdx) From b68955da0e5ab4707513223e5888da641e8571e3 Mon Sep 17 00:00:00 2001 From: Matt Zanchelli Date: Wed, 22 Dec 2021 16:54:19 -0500 Subject: [PATCH 7/7] Avoid redundant index comparison --- Tests/SwiftAlgorithmsTests/TestUtilities.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tests/SwiftAlgorithmsTests/TestUtilities.swift b/Tests/SwiftAlgorithmsTests/TestUtilities.swift index ad56f08b..0e2ba6d5 100644 --- a/Tests/SwiftAlgorithmsTests/TestUtilities.swift +++ b/Tests/SwiftAlgorithmsTests/TestUtilities.swift @@ -111,9 +111,7 @@ func XCTAssertUnorderedEqualSequences( // an element at an index in the middle of the array, which requires // shifting all subsequent elements down. let lastIdx = s1.index(before: s1.endIndex) - if lastIdx != idx { - s1.swapAt(idx, lastIdx) - } + s1.swapAt(idx, lastIdx) s1.remove(at: lastIdx) }