From e8f7bf376aed38141b3643ad1c708b181d44bb83 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Fri, 27 Nov 2020 11:38:57 -0500 Subject: [PATCH 01/17] Add method to copy elements Add method to element-mutable collections that takes a sequence of the same element type to read its elements and assign them on top of the collection's current elements. The copying stops upon reaching the end of the shorter sequence. The method returns two items. The first is one index past the last destination element written over. The second is an iterator for the elements of the source sequence that were not used for copying. --- CHANGELOG.md | 9 +- Guides/Copy.md | 58 ++++++++++ README.md | 1 + Sources/Algorithms/Copy.swift | 48 +++++++++ Tests/SwiftAlgorithmsTests/CopyTests.swift | 118 +++++++++++++++++++++ 5 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 Guides/Copy.md create mode 100644 Sources/Algorithms/Copy.swift create mode 100644 Tests/SwiftAlgorithmsTests/CopyTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 84f0a457..7c3daee7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,14 @@ package updates, you can specify your package dependency using ## [Unreleased] -*No changes yet.* +### Additions + +- The `copy(from:)` method has been added, applying to types conforming to + `MutableCollection`. It takes a sequence with the same element type as its + only parameter, whose elements will be copied on top of the existing + elements. The return values are the past-the-end index in the receiver where + the copying ended and an iterator for the source sequence after the elements + that were copied. --- diff --git a/Guides/Copy.md b/Guides/Copy.md new file mode 100644 index 00000000..9b21a08e --- /dev/null +++ b/Guides/Copy.md @@ -0,0 +1,58 @@ +# Copy + +[[Source](../Sources/Algorithms/Copy.swift) | + [Tests](../Tests/SwiftAlgorithmsTests/CopyTests.swift)] + +Copy a sequence onto an element-mutable collection. + +```swift +var destination = [1, 2, 3, 4, 5] +let source = [6, 7, 8, 9, 10] +print(destination) // "[1, 2, 3, 4, 5] + +let (_, sourceSuffix) = destination.copy(from: source) +print(destination) // "[6, 7, 8, 9, 10]" +print(Array(IteratorSequence(sourceSuffix))) // "[]" +``` + +`copy(from:)` takes a source sequence and overlays its first *k* elements' +values over the first `k` elements of the receiver, where `k` is the smaller of +the two sequences' lengths. + +## Detailed Design + +A new method is added to element-mutable collections: + +```swift +extension MutableCollection { + mutating func copy(from source: S) + -> (copyEnd: Index, sourceTail: S.Iterator) where S.Element == Element +} +``` + +The methods return two values. The first member is the index of the receiver +defining the non-anchored endpoint of the elements that were actually written +over. The second member is for reading the suffix of the source that wasn't +actually used. + +### Complexity + +Calling these methods is O(_k_), where _k_ is the length of the shorter +sequence between the receiver and `source`. + +### Naming + +This method’s name matches the term of art used in other languages and +libraries. + +### Comparison with other languages + +**C++:** Has a [`copy`][C++Copy] function in the `algorithm` library that takes +a bounding pair of input iterators for the source and a single output iterator +for the destination, returning one-past the last output iterator written over. +The `copy_if` function does not have an analogue, since it can be simulated by +submitting the result from `filter(_:)` as the source. + + + +[C++Copy]: https://en.cppreference.com/w/cpp/algorithm/copy diff --git a/README.md b/README.md index 9033c90a..be474991 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`rotate(toStartAt:)`, `rotate(subrange:toStartAt:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Rotate.md): In-place rotation of elements. - [`stablePartition(by:)`, `stablePartition(subrange:by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Partition.md): A partition that preserves the relative order of the resulting prefix and suffix. +- [`copy(from:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. #### Combining collections diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift new file mode 100644 index 00000000..740ccb6f --- /dev/null +++ b/Sources/Algorithms/Copy.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Algorithms open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +// copy(from:) +//===----------------------------------------------------------------------===// + +extension MutableCollection { + /// Copies the elements from the given sequence on top of the elements of this + /// collection, until the shorter one is exhausted. + /// + /// If you want to limit how much of this collection can be overrun, call this + /// method on the limiting subsequence instead. + /// + /// - Parameters: + /// - source: The sequence to read the replacement values from. + /// - Returns: A two-member tuple where the first member is the index of the + /// first element of this collection that was not assigned a copy. It will + /// be `startIndex` if no copying was done and `endIndex` if every element + /// was written over. The second member is an iterator covering all the + /// elements of `source` that where not used as part of the copying. It + /// will be empty if every element was used. + /// - Postcondition: Let *k* be the element count of the shorter of `self` and + /// source. Then `prefix(k)` will be equivalent to `source.prefix(k)`, + /// while `dropFirst(k)` is unchanged. + /// + /// - Complexity: O(*n*), where *n* is the length of the shorter sequence + /// between `self` and `source`. + public mutating func copy( + from source: S + ) -> (copyEnd: Index, sourceTail: S.Iterator) where S.Element == Element { + var current = startIndex, iterator = source.makeIterator() + let end = endIndex + while current < end, let source = iterator.next() { + self[current] = source + formIndex(after: ¤t) + } + return (current, iterator) + } +} diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/CopyTests.swift new file mode 100644 index 00000000..794fcbba --- /dev/null +++ b/Tests/SwiftAlgorithmsTests/CopyTests.swift @@ -0,0 +1,118 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Algorithms open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +import Algorithms + +/// Unit tests for the `copy(from:)` method. +final class CopyTests: XCTestCase { + /// Test empty source and destination. + func testBothEmpty() { + var empty1 = EmptyCollection() + let empty2 = EmptyCollection() + XCTAssertEqualSequences(empty1, []) + + let result = empty1.copy(from: empty2) + XCTAssertEqual(result.copyEnd, empty1.startIndex) + XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) + XCTAssertEqualSequences(empty1, []) + } + + /// Test nonempty source and empty destination. + func testOnlyDestinationEmpty() { + var empty = EmptyCollection() + let single = CollectionOfOne(1.1) + XCTAssertEqualSequences(empty, []) + + let result = empty.copy(from: single) + XCTAssertEqual(result.copyEnd, empty.endIndex) + XCTAssertEqualSequences(IteratorSequence(result.sourceTail), [1.1]) + XCTAssertEqualSequences(empty, []) + } + + /// Test empty source and nonempty destination. + func testOnlySourceEmpty() { + var single = CollectionOfOne(2.2) + let empty = EmptyCollection() + XCTAssertEqualSequences(single, [2.2]) + + let result = single.copy(from: empty) + XCTAssertEqual(result.copyEnd, single.startIndex) + XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) + XCTAssertEqualSequences(single, [2.2]) + } + + /// Test two one-element collections. + func testTwoSingles() { + var destination = CollectionOfOne(3.3) + let source = CollectionOfOne(4.4) + XCTAssertEqualSequences(destination, [3.3]) + + let result = destination.copy(from: source) + XCTAssertEqual(result.copyEnd, destination.endIndex) + XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) + XCTAssertEqualSequences(destination, [4.4]) + } + + /// Test two equal-length multi-element collections. + func testTwoWithEqualLength() { + var destination = Array("ABCDE") + let source = "fghij" + XCTAssertEqualSequences(destination, "ABCDE") + + let result = destination.copy(from: source) + XCTAssertEqual(result.copyEnd, destination.endIndex) + XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) + XCTAssertEqualSequences(destination, "fghij") + } + + /// Test a source longer than a multi-element destination. + func testLongerDestination() { + var destination = Array(1...5) + let source = 10...100 + XCTAssertEqualSequences(destination, 1...5) + + let result = destination.copy(from: source) + XCTAssertEqual(result.copyEnd, destination.endIndex) + XCTAssertEqualSequences(IteratorSequence(result.sourceTail), 15...100) + XCTAssertEqualSequences(destination, 10..<15) + } + + /// Test a multi-element source shorter than the destination. + func testShorterDestination() { + var destination = Array("abcdefghijklm") + let source = "NOPQR" + XCTAssertEqualSequences(destination, "abcdefghijklm") + + let result = destination.copy(from: source) + XCTAssertEqual(result.copyEnd, destination.index(destination.startIndex, + offsetBy: source.count)) + XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) + XCTAssertEqualSequences(destination, "NOPQRfghijklm") + } + + /// Test copying over part of the destination. + func testPartial() { + var destination = Array("abcdefghijklm") + let source = "STUVWXYZ" + XCTAssertEqualSequences(destination, "abcdefghijklm") + + let result = destination[3..<7].copy(from: source) + XCTAssertEqual(result.copyEnd, 7) + XCTAssertEqualSequences(IteratorSequence(result.sourceTail), "WXYZ") + XCTAssertEqualSequences(destination, "abcSTUVhijklm") + + let result2 = destination[3..<7].copy(from: "12") + XCTAssertEqual(result2.copyEnd, 5) + XCTAssertEqualSequences(IteratorSequence(result2.sourceTail), []) + XCTAssertEqualSequences(destination, "abc12UVhijklm") + } +} From c007b8d6e2dc09026f942b61af0068a3918a9109 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Thu, 17 Dec 2020 06:41:21 -0500 Subject: [PATCH 02/17] Add another method to copy elements Add method to element-mutable collections that takes a collection of the same element type to read its elements and assign them on top of the receiver's current elements. The copying stops upon reaching the end of the shorter collection. The method returns two items. The first is one index past the last destination element written over. The second is one index past the last source element read from. --- CHANGELOG.md | 4 +- Guides/Copy.md | 9 +++- README.md | 2 +- Sources/Algorithms/Copy.swift | 37 ++++++++++++++++- Tests/SwiftAlgorithmsTests/CopyTests.swift | 48 +++++++++++++++++++++- 5 files changed, 93 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c3daee7..a90e68d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,9 @@ package updates, you can specify your package dependency using only parameter, whose elements will be copied on top of the existing elements. The return values are the past-the-end index in the receiver where the copying ended and an iterator for the source sequence after the elements - that were copied. + that were copied. The `copy(collection:)` method works like the previous + method, but uses a collection as the source, and expresses the unread suffix + for that source as an `Index` instead. --- diff --git a/Guides/Copy.md b/Guides/Copy.md index 9b21a08e..319d6c37 100644 --- a/Guides/Copy.md +++ b/Guides/Copy.md @@ -17,16 +17,21 @@ print(Array(IteratorSequence(sourceSuffix))) // "[]" `copy(from:)` takes a source sequence and overlays its first *k* elements' values over the first `k` elements of the receiver, where `k` is the smaller of -the two sequences' lengths. +the two sequences' lengths. The `copy(collection:)` variant uses a collection +for the source sequence. ## Detailed Design -A new method is added to element-mutable collections: +New methods are added to element-mutable collections: ```swift extension MutableCollection { mutating func copy(from source: S) -> (copyEnd: Index, sourceTail: S.Iterator) where S.Element == Element + + mutating func copy(collection: C) + -> (copyEnd: Index, sourceTailStart: C.Index) + where C : Collection, Self.Element == C.Element } ``` diff --git a/README.md b/README.md index be474991..8dcd04a8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`rotate(toStartAt:)`, `rotate(subrange:toStartAt:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Rotate.md): In-place rotation of elements. - [`stablePartition(by:)`, `stablePartition(subrange:by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Partition.md): A partition that preserves the relative order of the resulting prefix and suffix. -- [`copy(from:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. +- [`copy(from:)`, `copy(collection:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. #### Combining collections diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift index 740ccb6f..6eb1590d 100644 --- a/Sources/Algorithms/Copy.swift +++ b/Sources/Algorithms/Copy.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// -// copy(from:) +// copy(from:), copy(collection:) //===----------------------------------------------------------------------===// extension MutableCollection { @@ -29,7 +29,7 @@ extension MutableCollection { /// elements of `source` that where not used as part of the copying. It /// will be empty if every element was used. /// - Postcondition: Let *k* be the element count of the shorter of `self` and - /// source. Then `prefix(k)` will be equivalent to `source.prefix(k)`, + /// `source`. Then `prefix(k)` will be equivalent to `source.prefix(k)`, /// while `dropFirst(k)` is unchanged. /// /// - Complexity: O(*n*), where *n* is the length of the shorter sequence @@ -45,4 +45,37 @@ extension MutableCollection { } return (current, iterator) } + + /// Copies the elements from the given collection on top of the elements of + /// this collection, until the shorter one is exhausted. + /// + /// If you want to limit how much of this collection can be overrun, call this + /// method on the limiting subsequence instead. + /// + /// - Parameters: + /// - collection: The collection to read the replacement values from. + /// - Returns: A two-member tuple where the first member is the index of the + /// first element of this collection that was not assigned a copy and the + /// second member is the index of the first element of `source` that was not + /// used for the source of a copy. They will be their collection's + /// `startIndex` if no copying was done and their collection's `endIndex` if + /// every element of that collection participated in a copy. + /// - Postcondition: Let *k* be the element count of the shorter of `self` and + /// `source`. Then `prefix(k)` will be equivalent to `source.prefix(k)`, + /// while `dropFirst(k)` is unchanged. + /// + /// - Complexity: O(*n*), where *n* is the length of the shorter sequence + /// between `self` and `source`. + public mutating func copy( + collection: C + ) -> (copyEnd: Index, sourceTailStart: C.Index) where C.Element == Element { + var selfIndex = startIndex, collectionIndex = collection.startIndex + let end = endIndex, sourceEnd = collection.endIndex + while selfIndex < end, collectionIndex < sourceEnd { + self[selfIndex] = collection[collectionIndex] + formIndex(after: &selfIndex) + collection.formIndex(after: &collectionIndex) + } + return (selfIndex, collectionIndex) + } } diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/CopyTests.swift index 794fcbba..f02a58d7 100644 --- a/Tests/SwiftAlgorithmsTests/CopyTests.swift +++ b/Tests/SwiftAlgorithmsTests/CopyTests.swift @@ -12,7 +12,7 @@ import XCTest import Algorithms -/// Unit tests for the `copy(from:)` method. +/// Unit tests for the `copy(from:)` and `copy(collection:)` methods. final class CopyTests: XCTestCase { /// Test empty source and destination. func testBothEmpty() { @@ -24,6 +24,11 @@ final class CopyTests: XCTestCase { XCTAssertEqual(result.copyEnd, empty1.startIndex) XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) XCTAssertEqualSequences(empty1, []) + + let result2 = empty1.copy(collection: empty2) + XCTAssertEqual(result2.copyEnd, empty1.startIndex) + XCTAssertEqual(result2.sourceTailStart, empty2.startIndex) + XCTAssertEqualSequences(empty1, []) } /// Test nonempty source and empty destination. @@ -36,6 +41,11 @@ final class CopyTests: XCTestCase { XCTAssertEqual(result.copyEnd, empty.endIndex) XCTAssertEqualSequences(IteratorSequence(result.sourceTail), [1.1]) XCTAssertEqualSequences(empty, []) + + let result2 = empty.copy(collection: single) + XCTAssertEqual(result2.copyEnd, empty.startIndex) + XCTAssertEqual(result2.sourceTailStart, single.startIndex) + XCTAssertEqualSequences(empty, []) } /// Test empty source and nonempty destination. @@ -48,6 +58,11 @@ final class CopyTests: XCTestCase { XCTAssertEqual(result.copyEnd, single.startIndex) XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) XCTAssertEqualSequences(single, [2.2]) + + let result2 = single.copy(collection: empty) + XCTAssertEqual(result2.copyEnd, single.startIndex) + XCTAssertEqual(result2.sourceTailStart, empty.startIndex) + XCTAssertEqualSequences(single, [2.2]) } /// Test two one-element collections. @@ -60,6 +75,12 @@ final class CopyTests: XCTestCase { XCTAssertEqual(result.copyEnd, destination.endIndex) XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) XCTAssertEqualSequences(destination, [4.4]) + + let source2 = CollectionOfOne(5.5), + result2 = destination.copy(collection: source2) + XCTAssertEqual(result2.copyEnd, destination.endIndex) + XCTAssertEqual(result2.sourceTailStart, source2.endIndex) + XCTAssertEqualSequences(destination, [5.5]) } /// Test two equal-length multi-element collections. @@ -72,6 +93,11 @@ final class CopyTests: XCTestCase { XCTAssertEqual(result.copyEnd, destination.endIndex) XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) XCTAssertEqualSequences(destination, "fghij") + + let source2 = "12345", result2 = destination.copy(collection: source2) + XCTAssertEqual(result2.copyEnd, destination.endIndex) + XCTAssertEqual(result2.sourceTailStart, source2.endIndex) + XCTAssertEqualSequences(destination, "12345") } /// Test a source longer than a multi-element destination. @@ -84,6 +110,11 @@ final class CopyTests: XCTestCase { XCTAssertEqual(result.copyEnd, destination.endIndex) XCTAssertEqualSequences(IteratorSequence(result.sourceTail), 15...100) XCTAssertEqualSequences(destination, 10..<15) + + let source2 = -50..<0, result2 = destination.copy(collection: source2) + XCTAssertEqual(result2.copyEnd, destination.endIndex) + XCTAssertEqual(result2.sourceTailStart, -45) + XCTAssertEqualSequences(destination, (-50)...(-46)) } /// Test a multi-element source shorter than the destination. @@ -97,6 +128,11 @@ final class CopyTests: XCTestCase { offsetBy: source.count)) XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) XCTAssertEqualSequences(destination, "NOPQRfghijklm") + + let source2 = "123", result2 = destination.copy(collection: source2) + XCTAssertEqual(result2.copyEnd, 3) + XCTAssertEqual(result2.sourceTailStart, source2.endIndex) + XCTAssertEqualSequences(destination, "123QRfghijklm") } /// Test copying over part of the destination. @@ -114,5 +150,15 @@ final class CopyTests: XCTestCase { XCTAssertEqual(result2.copyEnd, 5) XCTAssertEqualSequences(IteratorSequence(result2.sourceTail), []) XCTAssertEqualSequences(destination, "abc12UVhijklm") + + let result3 = destination[3..<7].copy(collection: source) + XCTAssertEqual(result3.copyEnd, 7) + XCTAssertEqualSequences(source[result3.sourceTailStart...], "WXYZ") + XCTAssertEqualSequences(destination, "abcSTUVhijklm") + + let source2 = "12", result4 = destination[3..<7].copy(collection: source2) + XCTAssertEqual(result4.copyEnd, 5) + XCTAssertEqual(result4.sourceTailStart, source2.endIndex) + XCTAssertEqualSequences(destination, "abc12UVhijklm") } } From 8e19993872efc48e4de536db4af6de273af21888 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Thu, 17 Dec 2020 07:31:11 -0500 Subject: [PATCH 03/17] Correct name of parameter in documentation --- Sources/Algorithms/Copy.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift index 6eb1590d..de42e0d0 100644 --- a/Sources/Algorithms/Copy.swift +++ b/Sources/Algorithms/Copy.swift @@ -56,16 +56,16 @@ extension MutableCollection { /// - collection: The collection to read the replacement values from. /// - Returns: A two-member tuple where the first member is the index of the /// first element of this collection that was not assigned a copy and the - /// second member is the index of the first element of `source` that was not - /// used for the source of a copy. They will be their collection's + /// second member is the index of the first element of `collection` that was + /// not used for the source of a copy. They will be their collection's /// `startIndex` if no copying was done and their collection's `endIndex` if /// every element of that collection participated in a copy. /// - Postcondition: Let *k* be the element count of the shorter of `self` and - /// `source`. Then `prefix(k)` will be equivalent to `source.prefix(k)`, - /// while `dropFirst(k)` is unchanged. + /// `collection`. Then `prefix(k)` will be equivalent to + /// `collection.prefix(k)`, while `dropFirst(k)` is unchanged. /// /// - Complexity: O(*n*), where *n* is the length of the shorter sequence - /// between `self` and `source`. + /// between `self` and `collection`. public mutating func copy( collection: C ) -> (copyEnd: Index, sourceTailStart: C.Index) where C.Element == Element { From 39021cb4c2f757aa988f2d93fc5ea6337c11861c Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Thu, 17 Dec 2020 20:29:32 -0500 Subject: [PATCH 04/17] Add methods to copy elements onto a suffix Add method to element-mutable bidirectional collections that takes a sequence of the same element type to read its elements and assign them on top of the collection's last elements. The copying stops upon reaching the end of the source sequence or the beginning of the receiver, whichever is hit first. The method returns two items. The first is the index for the first destination element written over. The second is an iterator for the elements of the source sequence that were not used for copying. Add another method that does the same thing but the source parameter is instead also a collection; its second returned item is instead one index past the last source element read from. For the tests over the copying methods where both the receiver and argument are collections, use the returned index bounds to compare equality for the targeted subsequences. --- CHANGELOG.md | 6 +- Guides/Copy.md | 14 ++- README.md | 2 +- Sources/Algorithms/Copy.swift | 74 +++++++++++++ Tests/SwiftAlgorithmsTests/CopyTests.swift | 122 ++++++++++++++++++++- 5 files changed, 214 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a90e68d0..86317dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,11 @@ package updates, you can specify your package dependency using the copying ended and an iterator for the source sequence after the elements that were copied. The `copy(collection:)` method works like the previous method, but uses a collection as the source, and expresses the unread suffix - for that source as an `Index` instead. + for that source as an `Index` instead. The `copyOntoSuffix(with:)` and + `copyOntoSuffix(withCollection:)` methods work like the first two methods + except the end of the receiver is overwritten instead of the beginning, and + so their return value instead includes the starting index in the receiver + where the copying began. --- diff --git a/Guides/Copy.md b/Guides/Copy.md index 319d6c37..5ba08864 100644 --- a/Guides/Copy.md +++ b/Guides/Copy.md @@ -18,7 +18,9 @@ print(Array(IteratorSequence(sourceSuffix))) // "[]" `copy(from:)` takes a source sequence and overlays its first *k* elements' values over the first `k` elements of the receiver, where `k` is the smaller of the two sequences' lengths. The `copy(collection:)` variant uses a collection -for the source sequence. +for the source sequence. The `copyOntoSuffix(with:)` and +`copyOntoSuffix(withCollection:)` methods work similar to the first two methods +except the last `k` elements of the receiver are overlaid instead. ## Detailed Design @@ -33,6 +35,16 @@ extension MutableCollection { -> (copyEnd: Index, sourceTailStart: C.Index) where C : Collection, Self.Element == C.Element } + +extension MutableCollection where Self: BidirectionalCollection { + mutating func copyOntoSuffix(with source: S) + -> (copyStart: Index, sourceTail: S.Iterator) + where S : Sequence, Self.Element == S.Element + + mutating func copyOntoSuffix(withCollection source: C) + -> (copyStart: Index, sourceTailStart: C.Index) + where C : Collection, Self.Element == C.Element +} ``` The methods return two values. The first member is the index of the receiver diff --git a/README.md b/README.md index 8dcd04a8..305da0c8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`rotate(toStartAt:)`, `rotate(subrange:toStartAt:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Rotate.md): In-place rotation of elements. - [`stablePartition(by:)`, `stablePartition(subrange:by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Partition.md): A partition that preserves the relative order of the resulting prefix and suffix. -- [`copy(from:)`, `copy(collection:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. +- [`copy(from:)`, `copy(collection:)`, `copyOntoSuffix(with:)`, `copyOntoSuffix(withCollection:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. #### Combining collections diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift index de42e0d0..e715843a 100644 --- a/Sources/Algorithms/Copy.swift +++ b/Sources/Algorithms/Copy.swift @@ -79,3 +79,77 @@ extension MutableCollection { return (selfIndex, collectionIndex) } } + +//===----------------------------------------------------------------------===// +// copyOntoSuffix(with:), copyOntoSuffix(withCollection:) +//===----------------------------------------------------------------------===// + +extension MutableCollection where Self: BidirectionalCollection { + /// Copies the elements from the given sequence on top of the elements at the + /// end of this collection, until the shorter one is exhausted. + /// + /// If you want to limit how much of this collection can be overrun, call this + /// method on the limiting subsequence instead. The elements in the mutated + /// suffix stay in the same order as they were in `source`. + /// + /// - Parameters: + /// - source: The sequence to read the replacement values from. + /// - Returns: A two-member tuple where the first member is the index of the + /// earliest element of this collection that was assigned a copy. It will + /// be `endIndex` if no copying was done and `startIndex` if every element + /// was written over. The second member is an iterator covering all the + /// elements of `source` that where not used as part of the copying. It + /// will be empty if every element was used. + /// - Postcondition: Let *k* be the element count of the shorter of `self` and + /// `source`. Then `suffix(k)` will be equivalent to `source.prefix(k)`, + /// while `dropLast(k)` is unchanged. + /// + /// - Complexity: O(*n*), where *n* is the length of the shorter sequence + /// between `self` and `source`. + public mutating func copyOntoSuffix( + with source: S + ) -> (copyStart: Index, sourceTail: S.Iterator) where S.Element == Element { + var current = endIndex, iterator = source.makeIterator() + let start = startIndex + while current > start, let source = iterator.next() { + formIndex(before: ¤t) + self[current] = source + } + self[current...].reverse() + return (current, iterator) + } + + /// Copies the elements from the given collection on top of the elements at + /// the end of this collection, until the shorter one is exhausted. + /// + /// If you want to limit how much of this collection can be overrun, call this + /// method on the limiting subsequence instead. The elements in the mutated + /// suffix stay in the same order as they were in `source`. + /// + /// - Parameters: + /// - source: The collection to read the replacement values from. + /// - Returns: A two-member tuple. The first member is the index of the + /// earliest element of this collection that was assigned a copy; or + /// `endIndex` if no copying was done. The second member is the index + /// immediately after the latest element of `source` read for a copy; or + /// `startIndex` if no copying was done. + /// - Postcondition: Let *k* be the element count of the shorter of `self` and + /// `source`. Then `suffix(k)` will be equivalent to `source.prefix(k)`, + /// while `dropLast(k)` is unchanged. + /// + /// - Complexity: O(*n*), where *n* is the length of the shorter sequence + /// between `self` and `source`. + public mutating func copyOntoSuffix( + withCollection source: C + ) -> (copyStart: Index, sourceTailStart: C.Index) where C.Element == Element { + var selfIndex = endIndex, sourceIndex = source.startIndex + let start = startIndex, sourceEnd = source.endIndex + while selfIndex > start, sourceIndex < sourceEnd { + formIndex(before: &selfIndex) + self[selfIndex] = source[sourceIndex] + source.formIndex(after: &sourceIndex) + } + self[selfIndex...].reverse() + return (selfIndex, sourceIndex) + } +} diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/CopyTests.swift index f02a58d7..c95ade73 100644 --- a/Tests/SwiftAlgorithmsTests/CopyTests.swift +++ b/Tests/SwiftAlgorithmsTests/CopyTests.swift @@ -12,7 +12,7 @@ import XCTest import Algorithms -/// Unit tests for the `copy(from:)` and `copy(collection:)` methods. +/// Unit tests for the `copy` and `copyOntoSuffix` methods. final class CopyTests: XCTestCase { /// Test empty source and destination. func testBothEmpty() { @@ -29,6 +29,20 @@ final class CopyTests: XCTestCase { XCTAssertEqual(result2.copyEnd, empty1.startIndex) XCTAssertEqual(result2.sourceTailStart, empty2.startIndex) XCTAssertEqualSequences(empty1, []) + XCTAssertEqualSequences(empty1[.. Date: Thu, 17 Dec 2020 20:31:26 -0500 Subject: [PATCH 05/17] Correct names of tests --- Tests/SwiftAlgorithmsTests/CopyTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/CopyTests.swift index c95ade73..f882a2fd 100644 --- a/Tests/SwiftAlgorithmsTests/CopyTests.swift +++ b/Tests/SwiftAlgorithmsTests/CopyTests.swift @@ -174,7 +174,7 @@ final class CopyTests: XCTestCase { } /// Test a source longer than a multi-element destination. - func testLongerDestination() { + func testLongerSource() { var destination = Array(1...5) let source = 10...100 XCTAssertEqualSequences(destination, 1...5) @@ -206,7 +206,7 @@ final class CopyTests: XCTestCase { } /// Test a multi-element source shorter than the destination. - func testShorterDestination() { + func testShorterSource() { var destination = Array("abcdefghijklm") let source = "NOPQR" XCTAssertEqualSequences(destination, "abcdefghijklm") From f143910d5c9a9024b23d641cdcda0b2bd35815e8 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Fri, 18 Dec 2020 00:16:57 -0500 Subject: [PATCH 06/17] Add method to copy elements backwards Add method to element-mutable bidirectional collections that takes a bidirectional collection with the same element type to read its last elements and assign them on top of the receiver's last elements. The copying is done backwards and stops upon at least one of the collections reaching their beginning (whichever hits it first). The method returns two items: the starting indices of each collection's suffix that participated in the copying. --- CHANGELOG.md | 5 +- Guides/Copy.md | 16 ++++-- README.md | 2 +- Sources/Algorithms/Copy.swift | 35 ++++++++++++- Tests/SwiftAlgorithmsTests/CopyTests.swift | 57 ++++++++++++++++++++++ 5 files changed, 109 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86317dec..4bdd1efa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,10 @@ package updates, you can specify your package dependency using `copyOntoSuffix(withCollection:)` methods work like the first two methods except the end of the receiver is overwritten instead of the beginning, and so their return value instead includes the starting index in the receiver - where the copying began. + where the copying began. The `copy(backwards:)` method works like the + previous method, except the source is also read from the end instead of the + beginning, and so the return values are the starting indices of both + collections' targeted elements. --- diff --git a/Guides/Copy.md b/Guides/Copy.md index 5ba08864..4950abd9 100644 --- a/Guides/Copy.md +++ b/Guides/Copy.md @@ -20,7 +20,9 @@ values over the first `k` elements of the receiver, where `k` is the smaller of the two sequences' lengths. The `copy(collection:)` variant uses a collection for the source sequence. The `copyOntoSuffix(with:)` and `copyOntoSuffix(withCollection:)` methods work similar to the first two methods -except the last `k` elements of the receiver are overlaid instead. +except the last `k` elements of the receiver are overlaid instead. The +`copy(backwards:)` method is like the previous method, except both the source +and destination collections are traversed from the end. ## Detailed Design @@ -44,12 +46,16 @@ extension MutableCollection where Self: BidirectionalCollection { mutating func copyOntoSuffix(withCollection source: C) -> (copyStart: Index, sourceTailStart: C.Index) where C : Collection, Self.Element == C.Element + + mutating func copy(backwards source: C) + -> (writtenStart: Index, readStart: C.Index) + where C : BidirectionalCollection, Self.Element == C.Element } ``` The methods return two values. The first member is the index of the receiver defining the non-anchored endpoint of the elements that were actually written -over. The second member is for reading the suffix of the source that wasn't +over. The second member is for reading the adfix of the source that wasn't actually used. ### Complexity @@ -68,8 +74,12 @@ libraries. a bounding pair of input iterators for the source and a single output iterator for the destination, returning one-past the last output iterator written over. The `copy_if` function does not have an analogue, since it can be simulated by -submitting the result from `filter(_:)` as the source. +submitting the result from `filter(_:)` as the source. There is a +[`copy_backward`][C++CopyBackward] function that copies elements backwards from +the far end of the source and destination, returning the near end of the +destination that got written. [C++Copy]: https://en.cppreference.com/w/cpp/algorithm/copy +[C++CopyBackward]: https://en.cppreference.com/w/cpp/algorithm/copy_backward \ No newline at end of file diff --git a/README.md b/README.md index 305da0c8..b09cd45c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`rotate(toStartAt:)`, `rotate(subrange:toStartAt:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Rotate.md): In-place rotation of elements. - [`stablePartition(by:)`, `stablePartition(subrange:by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Partition.md): A partition that preserves the relative order of the resulting prefix and suffix. -- [`copy(from:)`, `copy(collection:)`, `copyOntoSuffix(with:)`, `copyOntoSuffix(withCollection:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. +- [`copy(from:)`, `copy(collection:)`, `copyOntoSuffix(with:)`, `copyOntoSuffix(withCollection:)`, `copy(backwards:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. #### Combining collections diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift index e715843a..35d0c53e 100644 --- a/Sources/Algorithms/Copy.swift +++ b/Sources/Algorithms/Copy.swift @@ -81,7 +81,7 @@ extension MutableCollection { } //===----------------------------------------------------------------------===// -// copyOntoSuffix(with:), copyOntoSuffix(withCollection:) +// copyOntoSuffix(with:), copyOntoSuffix(withCollection:), copy(backwards:) //===----------------------------------------------------------------------===// extension MutableCollection where Self: BidirectionalCollection { @@ -152,4 +152,37 @@ extension MutableCollection where Self: BidirectionalCollection { self[selfIndex...].reverse() return (selfIndex, sourceIndex) } + + /// Copies the elements from the given collection on top of the elements of + /// this collection, going backwards from the ends of both collections, until + /// the shorter one is exhausted. + /// + /// If you want to limit how much of this collection can be overrun, call this + /// method on the limiting subsequence instead. + /// + /// - Parameters: + /// - source: The collection to read the replacement values from. + /// - Returns: A two-member tuple. The first member is the index of the + /// earliest element of this collection that was assigned a copy. The + /// second member is the index of the earliest element of `source` that was + /// read for copying. If no copying was done, both returned indices are at + /// their respective owner's `endIndex`. + /// - Postcondition: Let *k* be the element count of the shorter of `self` and + /// `source`. Then `suffix(k)` will be equivalent to `source.suffix(k)`, + /// while `dropLast(k)` is unchanged. + /// + /// - Complexity: O(*n*), where *n* is the length of the shorter collection + /// between `self` and `source`. + public mutating func copy( + backwards source: C + ) -> (writtenStart: Index, readStart: C.Index) where C.Element == Element { + var selfIndex = endIndex, sourceIndex = source.endIndex + let start = startIndex, sourceStart = source.startIndex + while selfIndex > start, sourceIndex > sourceStart { + formIndex(before: &selfIndex) + source.formIndex(before: &sourceIndex) + self[selfIndex] = source[sourceIndex] + } + return (selfIndex, sourceIndex) + } } diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/CopyTests.swift index f882a2fd..f23ebcac 100644 --- a/Tests/SwiftAlgorithmsTests/CopyTests.swift +++ b/Tests/SwiftAlgorithmsTests/CopyTests.swift @@ -43,6 +43,13 @@ final class CopyTests: XCTestCase { XCTAssertEqualSequences(empty1, []) XCTAssertEqualSequences(empty1[result4.copyStart...], empty2[.. Date: Sat, 19 Dec 2020 03:12:34 -0500 Subject: [PATCH 07/17] Add methods to internally copy elements Add method to element-mutable collections that takes range expressions for two subsequences of the receiver and copies elements from one subsequence on top of the elements for the other. The copying is done forwards, targeting the subsequences' prefixes until at least one subsequence runs out of elements. Add an overload for bidirectional collections that does its copies in reverse over the subsequences' suffixes instead. The methods return two items; range expressions bounding the parts of the subsequences that actually participated in copying. --- CHANGELOG.md | 4 +- Guides/Copy.md | 35 +++++- README.md | 2 +- Sources/Algorithms/Copy.swift | 91 ++++++++++++++- Tests/SwiftAlgorithmsTests/CopyTests.swift | 130 +++++++++++++++++++++ 5 files changed, 253 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bdd1efa..838eca85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,9 @@ package updates, you can specify your package dependency using where the copying began. The `copy(backwards:)` method works like the previous method, except the source is also read from the end instead of the beginning, and so the return values are the starting indices of both - collections' targeted elements. + collections' targeted elements. The Swift memory model restricts reading and + writing into the same collection, so the `copy(forwardsFrom:to:)` and + `copy(backwardsFrom:to:)` methods provide same-collection element copying. --- diff --git a/Guides/Copy.md b/Guides/Copy.md index 4950abd9..b3872b93 100644 --- a/Guides/Copy.md +++ b/Guides/Copy.md @@ -24,6 +24,11 @@ except the last `k` elements of the receiver are overlaid instead. The `copy(backwards:)` method is like the previous method, except both the source and destination collections are traversed from the end. +Since the Swift memory model prevents a collection from being used multiple +times in code where at least one use is mutable, the `copy(forwardsFrom:to:)` +and `copy(backwardsFrom:to:)` methods permit copying elements across +subsequences of the same collection. + ## Detailed Design New methods are added to element-mutable collections: @@ -36,6 +41,11 @@ extension MutableCollection { mutating func copy(collection: C) -> (copyEnd: Index, sourceTailStart: C.Index) where C : Collection, Self.Element == C.Element + + mutating func copy(forwardsFrom source: R, to destination: S) + -> (sourceRead: Range, destinationWritten: Range) + where R : RangeExpression, S : RangeExpression, Self.Index == R.Bound, + R.Bound == S.Bound } extension MutableCollection where Self: BidirectionalCollection { @@ -50,13 +60,23 @@ extension MutableCollection where Self: BidirectionalCollection { mutating func copy(backwards source: C) -> (writtenStart: Index, readStart: C.Index) where C : BidirectionalCollection, Self.Element == C.Element + + mutating func copy(backwardsFrom source: R, to destination: S) + -> (sourceRead: Range, destinationWritten: Range) + where R : RangeExpression, S : RangeExpression, Self.Index == R.Bound, + R.Bound == S.Bound } ``` -The methods return two values. The first member is the index of the receiver -defining the non-anchored endpoint of the elements that were actually written -over. The second member is for reading the adfix of the source that wasn't -actually used. +Each method returns two values. For the two-sequence methods, the first member +is the index for the non-endpoint bound for the destination adfix. For the +two-sequence methods where the non-receiver is a `Sequence`, the second member +is an iterator for the elements of the source's suffix that were never read in +for copying. For the two-sequence methods where the non-receiver is a +`Collection`, the second member is the index for the first element of the +source's suffix that was never read in for copying. For the two-subsequences +methods, the members are the ranges for the parts of the subsequence operands +that were actually touched during copying. ### Complexity @@ -77,7 +97,12 @@ The `copy_if` function does not have an analogue, since it can be simulated by submitting the result from `filter(_:)` as the source. There is a [`copy_backward`][C++CopyBackward] function that copies elements backwards from the far end of the source and destination, returning the near end of the -destination that got written. +destination that got written. These functions take their buffer arguments as +separate iterator/pointer values; as such, the functions can handle the source +and destination buffers having overlap or otherwise being sub-buffers of a +shared collection. Swift's memory safety model prevents it from doing the +same, necessitating it to use customized methods when the source and +destination buffers subset the same super-buffer. diff --git a/README.md b/README.md index b09cd45c..b74f7f4b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`rotate(toStartAt:)`, `rotate(subrange:toStartAt:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Rotate.md): In-place rotation of elements. - [`stablePartition(by:)`, `stablePartition(subrange:by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Partition.md): A partition that preserves the relative order of the resulting prefix and suffix. -- [`copy(from:)`, `copy(collection:)`, `copyOntoSuffix(with:)`, `copyOntoSuffix(withCollection:)`, `copy(backwards:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. +- [`copy(from:)`, `copy(collection:)`, `copyOntoSuffix(with:)`, `copyOntoSuffix(withCollection:)`, `copy(backwards:)`, `copy(forwardsFrom:to:)`, `copy(backwardsFrom:to:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. #### Combining collections diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift index 35d0c53e..0ea8d1dd 100644 --- a/Sources/Algorithms/Copy.swift +++ b/Sources/Algorithms/Copy.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// -// copy(from:), copy(collection:) +// copy(from:), copy(collection:), copy(forwardsFrom:to:) //===----------------------------------------------------------------------===// extension MutableCollection { @@ -78,10 +78,54 @@ extension MutableCollection { } return (selfIndex, collectionIndex) } + + /// Copies, in forward traversal, the prefix of a subsequence on top of the + /// prefix of another, using the given bounds to demarcate the subsequences. + /// + /// Copying stops when the end of the shorter subsequence is reached. + /// + /// - Precondition: + /// - `source` and `destination` must bound valid subsequences of this collection. + /// - Either `source` and `destination` are disjoint, or + /// `self[source].startIndex >= self[destination].startIndex`. + /// + /// - Parameters: + /// - source: The index range bounding the subsequence to read the + /// replacement values from. + /// - destination: The index range bounding the subsequence whose elements + /// will be overwritten. + /// - Returns: A two-member tuple where the first member are the indices of + /// `self[source]` that were read for copying and the second member are the + /// indices of `self[destination]` that were written over during copying. + /// - Postcondition: Let *k* be the element count of the shorter of + /// `self[source]` and `self[destination]`, and *c* be the pre-call value of + /// `self[source]`. Then `self[destination].prefix(k)` will be equivalent + /// to `c.prefix(k)`, while `self[destination].dropFirst(k)` is unchanged. + /// + /// - Complexity: O(*n*), where *n* is the length of the shorter subsequence + /// between `self[source]` and `self[destination]`. + public mutating func copy( + forwardsFrom source: R, + to destination: S + ) -> (sourceRead: Range, destinationWritten: Range) + where R.Bound == Index, S.Bound == Index { + let rangeS = source.relative(to: self), + rangeD = destination.relative(to: self) + var sourceIndex = rangeS.lowerBound, destinationIndex = rangeD.lowerBound + while sourceIndex < rangeS.upperBound, + destinationIndex < rangeD.upperBound { + self[destinationIndex] = self[sourceIndex] + formIndex(after: &sourceIndex) + formIndex(after: &destinationIndex) + } + return (rangeS.lowerBound ..< sourceIndex, + rangeD.lowerBound ..< destinationIndex) + } } //===----------------------------------------------------------------------===// -// copyOntoSuffix(with:), copyOntoSuffix(withCollection:), copy(backwards:) +// copyOntoSuffix(with:), copyOntoSuffix(withCollection:), copy(backwards:), +// copy(backwardsFrom:to:) //===----------------------------------------------------------------------===// extension MutableCollection where Self: BidirectionalCollection { @@ -185,4 +229,47 @@ extension MutableCollection where Self: BidirectionalCollection { } return (selfIndex, sourceIndex) } + + /// Copies, in reverse traversal, the suffix of a subsequence on top of the + /// suffix of another, using the given bounds to demarcate the subsequences. + /// + /// Copying stops when the beginning of the shorter subsequence is reached. + /// + /// - Precondition: + /// - `source` and `destination` must bound valid subsequences of this collection. + /// - Either `source` and `destination` are disjoint, or + /// `self[source].endIndex <= self[destination].endIndex`. + /// + /// - Parameters: + /// - source: The index range bounding the subsequence to read the + /// replacement values from. + /// - destination: The index range bounding the subsequence whose elements + /// will be overwritten. + /// - Returns: A two-member tuple where the first member are the indices of + /// `self[source]` that were read for copying and the second member are the + /// indices of `self[destination]` that were written over during copying. + /// - Postcondition: Let *k* be the element count of the shorter of + /// `self[source]` and `self[destination]`, and *c* be the pre-call value of + /// `self[source]`. Then `self[destination].suffix(k)` will be equivalent + /// to `c.suffix(k)`, while `self[destination].dropLast(k)` is unchanged. + /// + /// - Complexity: O(*n*), where *n* is the length of the shorter subsequence + /// between `self[source]` and `self[destination]`. + public mutating func copy( + backwardsFrom source: R, + to destination: S + ) -> (sourceRead: Range, destinationWritten: Range) + where R.Bound == Index, S.Bound == Index { + let rangeS = source.relative(to: self), + rangeD = destination.relative(to: self) + var sourceIndex = rangeS.upperBound, destinationIndex = rangeD.upperBound + while sourceIndex > rangeS.lowerBound, + destinationIndex > rangeD.lowerBound { + formIndex(before: &destinationIndex) + formIndex(before: &sourceIndex) + self[destinationIndex] = self[sourceIndex] + } + return (sourceIndex ..< rangeS.upperBound, + destinationIndex ..< rangeD.upperBound) + } } diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/CopyTests.swift index f23ebcac..c33680e3 100644 --- a/Tests/SwiftAlgorithmsTests/CopyTests.swift +++ b/Tests/SwiftAlgorithmsTests/CopyTests.swift @@ -338,4 +338,134 @@ final class CopyTests: XCTestCase { XCTAssertEqualSequences(destination[3..<7][result7.writtenStart...], source4[result7.readStart...]) } + + /// Test forward copying within a collection. + func testInternalForward() { + // Empty source and destination + let untarnished = (0..<10).map(Double.init) + var sample = untarnished, + sRange, dRange: Range + XCTAssertEqualSequences(sample, untarnished) + (sRange, dRange) = sample.copy(forwardsFrom: 1..<1, to: 6..<6) + XCTAssertEqualSequences(sample, untarnished) + XCTAssertEqual(sRange, 1..<1) + XCTAssertEqual(dRange, 6..<6) + + // Empty source + (sRange, dRange) = sample.copy(forwardsFrom: 2..<2, to: 7..<8) + XCTAssertEqualSequences(sample, untarnished) + XCTAssertEqual(sRange, 2..<2) + XCTAssertEqual(dRange, 7..<7) + + // Empty destination + (sRange, dRange) = sample.copy(forwardsFrom: 3..<4, to: 9..<9) + XCTAssertEqualSequences(sample, untarnished) + XCTAssertEqual(sRange, 3..<3) + XCTAssertEqual(dRange, 9..<9) + + // Equal nonempty source and destination + (sRange, dRange) = sample.copy(forwardsFrom: 5..<8, to: 5..<8) + XCTAssertEqualSequences(sample, untarnished) + XCTAssertEqual(sRange, 5..<8) + XCTAssertEqual(dRange, 5..<8) + + // Overlapping nonempty source and destination + (sRange, dRange) = sample.copy(forwardsFrom: 5..<9, to: 3..<7) + XCTAssertEqualSequences(sample, [0, 1, 2, 5, 6, 7, 8, 7, 8, 9]) + XCTAssertEqual(sRange, 5..<9) + XCTAssertEqual(dRange, 3..<7) + + // Disjoint but nonempty equal-sized source and destination + sample = untarnished + (sRange, dRange) = sample.copy(forwardsFrom: 7..<9, to: 2..<4) + XCTAssertEqualSequences(sample, [0, 1, 7, 8, 4, 5, 6, 7, 8, 9]) + XCTAssertEqual(sRange, 7..<9) + XCTAssertEqual(dRange, 2..<4) + + // Source longer than nonempty destination + sample = untarnished + (sRange, dRange) = sample.copy(forwardsFrom: 2..<6, to: 7..<10) + XCTAssertEqualSequences(sample, [0, 1, 2, 3, 4, 5, 6, 2, 3, 4]) + XCTAssertEqual(sRange, 2..<5) + XCTAssertEqual(dRange, 7..<10) + + // Nonempty source shorter than destination + sample = untarnished + (sRange, dRange) = sample.copy(forwardsFrom: 5..<7, to: 1..<9) + XCTAssertEqualSequences(sample, [0, 5, 6, 3, 4, 5, 6, 7, 8, 9]) + XCTAssertEqual(sRange, 5..<7) + XCTAssertEqual(dRange, 1..<3) + + // Using expressions other than `Range` + sample = untarnished + (sRange, dRange) = sample.copy(forwardsFrom: ..<2, to: 8...) + XCTAssertEqualSequences(sample, [0, 1, 2, 3, 4, 5, 6, 7, 0, 1]) + XCTAssertEqual(sRange, 0..<2) + XCTAssertEqual(dRange, 8..<10) + } + + /// Test backward copying within a collection. + func testInternalBackward() { + // Empty source and destination + let untarnished = (0..<10).map(Double.init) + var sample = untarnished, + sRange, dRange: Range + XCTAssertEqualSequences(sample, untarnished) + (sRange, dRange) = sample.copy(backwardsFrom: 1..<1, to: 6..<6) + XCTAssertEqualSequences(sample, untarnished) + XCTAssertEqual(sRange, 1..<1) + XCTAssertEqual(dRange, 6..<6) + + // Empty source + (sRange, dRange) = sample.copy(backwardsFrom: 2..<2, to: 7..<8) + XCTAssertEqualSequences(sample, untarnished) + XCTAssertEqual(sRange, 2..<2) + XCTAssertEqual(dRange, 8..<8) + + // Empty destination + (sRange, dRange) = sample.copy(backwardsFrom: 3..<4, to: 9..<9) + XCTAssertEqualSequences(sample, untarnished) + XCTAssertEqual(sRange, 4..<4) + XCTAssertEqual(dRange, 9..<9) + + // Equal nonempty source and destination + (sRange, dRange) = sample.copy(backwardsFrom: 5..<8, to: 5..<8) + XCTAssertEqualSequences(sample, untarnished) + XCTAssertEqual(sRange, 5..<8) + XCTAssertEqual(dRange, 5..<8) + + // Overlapping nonempty source and destination + (sRange, dRange) = sample.copy(backwardsFrom: 3..<7, to: 5..<9) + XCTAssertEqualSequences(sample, [0, 1, 2, 3, 4, 3, 4, 5, 6, 9]) + XCTAssertEqual(sRange, 3..<7) + XCTAssertEqual(dRange, 5..<9) + + // Disjoint but nonempty equal-sized source and destination + sample = untarnished + (sRange, dRange) = sample.copy(backwardsFrom: 7..<9, to: 2..<4) + XCTAssertEqualSequences(sample, [0, 1, 7, 8, 4, 5, 6, 7, 8, 9]) + XCTAssertEqual(sRange, 7..<9) + XCTAssertEqual(dRange, 2..<4) + + // Source longer than nonempty destination + sample = untarnished + (sRange, dRange) = sample.copy(backwardsFrom: 2..<6, to: 7..<10) + XCTAssertEqualSequences(sample, [0, 1, 2, 3, 4, 5, 6, 3, 4, 5]) + XCTAssertEqual(sRange, 3..<6) + XCTAssertEqual(dRange, 7..<10) + + // Nonempty source shorter than destination + sample = untarnished + (sRange, dRange) = sample.copy(backwardsFrom: 5..<7, to: 1..<9) + XCTAssertEqualSequences(sample, [0, 1, 2, 3, 4, 5, 6, 5, 6, 9]) + XCTAssertEqual(sRange, 5..<7) + XCTAssertEqual(dRange, 7..<9) + + // Using expressions other than `Range` + sample = untarnished + (sRange, dRange) = sample.copy(backwardsFrom: 8..., to: ..<2) + XCTAssertEqualSequences(sample, [8, 9, 2, 3, 4, 5, 6, 7, 8, 9]) + XCTAssertEqual(sRange, 8..<10) + XCTAssertEqual(dRange, 0..<2) + } } From 676de79cf5306aafe25045a360eb1176b4fc2fbc Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Mon, 21 Dec 2020 22:50:43 -0500 Subject: [PATCH 08/17] Improve documentation; rename methods Improve the summary descriptions for the methods of element-mutable collections that copy over elements, but only for the overloads where the source is a sequence or collection. They clarify what parts of the receiver and source are touched. Rename the "copyOntoSuffix" methods to use "copy" by itself as the base names, and use new parameter labels to differentiate the overloads. Now all of the element-copying methods use the same base name. --- CHANGELOG.md | 4 +- Guides/Copy.md | 14 ++--- README.md | 2 +- Sources/Algorithms/Copy.swift | 65 ++++++++++++---------- Tests/SwiftAlgorithmsTests/CopyTests.swift | 34 +++++------ 5 files changed, 62 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 838eca85..a824eecf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,8 +21,8 @@ package updates, you can specify your package dependency using the copying ended and an iterator for the source sequence after the elements that were copied. The `copy(collection:)` method works like the previous method, but uses a collection as the source, and expresses the unread suffix - for that source as an `Index` instead. The `copyOntoSuffix(with:)` and - `copyOntoSuffix(withCollection:)` methods work like the first two methods + for that source as an `Index` instead. The `copy(asSuffix:)` and + `copy(collectionAsSuffix:)` methods work like the first two methods except the end of the receiver is overwritten instead of the beginning, and so their return value instead includes the starting index in the receiver where the copying began. The `copy(backwards:)` method works like the diff --git a/Guides/Copy.md b/Guides/Copy.md index b3872b93..32426d8a 100644 --- a/Guides/Copy.md +++ b/Guides/Copy.md @@ -18,11 +18,11 @@ print(Array(IteratorSequence(sourceSuffix))) // "[]" `copy(from:)` takes a source sequence and overlays its first *k* elements' values over the first `k` elements of the receiver, where `k` is the smaller of the two sequences' lengths. The `copy(collection:)` variant uses a collection -for the source sequence. The `copyOntoSuffix(with:)` and -`copyOntoSuffix(withCollection:)` methods work similar to the first two methods -except the last `k` elements of the receiver are overlaid instead. The -`copy(backwards:)` method is like the previous method, except both the source -and destination collections are traversed from the end. +for the source sequence. The `copy(asSuffix:)` and `copy(collectionAsSuffix:)` +methods work similar to the first two methods except the last `k` elements of +the receiver are overlaid instead. The `copy(backwards:)` method is like the +previous method, except both the source and destination collections are +traversed from the end. Since the Swift memory model prevents a collection from being used multiple times in code where at least one use is mutable, the `copy(forwardsFrom:to:)` @@ -49,11 +49,11 @@ extension MutableCollection { } extension MutableCollection where Self: BidirectionalCollection { - mutating func copyOntoSuffix(with source: S) + mutating func copy(asSuffix source: S) -> (copyStart: Index, sourceTail: S.Iterator) where S : Sequence, Self.Element == S.Element - mutating func copyOntoSuffix(withCollection source: C) + mutating func copy(collectionAsSuffix source: C) -> (copyStart: Index, sourceTailStart: C.Index) where C : Collection, Self.Element == C.Element diff --git a/README.md b/README.md index b74f7f4b..b8a0b4a0 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`rotate(toStartAt:)`, `rotate(subrange:toStartAt:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Rotate.md): In-place rotation of elements. - [`stablePartition(by:)`, `stablePartition(subrange:by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Partition.md): A partition that preserves the relative order of the resulting prefix and suffix. -- [`copy(from:)`, `copy(collection:)`, `copyOntoSuffix(with:)`, `copyOntoSuffix(withCollection:)`, `copy(backwards:)`, `copy(forwardsFrom:to:)`, `copy(backwardsFrom:to:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. +- [`copy(from:)`, `copy(collection:)`, `copy(asSuffix:)`, `copy(collectionAsSuffix:)`, `copy(backwards:)`, `copy(forwardsFrom:to:)`, `copy(backwardsFrom:to:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. #### Combining collections diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift index 0ea8d1dd..3cd9ff2e 100644 --- a/Sources/Algorithms/Copy.swift +++ b/Sources/Algorithms/Copy.swift @@ -14,11 +14,12 @@ //===----------------------------------------------------------------------===// extension MutableCollection { - /// Copies the elements from the given sequence on top of the elements of this - /// collection, until the shorter one is exhausted. + /// Copies the prefix of the given sequence on top of the prefix of this + /// collection. /// - /// If you want to limit how much of this collection can be overrun, call this - /// method on the limiting subsequence instead. + /// Copying stops when the end of the shorter sequence is reached. If you + /// want to limit how much of this collection can be overrun, call this method + /// on the limiting subsequence instead. /// /// - Parameters: /// - source: The sequence to read the replacement values from. @@ -46,11 +47,12 @@ extension MutableCollection { return (current, iterator) } - /// Copies the elements from the given collection on top of the elements of - /// this collection, until the shorter one is exhausted. + /// Copies the prefix of the given collection on top of the prefix of this + /// collection. /// - /// If you want to limit how much of this collection can be overrun, call this - /// method on the limiting subsequence instead. + /// Copying stops when the end of the shorter collection is reached. If you + /// want to limit how much of this collection can be overrun, call this method + /// on the limiting subsequence instead. /// /// - Parameters: /// - collection: The collection to read the replacement values from. @@ -64,7 +66,7 @@ extension MutableCollection { /// `collection`. Then `prefix(k)` will be equivalent to /// `collection.prefix(k)`, while `dropFirst(k)` is unchanged. /// - /// - Complexity: O(*n*), where *n* is the length of the shorter sequence + /// - Complexity: O(*n*), where *n* is the length of the shorter collection /// between `self` and `collection`. public mutating func copy( collection: C @@ -124,17 +126,19 @@ extension MutableCollection { } //===----------------------------------------------------------------------===// -// copyOntoSuffix(with:), copyOntoSuffix(withCollection:), copy(backwards:), +// copy(asSuffix:), copy(collectionAsSuffix:), copy(backwards:), // copy(backwardsFrom:to:) //===----------------------------------------------------------------------===// extension MutableCollection where Self: BidirectionalCollection { - /// Copies the elements from the given sequence on top of the elements at the - /// end of this collection, until the shorter one is exhausted. + /// Copies the prefix of the given sequence on top of the suffix of this + /// collection. /// - /// If you want to limit how much of this collection can be overrun, call this - /// method on the limiting subsequence instead. The elements in the mutated - /// suffix stay in the same order as they were in `source`. + /// Copying stops when either the sequence is exhausted or every element of + /// this collection is touched. If you want to limit how much of this + /// collection can be overrun, call this method on the limiting subsequence + /// instead. The elements in the mutated suffix preserve the order they had + /// in `source`. /// /// - Parameters: /// - source: The sequence to read the replacement values from. @@ -150,8 +154,8 @@ extension MutableCollection where Self: BidirectionalCollection { /// /// - Complexity: O(*n*), where *n* is the length of the shorter sequence /// between `self` and `source`. - public mutating func copyOntoSuffix( - with source: S + public mutating func copy( + asSuffix source: S ) -> (copyStart: Index, sourceTail: S.Iterator) where S.Element == Element { var current = endIndex, iterator = source.makeIterator() let start = startIndex @@ -163,12 +167,13 @@ extension MutableCollection where Self: BidirectionalCollection { return (current, iterator) } - /// Copies the elements from the given collection on top of the elements at - /// the end of this collection, until the shorter one is exhausted. + /// Copies the prefix of the given collection on top of the suffix of this + /// collection. /// - /// If you want to limit how much of this collection can be overrun, call this - /// method on the limiting subsequence instead. The elements in the mutated - /// suffix stay in the same order as they were in `source`. + /// Copying stops when at least one of the collections has had all of its + /// elements touched. If you want to limit how much of this collection can be + /// overrun, call this method on the limiting subsequence instead. The + /// elements in the mutated suffix preserve the order they had in `source`. /// /// - Parameters: /// - source: The collection to read the replacement values from. @@ -181,10 +186,10 @@ extension MutableCollection where Self: BidirectionalCollection { /// `source`. Then `suffix(k)` will be equivalent to `source.prefix(k)`, /// while `dropLast(k)` is unchanged. /// - /// - Complexity: O(*n*), where *n* is the length of the shorter sequence + /// - Complexity: O(*n*), where *n* is the length of the shorter collection /// between `self` and `source`. - public mutating func copyOntoSuffix( - withCollection source: C + public mutating func copy( + collectionAsSuffix source: C ) -> (copyStart: Index, sourceTailStart: C.Index) where C.Element == Element { var selfIndex = endIndex, sourceIndex = source.startIndex let start = startIndex, sourceEnd = source.endIndex @@ -197,12 +202,12 @@ extension MutableCollection where Self: BidirectionalCollection { return (selfIndex, sourceIndex) } - /// Copies the elements from the given collection on top of the elements of - /// this collection, going backwards from the ends of both collections, until - /// the shorter one is exhausted. + /// Copies the suffix of the given collection on top of the suffix of this + /// collection. /// - /// If you want to limit how much of this collection can be overrun, call this - /// method on the limiting subsequence instead. + /// Copying occurs backwards, and stops when the beginning of the shorter + /// collection is reached. If you want to limit how much of this collection + /// can be overrun, call this method on the limiting subsequence instead. /// /// - Parameters: /// - source: The collection to read the replacement values from. diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/CopyTests.swift index c33680e3..f9d0c22e 100644 --- a/Tests/SwiftAlgorithmsTests/CopyTests.swift +++ b/Tests/SwiftAlgorithmsTests/CopyTests.swift @@ -12,7 +12,7 @@ import XCTest import Algorithms -/// Unit tests for the `copy` and `copyOntoSuffix` methods. +/// Unit tests for the `copy` methods. final class CopyTests: XCTestCase { /// Test empty source and destination. func testBothEmpty() { @@ -32,12 +32,12 @@ final class CopyTests: XCTestCase { XCTAssertEqualSequences(empty1[.. Date: Thu, 24 Dec 2020 21:44:05 -0500 Subject: [PATCH 09/17] Rename the "copy" methods to "overwrite" --- CHANGELOG.md | 14 +-- Guides/Copy.md | 28 ++--- README.md | 2 +- Sources/Algorithms/Copy.swift | 28 ++--- Tests/SwiftAlgorithmsTests/CopyTests.swift | 122 ++++++++++----------- 5 files changed, 97 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a824eecf..83e3a725 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,23 +14,23 @@ package updates, you can specify your package dependency using ### Additions -- The `copy(from:)` method has been added, applying to types conforming to +- The `overwrite(prefixWith:)` method has been added, applying to types conforming to `MutableCollection`. It takes a sequence with the same element type as its only parameter, whose elements will be copied on top of the existing elements. The return values are the past-the-end index in the receiver where the copying ended and an iterator for the source sequence after the elements - that were copied. The `copy(collection:)` method works like the previous + that were copied. The `overwrite(prefixWithCollection:)` method works like the previous method, but uses a collection as the source, and expresses the unread suffix - for that source as an `Index` instead. The `copy(asSuffix:)` and - `copy(collectionAsSuffix:)` methods work like the first two methods + for that source as an `Index` instead. The `overwrite(suffixWith:)` and + `overwrite(suffixWithCollection:)` methods work like the first two methods except the end of the receiver is overwritten instead of the beginning, and so their return value instead includes the starting index in the receiver - where the copying began. The `copy(backwards:)` method works like the + where the copying began. The `overwrite(backwards:)` method works like the previous method, except the source is also read from the end instead of the beginning, and so the return values are the starting indices of both collections' targeted elements. The Swift memory model restricts reading and - writing into the same collection, so the `copy(forwardsFrom:to:)` and - `copy(backwardsFrom:to:)` methods provide same-collection element copying. + writing into the same collection, so the `overwrite(forwardsFrom:to:)` and + `overwrite(backwardsFrom:to:)` methods provide same-collection element copying. --- diff --git a/Guides/Copy.md b/Guides/Copy.md index 32426d8a..b5953c28 100644 --- a/Guides/Copy.md +++ b/Guides/Copy.md @@ -10,23 +10,23 @@ var destination = [1, 2, 3, 4, 5] let source = [6, 7, 8, 9, 10] print(destination) // "[1, 2, 3, 4, 5] -let (_, sourceSuffix) = destination.copy(from: source) +let (_, sourceSuffix) = destination.overwrite(prefixWith: source) print(destination) // "[6, 7, 8, 9, 10]" print(Array(IteratorSequence(sourceSuffix))) // "[]" ``` -`copy(from:)` takes a source sequence and overlays its first *k* elements' +`overwrite(prefixWith:)` takes a source sequence and overlays its first *k* elements' values over the first `k` elements of the receiver, where `k` is the smaller of -the two sequences' lengths. The `copy(collection:)` variant uses a collection -for the source sequence. The `copy(asSuffix:)` and `copy(collectionAsSuffix:)` +the two sequences' lengths. The `overwrite(prefixWithCollection:)` variant uses a collection +for the source sequence. The `overwrite(suffixWith:)` and `overwrite(suffixWithCollection:)` methods work similar to the first two methods except the last `k` elements of -the receiver are overlaid instead. The `copy(backwards:)` method is like the +the receiver are overlaid instead. The `overwrite(backwards:)` method is like the previous method, except both the source and destination collections are traversed from the end. Since the Swift memory model prevents a collection from being used multiple -times in code where at least one use is mutable, the `copy(forwardsFrom:to:)` -and `copy(backwardsFrom:to:)` methods permit copying elements across +times in code where at least one use is mutable, the `overwrite(forwardsFrom:to:)` +and `overwrite(backwardsFrom:to:)` methods permit copying elements across subsequences of the same collection. ## Detailed Design @@ -35,33 +35,33 @@ New methods are added to element-mutable collections: ```swift extension MutableCollection { - mutating func copy(from source: S) + mutating func overwrite(prefixWith source: S) -> (copyEnd: Index, sourceTail: S.Iterator) where S.Element == Element - mutating func copy(collection: C) + mutating func overwrite(prefixWithCollection collection: C) -> (copyEnd: Index, sourceTailStart: C.Index) where C : Collection, Self.Element == C.Element - mutating func copy(forwardsFrom source: R, to destination: S) + mutating func overwrite(forwardsFrom source: R, to destination: S) -> (sourceRead: Range, destinationWritten: Range) where R : RangeExpression, S : RangeExpression, Self.Index == R.Bound, R.Bound == S.Bound } extension MutableCollection where Self: BidirectionalCollection { - mutating func copy(asSuffix source: S) + mutating func overwrite(suffixWith source: S) -> (copyStart: Index, sourceTail: S.Iterator) where S : Sequence, Self.Element == S.Element - mutating func copy(collectionAsSuffix source: C) + mutating func overwrite(suffixWithCollection source: C) -> (copyStart: Index, sourceTailStart: C.Index) where C : Collection, Self.Element == C.Element - mutating func copy(backwards source: C) + mutating func overwrite(backwards source: C) -> (writtenStart: Index, readStart: C.Index) where C : BidirectionalCollection, Self.Element == C.Element - mutating func copy(backwardsFrom source: R, to destination: S) + mutating func overwrite(backwardsFrom source: R, to destination: S) -> (sourceRead: Range, destinationWritten: Range) where R : RangeExpression, S : RangeExpression, Self.Index == R.Bound, R.Bound == S.Bound diff --git a/README.md b/README.md index b8a0b4a0..c2d4c416 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`rotate(toStartAt:)`, `rotate(subrange:toStartAt:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Rotate.md): In-place rotation of elements. - [`stablePartition(by:)`, `stablePartition(subrange:by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Partition.md): A partition that preserves the relative order of the resulting prefix and suffix. -- [`copy(from:)`, `copy(collection:)`, `copy(asSuffix:)`, `copy(collectionAsSuffix:)`, `copy(backwards:)`, `copy(forwardsFrom:to:)`, `copy(backwardsFrom:to:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. +- [`overwrite(prefixWith:)`, `overwrite(prefixWithCollection:)`, `overwrite(suffixWith:)`, `overwrite(suffixWithCollection:)`, `overwrite(backwards:)`, `overwrite(forwardsFrom:to:)`, `overwrite(backwardsFrom:to:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. #### Combining collections diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift index 3cd9ff2e..ab926433 100644 --- a/Sources/Algorithms/Copy.swift +++ b/Sources/Algorithms/Copy.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// -// copy(from:), copy(collection:), copy(forwardsFrom:to:) +// overwrite(prefixWith:), overwrite(prefixWithCollection:), overwrite(forwardsFrom:to:) //===----------------------------------------------------------------------===// extension MutableCollection { @@ -35,8 +35,8 @@ extension MutableCollection { /// /// - Complexity: O(*n*), where *n* is the length of the shorter sequence /// between `self` and `source`. - public mutating func copy( - from source: S + public mutating func overwrite( + prefixWith source: S ) -> (copyEnd: Index, sourceTail: S.Iterator) where S.Element == Element { var current = startIndex, iterator = source.makeIterator() let end = endIndex @@ -68,8 +68,8 @@ extension MutableCollection { /// /// - Complexity: O(*n*), where *n* is the length of the shorter collection /// between `self` and `collection`. - public mutating func copy( - collection: C + public mutating func overwrite( + prefixWithCollection collection: C ) -> (copyEnd: Index, sourceTailStart: C.Index) where C.Element == Element { var selfIndex = startIndex, collectionIndex = collection.startIndex let end = endIndex, sourceEnd = collection.endIndex @@ -106,7 +106,7 @@ extension MutableCollection { /// /// - Complexity: O(*n*), where *n* is the length of the shorter subsequence /// between `self[source]` and `self[destination]`. - public mutating func copy( + public mutating func overwrite( forwardsFrom source: R, to destination: S ) -> (sourceRead: Range, destinationWritten: Range) @@ -126,8 +126,8 @@ extension MutableCollection { } //===----------------------------------------------------------------------===// -// copy(asSuffix:), copy(collectionAsSuffix:), copy(backwards:), -// copy(backwardsFrom:to:) +// overwrite(suffixWith:), overwrite(suffixWithCollection:), overwrite(backwards:), +// overwrite(backwardsFrom:to:) //===----------------------------------------------------------------------===// extension MutableCollection where Self: BidirectionalCollection { @@ -154,8 +154,8 @@ extension MutableCollection where Self: BidirectionalCollection { /// /// - Complexity: O(*n*), where *n* is the length of the shorter sequence /// between `self` and `source`. - public mutating func copy( - asSuffix source: S + public mutating func overwrite( + suffixWith source: S ) -> (copyStart: Index, sourceTail: S.Iterator) where S.Element == Element { var current = endIndex, iterator = source.makeIterator() let start = startIndex @@ -188,8 +188,8 @@ extension MutableCollection where Self: BidirectionalCollection { /// /// - Complexity: O(*n*), where *n* is the length of the shorter collection /// between `self` and `source`. - public mutating func copy( - collectionAsSuffix source: C + public mutating func overwrite( + suffixWithCollection source: C ) -> (copyStart: Index, sourceTailStart: C.Index) where C.Element == Element { var selfIndex = endIndex, sourceIndex = source.startIndex let start = startIndex, sourceEnd = source.endIndex @@ -222,7 +222,7 @@ extension MutableCollection where Self: BidirectionalCollection { /// /// - Complexity: O(*n*), where *n* is the length of the shorter collection /// between `self` and `source`. - public mutating func copy( + public mutating func overwrite( backwards source: C ) -> (writtenStart: Index, readStart: C.Index) where C.Element == Element { var selfIndex = endIndex, sourceIndex = source.endIndex @@ -260,7 +260,7 @@ extension MutableCollection where Self: BidirectionalCollection { /// /// - Complexity: O(*n*), where *n* is the length of the shorter subsequence /// between `self[source]` and `self[destination]`. - public mutating func copy( + public mutating func overwrite( backwardsFrom source: R, to destination: S ) -> (sourceRead: Range, destinationWritten: Range) diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/CopyTests.swift index f9d0c22e..c80bd1c8 100644 --- a/Tests/SwiftAlgorithmsTests/CopyTests.swift +++ b/Tests/SwiftAlgorithmsTests/CopyTests.swift @@ -12,7 +12,7 @@ import XCTest import Algorithms -/// Unit tests for the `copy` methods. +/// Unit tests for the `overwrite` methods. final class CopyTests: XCTestCase { /// Test empty source and destination. func testBothEmpty() { @@ -20,31 +20,31 @@ final class CopyTests: XCTestCase { let empty2 = EmptyCollection() XCTAssertEqualSequences(empty1, []) - let result = empty1.copy(from: empty2) + let result = empty1.overwrite(prefixWith: empty2) XCTAssertEqual(result.copyEnd, empty1.startIndex) XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) XCTAssertEqualSequences(empty1, []) - let result2 = empty1.copy(collection: empty2) + let result2 = empty1.overwrite(prefixWithCollection: empty2) XCTAssertEqual(result2.copyEnd, empty1.startIndex) XCTAssertEqual(result2.sourceTailStart, empty2.startIndex) XCTAssertEqualSequences(empty1, []) XCTAssertEqualSequences(empty1[..() XCTAssertEqualSequences(single, [2.2]) - let result = single.copy(from: empty) + let result = single.overwrite(prefixWith: empty) XCTAssertEqual(result.copyEnd, single.startIndex) XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) XCTAssertEqualSequences(single, [2.2]) - let result2 = single.copy(collection: empty) + let result2 = single.overwrite(prefixWithCollection: empty) XCTAssertEqual(result2.copyEnd, single.startIndex) XCTAssertEqual(result2.sourceTailStart, empty.startIndex) XCTAssertEqualSequences(single, [2.2]) XCTAssertEqualSequences(single[.. XCTAssertEqualSequences(sample, untarnished) - (sRange, dRange) = sample.copy(forwardsFrom: 1..<1, to: 6..<6) + (sRange, dRange) = sample.overwrite(forwardsFrom: 1..<1, to: 6..<6) XCTAssertEqualSequences(sample, untarnished) XCTAssertEqual(sRange, 1..<1) XCTAssertEqual(dRange, 6..<6) // Empty source - (sRange, dRange) = sample.copy(forwardsFrom: 2..<2, to: 7..<8) + (sRange, dRange) = sample.overwrite(forwardsFrom: 2..<2, to: 7..<8) XCTAssertEqualSequences(sample, untarnished) XCTAssertEqual(sRange, 2..<2) XCTAssertEqual(dRange, 7..<7) // Empty destination - (sRange, dRange) = sample.copy(forwardsFrom: 3..<4, to: 9..<9) + (sRange, dRange) = sample.overwrite(forwardsFrom: 3..<4, to: 9..<9) XCTAssertEqualSequences(sample, untarnished) XCTAssertEqual(sRange, 3..<3) XCTAssertEqual(dRange, 9..<9) // Equal nonempty source and destination - (sRange, dRange) = sample.copy(forwardsFrom: 5..<8, to: 5..<8) + (sRange, dRange) = sample.overwrite(forwardsFrom: 5..<8, to: 5..<8) XCTAssertEqualSequences(sample, untarnished) XCTAssertEqual(sRange, 5..<8) XCTAssertEqual(dRange, 5..<8) // Overlapping nonempty source and destination - (sRange, dRange) = sample.copy(forwardsFrom: 5..<9, to: 3..<7) + (sRange, dRange) = sample.overwrite(forwardsFrom: 5..<9, to: 3..<7) XCTAssertEqualSequences(sample, [0, 1, 2, 5, 6, 7, 8, 7, 8, 9]) XCTAssertEqual(sRange, 5..<9) XCTAssertEqual(dRange, 3..<7) // Disjoint but nonempty equal-sized source and destination sample = untarnished - (sRange, dRange) = sample.copy(forwardsFrom: 7..<9, to: 2..<4) + (sRange, dRange) = sample.overwrite(forwardsFrom: 7..<9, to: 2..<4) XCTAssertEqualSequences(sample, [0, 1, 7, 8, 4, 5, 6, 7, 8, 9]) XCTAssertEqual(sRange, 7..<9) XCTAssertEqual(dRange, 2..<4) // Source longer than nonempty destination sample = untarnished - (sRange, dRange) = sample.copy(forwardsFrom: 2..<6, to: 7..<10) + (sRange, dRange) = sample.overwrite(forwardsFrom: 2..<6, to: 7..<10) XCTAssertEqualSequences(sample, [0, 1, 2, 3, 4, 5, 6, 2, 3, 4]) XCTAssertEqual(sRange, 2..<5) XCTAssertEqual(dRange, 7..<10) // Nonempty source shorter than destination sample = untarnished - (sRange, dRange) = sample.copy(forwardsFrom: 5..<7, to: 1..<9) + (sRange, dRange) = sample.overwrite(forwardsFrom: 5..<7, to: 1..<9) XCTAssertEqualSequences(sample, [0, 5, 6, 3, 4, 5, 6, 7, 8, 9]) XCTAssertEqual(sRange, 5..<7) XCTAssertEqual(dRange, 1..<3) // Using expressions other than `Range` sample = untarnished - (sRange, dRange) = sample.copy(forwardsFrom: ..<2, to: 8...) + (sRange, dRange) = sample.overwrite(forwardsFrom: ..<2, to: 8...) XCTAssertEqualSequences(sample, [0, 1, 2, 3, 4, 5, 6, 7, 0, 1]) XCTAssertEqual(sRange, 0..<2) XCTAssertEqual(dRange, 8..<10) @@ -411,59 +411,59 @@ final class CopyTests: XCTestCase { var sample = untarnished, sRange, dRange: Range XCTAssertEqualSequences(sample, untarnished) - (sRange, dRange) = sample.copy(backwardsFrom: 1..<1, to: 6..<6) + (sRange, dRange) = sample.overwrite(backwardsFrom: 1..<1, to: 6..<6) XCTAssertEqualSequences(sample, untarnished) XCTAssertEqual(sRange, 1..<1) XCTAssertEqual(dRange, 6..<6) // Empty source - (sRange, dRange) = sample.copy(backwardsFrom: 2..<2, to: 7..<8) + (sRange, dRange) = sample.overwrite(backwardsFrom: 2..<2, to: 7..<8) XCTAssertEqualSequences(sample, untarnished) XCTAssertEqual(sRange, 2..<2) XCTAssertEqual(dRange, 8..<8) // Empty destination - (sRange, dRange) = sample.copy(backwardsFrom: 3..<4, to: 9..<9) + (sRange, dRange) = sample.overwrite(backwardsFrom: 3..<4, to: 9..<9) XCTAssertEqualSequences(sample, untarnished) XCTAssertEqual(sRange, 4..<4) XCTAssertEqual(dRange, 9..<9) // Equal nonempty source and destination - (sRange, dRange) = sample.copy(backwardsFrom: 5..<8, to: 5..<8) + (sRange, dRange) = sample.overwrite(backwardsFrom: 5..<8, to: 5..<8) XCTAssertEqualSequences(sample, untarnished) XCTAssertEqual(sRange, 5..<8) XCTAssertEqual(dRange, 5..<8) // Overlapping nonempty source and destination - (sRange, dRange) = sample.copy(backwardsFrom: 3..<7, to: 5..<9) + (sRange, dRange) = sample.overwrite(backwardsFrom: 3..<7, to: 5..<9) XCTAssertEqualSequences(sample, [0, 1, 2, 3, 4, 3, 4, 5, 6, 9]) XCTAssertEqual(sRange, 3..<7) XCTAssertEqual(dRange, 5..<9) // Disjoint but nonempty equal-sized source and destination sample = untarnished - (sRange, dRange) = sample.copy(backwardsFrom: 7..<9, to: 2..<4) + (sRange, dRange) = sample.overwrite(backwardsFrom: 7..<9, to: 2..<4) XCTAssertEqualSequences(sample, [0, 1, 7, 8, 4, 5, 6, 7, 8, 9]) XCTAssertEqual(sRange, 7..<9) XCTAssertEqual(dRange, 2..<4) // Source longer than nonempty destination sample = untarnished - (sRange, dRange) = sample.copy(backwardsFrom: 2..<6, to: 7..<10) + (sRange, dRange) = sample.overwrite(backwardsFrom: 2..<6, to: 7..<10) XCTAssertEqualSequences(sample, [0, 1, 2, 3, 4, 5, 6, 3, 4, 5]) XCTAssertEqual(sRange, 3..<6) XCTAssertEqual(dRange, 7..<10) // Nonempty source shorter than destination sample = untarnished - (sRange, dRange) = sample.copy(backwardsFrom: 5..<7, to: 1..<9) + (sRange, dRange) = sample.overwrite(backwardsFrom: 5..<7, to: 1..<9) XCTAssertEqualSequences(sample, [0, 1, 2, 3, 4, 5, 6, 5, 6, 9]) XCTAssertEqual(sRange, 5..<7) XCTAssertEqual(dRange, 7..<9) // Using expressions other than `Range` sample = untarnished - (sRange, dRange) = sample.copy(backwardsFrom: 8..., to: ..<2) + (sRange, dRange) = sample.overwrite(backwardsFrom: 8..., to: ..<2) XCTAssertEqualSequences(sample, [8, 9, 2, 3, 4, 5, 6, 7, 8, 9]) XCTAssertEqual(sRange, 8..<10) XCTAssertEqual(dRange, 0..<2) From 3a9f943c49d2bb1c9a7e8c24aa1ac4ecc7505b7b Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Sat, 26 Dec 2020 12:02:37 -0500 Subject: [PATCH 10/17] Redo documentation Trim down the summary descriptions of this library to minimize (or remove) descriptions of the non-main methods. Ensure the main documentation file ends with a line-breaking sequence. --- CHANGELOG.md | 27 ++++++++++----------------- Guides/Copy.md | 2 +- README.md | 2 +- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83e3a725..794921be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,23 +14,16 @@ package updates, you can specify your package dependency using ### Additions -- The `overwrite(prefixWith:)` method has been added, applying to types conforming to - `MutableCollection`. It takes a sequence with the same element type as its - only parameter, whose elements will be copied on top of the existing - elements. The return values are the past-the-end index in the receiver where - the copying ended and an iterator for the source sequence after the elements - that were copied. The `overwrite(prefixWithCollection:)` method works like the previous - method, but uses a collection as the source, and expresses the unread suffix - for that source as an `Index` instead. The `overwrite(suffixWith:)` and - `overwrite(suffixWithCollection:)` methods work like the first two methods - except the end of the receiver is overwritten instead of the beginning, and - so their return value instead includes the starting index in the receiver - where the copying began. The `overwrite(backwards:)` method works like the - previous method, except the source is also read from the end instead of the - beginning, and so the return values are the starting indices of both - collections' targeted elements. The Swift memory model restricts reading and - writing into the same collection, so the `overwrite(forwardsFrom:to:)` and - `overwrite(backwardsFrom:to:)` methods provide same-collection element copying. +- The `overwrite(prefixWith:)` method has been added to element-mutable + collections. It takes a sequence with the same element type as the receiver; + copies the leading elements from that source on top of the leading elements + of the receiver, in order, until at least one sequence runs out; and returns + the index after the last element of the receiver that was overwritten. The + similar `overwrite(suffixWith:)` method is restricted to bidirectional + element-mutable collections, uses the trailing elements of the receiver as + the destination, and returns the index to the first element of the receiver + that was overwritten instead. There are variants of these methods for when + the source is an iterator, collection, or subsequence. --- diff --git a/Guides/Copy.md b/Guides/Copy.md index b5953c28..40ae06e2 100644 --- a/Guides/Copy.md +++ b/Guides/Copy.md @@ -107,4 +107,4 @@ destination buffers subset the same super-buffer. [C++Copy]: https://en.cppreference.com/w/cpp/algorithm/copy -[C++CopyBackward]: https://en.cppreference.com/w/cpp/algorithm/copy_backward \ No newline at end of file +[C++CopyBackward]: https://en.cppreference.com/w/cpp/algorithm/copy_backward diff --git a/README.md b/README.md index c2d4c416..ea7a414a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`rotate(toStartAt:)`, `rotate(subrange:toStartAt:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Rotate.md): In-place rotation of elements. - [`stablePartition(by:)`, `stablePartition(subrange:by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Partition.md): A partition that preserves the relative order of the resulting prefix and suffix. -- [`overwrite(prefixWith:)`, `overwrite(prefixWithCollection:)`, `overwrite(suffixWith:)`, `overwrite(suffixWithCollection:)`, `overwrite(backwards:)`, `overwrite(forwardsFrom:to:)`, `overwrite(backwardsFrom:to:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. +- [`overwrite(prefixWith:)`, `overwrite(suffixWith:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. #### Combining collections From d92b9d2b68a925624f603409f6693cd99f85392e Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Sat, 26 Dec 2020 12:54:24 -0500 Subject: [PATCH 11/17] Add methods to copy elements from iterators Add method to element-mutable collections that takes an iterator of the same element type to extract enough of its elements for assigning them on top of the receiver's leading elements (in order). The iterator argument is taken as an in-out reference, so its post-call state only points to its unread elements. Copying during the call stops upon either the iterator running out of elements or the receiver running out of untouched elements. The method returns the index to the first element after the overwritten elements, or the past-the-end index if all elements of the receiver were overwritten. For element-mutable collections that also support bidirectional traversal, add another method that takes an iterator in order to extract its elements to copy over existing elements of the receiver, but targets the receiver's trailing elements instead. Elements in the overwritten suffix maintain the same order they had in the source iterator. This method returns the index of the first element that was overwritten, or the past-the-end index if no element of the receiver was touched. --- Guides/Copy.md | 26 ++- Sources/Algorithms/Copy.swift | 139 ++++++++++++- Tests/SwiftAlgorithmsTests/CopyTests.swift | 226 +++++++++++++++++++++ 3 files changed, 381 insertions(+), 10 deletions(-) diff --git a/Guides/Copy.md b/Guides/Copy.md index 40ae06e2..cb78ae09 100644 --- a/Guides/Copy.md +++ b/Guides/Copy.md @@ -15,14 +15,18 @@ print(destination) // "[6, 7, 8, 9, 10]" print(Array(IteratorSequence(sourceSuffix))) // "[]" ``` -`overwrite(prefixWith:)` takes a source sequence and overlays its first *k* elements' -values over the first `k` elements of the receiver, where `k` is the smaller of -the two sequences' lengths. The `overwrite(prefixWithCollection:)` variant uses a collection -for the source sequence. The `overwrite(suffixWith:)` and `overwrite(suffixWithCollection:)` -methods work similar to the first two methods except the last `k` elements of -the receiver are overlaid instead. The `overwrite(backwards:)` method is like the -previous method, except both the source and destination collections are -traversed from the end. +`overwrite(prefixWith:)` takes a source sequence and overlays its first *k* +elements' values over the first `k` elements of the receiver, where `k` is the +smaller of the two sequences' lengths. The `overwrite(prefixWithCollection:)` +variant uses a collection for the source sequence. The +`overwrite(suffixWith:)` and `overwrite(suffixWithCollection:)` methods work +similar to the first two methods except the last `k` elements of the receiver +are overlaid instead. The `overwrite(backwards:)` method is like the previous +method, except both the source and destination collections are traversed from +the end. The `overwrite(prefixUsing:)` and `overwrite(suffixUsing:)` methods +are the core copying routines, extracting elements from their iterator argument +to copy on top of the elements of the targeted end of the receiver, returning +the index of the non-anchored endpoint of the overwritten subsequence. Since the Swift memory model prevents a collection from being used multiple times in code where at least one use is mutable, the `overwrite(forwardsFrom:to:)` @@ -35,6 +39,9 @@ New methods are added to element-mutable collections: ```swift extension MutableCollection { + mutating func overwrite(prefixUsing source: inout I) -> Index + where I : IteratorProtocol, Self.Element == I.Element + mutating func overwrite(prefixWith source: S) -> (copyEnd: Index, sourceTail: S.Iterator) where S.Element == Element @@ -49,6 +56,9 @@ extension MutableCollection { } extension MutableCollection where Self: BidirectionalCollection { + mutating func overwrite(suffixUsing source: inout I) -> Index + where I : IteratorProtocol, Self.Element == I.Element + mutating func overwrite(suffixWith source: S) -> (copyStart: Index, sourceTail: S.Iterator) where S : Sequence, Self.Element == S.Element diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift index ab926433..d709fec4 100644 --- a/Sources/Algorithms/Copy.swift +++ b/Sources/Algorithms/Copy.swift @@ -10,10 +10,77 @@ //===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// -// overwrite(prefixWith:), overwrite(prefixWithCollection:), overwrite(forwardsFrom:to:) +// overwrite(prefixUsing:_:), overwrite(prefixUsing:), overwrite(prefixWith:), +// overwrite(prefixWithCollection:), overwrite(forwardsFrom:to:) //===----------------------------------------------------------------------===// extension MutableCollection { + /// Copies the transformed prefix of the given iterator's virtual sequence on + /// top of the prefix of this collection, using the given closure for mapping. + /// + /// Copying stops when either the iterator runs out of elements or every + /// element of this collection has been overwritten. If you want to limit how + /// much of this collection can be overrun, call this method on the limiting + /// subsequence instead. + /// + /// - Parameters: + /// - source: The iterator with the virtual sequence of the seeds for the + /// replacement values. + /// - transform: The closure mapping seed values to the actual replacement + /// values. + /// - Returns: The index that is one past-the-end of the elements of this + /// collection that were overwritten. It will be `endIndex` if all elements + /// of this collection were touched, but `startIndex` if none were. + /// - Postcondition: Let *k* be the lesser of `count` and the number of + /// elements in `source`'s virtual sequence. Then `prefix(k)` will be + /// equivalent to the first *k* elements emitted from `source` and mapped + /// with `transform`, while `dropFirst(k)` is unchanged. + /// + /// - Complexity: O(*n*), where *n* is is the length of the shorter between + /// `self` and `source`'s virtual sequence. + fileprivate mutating func overwrite( + prefixUsing source: inout I, + _ transform: (I.Element) -> Element + ) -> Index { + var current = startIndex + let end = endIndex + while current < end, let seed = source.next() { + self[current] = transform(seed) + formIndex(after: ¤t) + } + return current + } + + /// Copies the prefix of the given iterator's virtual sequence on top of the + /// prefix of this collection. + /// + /// Copying stops when either the iterator runs out of elements or every + /// element of this collection has been overwritten. If you want to limit how + /// much of this collection can be overrun, call this method on the limiting + /// subsequence instead. + /// + /// - Parameters: + /// - source: The iterator with the virtual sequence of replacement values. + /// - Returns: The index that is one past-the-end of the elements of this + /// collection that were overwritten. It will be `endIndex` if all elements + /// of this collection were touched, but `startIndex` if none were. + /// - Postcondition: Let *k* be the lesser of `count` and the number of + /// elements in `source`'s virtual sequence. Then the next *k* elements + /// from `source` will have been extracted, `prefix(k)` will be equivalent + /// to that extracted sequence (in emission order), and `dropFirst(k)` will + /// be unchanged. + /// + /// - Complexity: O(*n*), where *n* is is the length of the shorter between + /// `self` and `source`'s virtual sequence. + @discardableResult + public mutating func overwrite( + prefixUsing source: inout I + ) -> Index where I.Element == Element { + // The second argument should be "\Element.self," but that's blocked by bug + // SR-12897. + return overwrite(prefixUsing: &source, { $0 }) + } + /// Copies the prefix of the given sequence on top of the prefix of this /// collection. /// @@ -126,11 +193,79 @@ extension MutableCollection { } //===----------------------------------------------------------------------===// -// overwrite(suffixWith:), overwrite(suffixWithCollection:), overwrite(backwards:), +// overwrite(suffixUsing:_:), overwrite(suffixUsing:), overwrite(suffixWith:), +// overwrite(suffixWithCollection:), overwrite(backwards:), // overwrite(backwardsFrom:to:) //===----------------------------------------------------------------------===// extension MutableCollection where Self: BidirectionalCollection { + /// Copies the transformed prefix of the given iterator's virtual sequence on + /// top of the suffix of this collection, using the given closure for mapping. + /// + /// Copying stops when either the iterator runs out of elements or every + /// element of this collection has been overwritten. If you want to limit how + /// much of this collection can be overrun, call this method on the limiting + /// subsequence instead. + /// + /// - Parameters: + /// - source: The iterator with the virtual sequence of the seeds for the + /// replacement values. + /// - transform: The closure mapping seed values to the actual replacement + /// values. + /// - Returns: The index for the first element of this collection that was + /// overwritten. It will be `startIndex` if all elements of this collection + /// were touched, but `endIndex` if none were. + /// - Postcondition: Let *k* be the lesser of `count` and the number of + /// elements in `source`'s virtual sequence. Then `suffix(k)` will be + /// equivalent to the first *k* elements emitted from `source` and mapped + /// with `transform`, while `dropLast(k)` is unchanged. + /// + /// - Complexity: O(*n*), where *n* is is the length of the shorter between + /// `self` and `source`'s virtual sequence. + fileprivate mutating func overwrite( + suffixUsing source: inout I, + _ transform: (I.Element) -> Element + ) -> Index { + var current = endIndex + let start = startIndex + while current > start, let seed = source.next() { + formIndex(before: ¤t) + self[current] = transform(seed) + } + self[current...].reverse() + return current + } + + /// Copies the prefix of the given iterator's virtual sequence on top of the + /// suffix of this collection. + /// + /// Copying stops when either the iterator runs out of elements or every + /// element of this collection has been overwritten. If you want to limit how + /// much of this collection can be overrun, call this method on the limiting + /// subsequence instead. + /// + /// - Parameters: + /// - source: The iterator with the virtual sequence of replacement values. + /// - Returns: The index for the first element of this collection that was + /// overwritten. It will be `startIndex` if all elements of this collection + /// were touched, but `endIndex` if none were. + /// - Postcondition: Let *k* be the lesser of `count` and the number of + /// elements in `source`'s virtual sequence. Then the next *k* elements + /// from `source` will have been extracted, `suffix(k)` will be equivalent + /// to that extracted sequence (in emission order), and `dropLast(k)` will + /// be unchanged. + /// + /// - Complexity: O(*n*), where *n* is is the length of the shorter between + /// `self` and `source`'s virtual sequence. + @discardableResult + public mutating func overwrite( + suffixUsing source: inout I + ) -> Index where I.Element == Element { + // The second argument should be "\Element.self," but that's blocked by bug + // SR-12897. + return overwrite(suffixUsing: &source, { $0 }) + } + /// Copies the prefix of the given sequence on top of the suffix of this /// collection. /// diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/CopyTests.swift index c80bd1c8..53f5f21c 100644 --- a/Tests/SwiftAlgorithmsTests/CopyTests.swift +++ b/Tests/SwiftAlgorithmsTests/CopyTests.swift @@ -14,6 +14,232 @@ import Algorithms /// Unit tests for the `overwrite` methods. final class CopyTests: XCTestCase { + /// Test using an iterator as the source for prefix copying. + func testIteratorSourcePrefix() { + // Empty source and destination + let source1 = EmptyCollection() + var destination1 = source1, iterator1 = source1.makeIterator() + XCTAssertEqualSequences(destination1, []) + + let result1 = destination1.overwrite(prefixUsing: &iterator1) + XCTAssertEqual(result1, destination1.startIndex) + XCTAssertEqualSequences(IteratorSequence(iterator1), []) + XCTAssertEqualSequences(destination1, []) + + // Nonempty source with empty destination + let source2 = CollectionOfOne(1.1) + var destination2 = EmptyCollection(), + iterator2 = source2.makeIterator() + XCTAssertEqualSequences(destination2, []) + + let result2 = destination2.overwrite(prefixUsing: &iterator2) + XCTAssertEqual(result2, destination2.startIndex) + XCTAssertEqualSequences(IteratorSequence(iterator2), [1.1]) + XCTAssertEqualSequences(destination2, []) + + // Empty source with nonempty destination + let source3 = EmptyCollection() + var destination3 = CollectionOfOne(2.2), iterator3 = source3.makeIterator() + XCTAssertEqualSequences(destination3, [2.2]) + + let result3 = destination3.overwrite(prefixUsing: &iterator3) + XCTAssertEqual(result3, destination3.startIndex) + XCTAssertEqualSequences(IteratorSequence(iterator3), []) + XCTAssertEqualSequences(destination3, [2.2]) + + // Two one-element collections + let source4 = CollectionOfOne(3.3) + var destination4 = CollectionOfOne(4.4), iterator4 = source4.makeIterator() + XCTAssertEqualSequences(destination4, [4.4]) + + let result4 = destination4.overwrite(prefixUsing: &iterator4) + XCTAssertEqual(result4, destination4.endIndex) + XCTAssertEqualSequences(IteratorSequence(iterator4), []) + XCTAssertEqualSequences(destination4, [3.3]) + + // Two equal-length multi-element collections + let source5 = 1...5 + var destination5 = Array(6...10), iterator5 = source5.makeIterator() + XCTAssertEqualSequences(destination5, 6...10) + XCTAssertEqual(source5.count, destination5.count) + + let result5 = destination5.overwrite(prefixUsing: &iterator5) + XCTAssertEqual(result5, destination5.endIndex) + XCTAssertEqualSequences(IteratorSequence(iterator5), []) + XCTAssertEqualSequences(destination5, 1...5) + + // Source longer than multi-element destination + let source6 = 10..<20 + var destination6 = Array(1...5), iterator6 = source6.makeIterator() + XCTAssertEqualSequences(destination6, 1...5) + XCTAssertGreaterThan(source6.count, destination6.count) + + let result6 = destination6.overwrite(prefixUsing: &iterator6) + XCTAssertEqual(result6, destination6.endIndex) + XCTAssertEqualSequences(IteratorSequence(iterator6), 15..<20) + XCTAssertEqualSequences(destination6, 10..<15) + + // Multi-element source shorter than destination + let source7 = -5..<1 + var destination7 = Array(0..<10), iterator7 = source7.makeIterator() + XCTAssertEqualSequences(destination7, 0..<10) + XCTAssertLessThan(source7.count, destination7.count) + + let result7 = destination7.overwrite(prefixUsing: &iterator7) + XCTAssertEqual(result7, 6) + XCTAssertEqualSequences(IteratorSequence(iterator7), []) + XCTAssertEqualSequences(destination7, [-5, -4, -3, -2, -1, 0, 6, 7, 8, 9]) + + // Copying over part of the destination + var destination8 = Array("abcdefghijklm") + XCTAssertEqualSequences(destination8, "abcdefghijklm") + + let source8a = EmptyCollection() + var iterator8a = source8a.makeIterator() + let result8a = destination8[3..<7].overwrite(prefixUsing: &iterator8a) + XCTAssertTrue(source8a.isEmpty) + XCTAssertEqual(result8a, 3) + XCTAssertEqualSequences(IteratorSequence(iterator8a), []) + XCTAssertEqualSequences(destination8, "abcdefghijklm") + + let source8b = "12" + var iterator8b = source8b.makeIterator() + let result8b = destination8[3..<7].overwrite(prefixUsing: &iterator8b) + XCTAssertLessThan(source8b.count, destination8[3..<7].count) + XCTAssertEqual(result8b, 5) + XCTAssertEqualSequences(IteratorSequence(iterator8b), []) + XCTAssertEqualSequences(destination8, "abc12fghijklm") + + let source8c = "!@#$" + var iterator8c = source8c.makeIterator() + let result8c = destination8[3..<7].overwrite(prefixUsing: &iterator8c) + XCTAssertEqual(source8c.count, destination8[3..<7].count) + XCTAssertEqual(result8c, 7) + XCTAssertEqualSequences(IteratorSequence(iterator8c), []) + XCTAssertEqualSequences(destination8, "abc!@#$hijklm") + + let source8d = "NOPQRST" + var iterator8d = source8d.makeIterator() + let result8d = destination8[3..<7].overwrite(prefixUsing: &iterator8d) + XCTAssertGreaterThan(source8d.count, destination8[3..<7].count) + XCTAssertEqual(result8d, 7) + XCTAssertEqualSequences(IteratorSequence(iterator8d), "RST") + XCTAssertEqualSequences(destination8, "abcNOPQhijklm") + } + + /// Test using an iterator as the source for suffix copying. + func testIteratorSourceSuffix() { + // Empty source and destination + let source1 = EmptyCollection() + var destination1 = source1, iterator1 = source1.makeIterator() + XCTAssertEqualSequences(destination1, []) + + let result1 = destination1.overwrite(suffixUsing: &iterator1) + XCTAssertEqual(result1, destination1.endIndex) + XCTAssertEqualSequences(IteratorSequence(iterator1), []) + XCTAssertEqualSequences(destination1, []) + + // Nonempty source with empty destination + let source2 = CollectionOfOne(1.1) + var destination2 = EmptyCollection(), + iterator2 = source2.makeIterator() + XCTAssertEqualSequences(destination2, []) + + let result2 = destination2.overwrite(suffixUsing: &iterator2) + XCTAssertEqual(result2, destination2.endIndex) + XCTAssertEqualSequences(IteratorSequence(iterator2), [1.1]) + XCTAssertEqualSequences(destination2, []) + + // Empty source with nonempty destination + let source3 = EmptyCollection() + var destination3 = CollectionOfOne(2.2), iterator3 = source3.makeIterator() + XCTAssertEqualSequences(destination3, [2.2]) + + let result3 = destination3.overwrite(suffixUsing: &iterator3) + XCTAssertEqual(result3, destination3.endIndex) + XCTAssertEqualSequences(IteratorSequence(iterator3), []) + XCTAssertEqualSequences(destination3, [2.2]) + + // Two one-element collections + let source4 = CollectionOfOne(3.3) + var destination4 = CollectionOfOne(4.4), iterator4 = source4.makeIterator() + XCTAssertEqualSequences(destination4, [4.4]) + + let result4 = destination4.overwrite(suffixUsing: &iterator4) + XCTAssertEqual(result4, destination4.startIndex) + XCTAssertEqualSequences(IteratorSequence(iterator4), []) + XCTAssertEqualSequences(destination4, [3.3]) + + // Two equal-length multi-element collections + let source5 = 1...5 + var destination5 = Array(6...10), iterator5 = source5.makeIterator() + XCTAssertEqualSequences(destination5, 6...10) + XCTAssertEqual(source5.count, destination5.count) + + let result5 = destination5.overwrite(suffixUsing: &iterator5) + XCTAssertEqual(result5, destination5.startIndex) + XCTAssertEqualSequences(IteratorSequence(iterator5), []) + XCTAssertEqualSequences(destination5, 1...5) + + // Source longer than multi-element destination + let source6 = 10..<20 + var destination6 = Array(1...5), iterator6 = source6.makeIterator() + XCTAssertEqualSequences(destination6, 1...5) + XCTAssertGreaterThan(source6.count, destination6.count) + + let result6 = destination6.overwrite(suffixUsing: &iterator6) + XCTAssertEqual(result6, destination6.startIndex) + XCTAssertEqualSequences(IteratorSequence(iterator6), 15..<20) + XCTAssertEqualSequences(destination6, 10..<15) + + // Multi-element source shorter than destination + let source7 = -5..<1 + var destination7 = Array(0..<10), iterator7 = source7.makeIterator() + XCTAssertEqualSequences(destination7, 0..<10) + XCTAssertLessThan(source7.count, destination7.count) + + let result7 = destination7.overwrite(suffixUsing: &iterator7) + XCTAssertEqual(result7, 4) + XCTAssertEqualSequences(IteratorSequence(iterator7), []) + XCTAssertEqualSequences(destination7, [0, 1, 2, 3, -5, -4, -3, -2, -1, 0]) + + // Copying over part of the destination + var destination8 = Array("abcdefghijklm") + XCTAssertEqualSequences(destination8, "abcdefghijklm") + + let source8a = EmptyCollection() + var iterator8a = source8a.makeIterator() + let result8a = destination8[3..<7].overwrite(suffixUsing: &iterator8a) + XCTAssertTrue(source8a.isEmpty) + XCTAssertEqual(result8a, 7) + XCTAssertEqualSequences(IteratorSequence(iterator8a), []) + XCTAssertEqualSequences(destination8, "abcdefghijklm") + + let source8b = "12" + var iterator8b = source8b.makeIterator() + let result8b = destination8[3..<7].overwrite(suffixUsing: &iterator8b) + XCTAssertLessThan(source8b.count, destination8[3..<7].count) + XCTAssertEqual(result8b, 5) + XCTAssertEqualSequences(IteratorSequence(iterator8b), []) + XCTAssertEqualSequences(destination8, "abcde12hijklm") + + let source8c = "!@#$" + var iterator8c = source8c.makeIterator() + let result8c = destination8[3..<7].overwrite(suffixUsing: &iterator8c) + XCTAssertEqual(source8c.count, destination8[3..<7].count) + XCTAssertEqual(result8c, 3) + XCTAssertEqualSequences(IteratorSequence(iterator8c), []) + XCTAssertEqualSequences(destination8, "abc!@#$hijklm") + + let source8d = "NOPQRST" + var iterator8d = source8d.makeIterator() + let result8d = destination8[3..<7].overwrite(suffixUsing: &iterator8d) + XCTAssertGreaterThan(source8d.count, destination8[3..<7].count) + XCTAssertEqual(result8d, 3) + XCTAssertEqualSequences(IteratorSequence(iterator8d), "RST") + XCTAssertEqualSequences(destination8, "abcNOPQhijklm") + } + /// Test empty source and destination. func testBothEmpty() { var empty1 = EmptyCollection() From 501154c6270ebfa9ccfd91591a3e637ef177a2f6 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Mon, 28 Dec 2020 00:59:02 -0500 Subject: [PATCH 12/17] Simplify copying from a sequence Move the secret implementation methods to the end of their file. Change the methods that copy from a sequence to return only the index of the first untouched element of the receiver. (Use the iterator-based overload to track unread elements from the source.) Move the location of the revised tests in their file. Update the documentation, including the prime example. --- Guides/Copy.md | 19 +- Sources/Algorithms/Copy.swift | 216 ++++++++++--------- Tests/SwiftAlgorithmsTests/CopyTests.swift | 228 ++++++++++++--------- 3 files changed, 250 insertions(+), 213 deletions(-) diff --git a/Guides/Copy.md b/Guides/Copy.md index cb78ae09..2788569f 100644 --- a/Guides/Copy.md +++ b/Guides/Copy.md @@ -6,13 +6,13 @@ Copy a sequence onto an element-mutable collection. ```swift -var destination = [1, 2, 3, 4, 5] -let source = [6, 7, 8, 9, 10] -print(destination) // "[1, 2, 3, 4, 5] +var destination = Array("abcde") +print(String(destination)) // "abcde" -let (_, sourceSuffix) = destination.overwrite(prefixWith: source) -print(destination) // "[6, 7, 8, 9, 10]" -print(Array(IteratorSequence(sourceSuffix))) // "[]" +let source = "123" +let changedEnd = destination.overwrite(prefixWith: source) +print(String(destination)) // "123de" +print(String(destination[changedEnd...])) // "de" ``` `overwrite(prefixWith:)` takes a source sequence and overlays its first *k* @@ -42,8 +42,8 @@ extension MutableCollection { mutating func overwrite(prefixUsing source: inout I) -> Index where I : IteratorProtocol, Self.Element == I.Element - mutating func overwrite(prefixWith source: S) - -> (copyEnd: Index, sourceTail: S.Iterator) where S.Element == Element + mutating func overwrite(prefixWith source: S) -> Index + where S : Sequence, Self.Element == S.Element mutating func overwrite(prefixWithCollection collection: C) -> (copyEnd: Index, sourceTailStart: C.Index) @@ -59,8 +59,7 @@ extension MutableCollection where Self: BidirectionalCollection { mutating func overwrite(suffixUsing source: inout I) -> Index where I : IteratorProtocol, Self.Element == I.Element - mutating func overwrite(suffixWith source: S) - -> (copyStart: Index, sourceTail: S.Iterator) + mutating func overwrite(suffixWith source: S) -> Index where S : Sequence, Self.Element == S.Element mutating func overwrite(suffixWithCollection source: C) diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift index d709fec4..2aad393a 100644 --- a/Sources/Algorithms/Copy.swift +++ b/Sources/Algorithms/Copy.swift @@ -10,47 +10,11 @@ //===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// -// overwrite(prefixUsing:_:), overwrite(prefixUsing:), overwrite(prefixWith:), +// overwrite(prefixUsing:), overwrite(prefixWith:), // overwrite(prefixWithCollection:), overwrite(forwardsFrom:to:) //===----------------------------------------------------------------------===// extension MutableCollection { - /// Copies the transformed prefix of the given iterator's virtual sequence on - /// top of the prefix of this collection, using the given closure for mapping. - /// - /// Copying stops when either the iterator runs out of elements or every - /// element of this collection has been overwritten. If you want to limit how - /// much of this collection can be overrun, call this method on the limiting - /// subsequence instead. - /// - /// - Parameters: - /// - source: The iterator with the virtual sequence of the seeds for the - /// replacement values. - /// - transform: The closure mapping seed values to the actual replacement - /// values. - /// - Returns: The index that is one past-the-end of the elements of this - /// collection that were overwritten. It will be `endIndex` if all elements - /// of this collection were touched, but `startIndex` if none were. - /// - Postcondition: Let *k* be the lesser of `count` and the number of - /// elements in `source`'s virtual sequence. Then `prefix(k)` will be - /// equivalent to the first *k* elements emitted from `source` and mapped - /// with `transform`, while `dropFirst(k)` is unchanged. - /// - /// - Complexity: O(*n*), where *n* is is the length of the shorter between - /// `self` and `source`'s virtual sequence. - fileprivate mutating func overwrite( - prefixUsing source: inout I, - _ transform: (I.Element) -> Element - ) -> Index { - var current = startIndex - let end = endIndex - while current < end, let seed = source.next() { - self[current] = transform(seed) - formIndex(after: ¤t) - } - return current - } - /// Copies the prefix of the given iterator's virtual sequence on top of the /// prefix of this collection. /// @@ -86,32 +50,28 @@ extension MutableCollection { /// /// Copying stops when the end of the shorter sequence is reached. If you /// want to limit how much of this collection can be overrun, call this method - /// on the limiting subsequence instead. + /// on the limiting subsequence instead. If you need access to the elements + /// of `source` that were not read, make an iterator from `source` and call + /// `overwrite(prefixUsing:)` instead. /// /// - Parameters: /// - source: The sequence to read the replacement values from. - /// - Returns: A two-member tuple where the first member is the index of the - /// first element of this collection that was not assigned a copy. It will - /// be `startIndex` if no copying was done and `endIndex` if every element - /// was written over. The second member is an iterator covering all the - /// elements of `source` that where not used as part of the copying. It - /// will be empty if every element was used. + /// - Returns: The index after the last element of the overwritten prefix. It + /// will be `endIndex` if every element of this collection was touched, but + /// `startIndex` if none were. /// - Postcondition: Let *k* be the element count of the shorter of `self` and /// `source`. Then `prefix(k)` will be equivalent to `source.prefix(k)`, - /// while `dropFirst(k)` is unchanged. + /// while `dropFirst(k)` will be unchanged. /// /// - Complexity: O(*n*), where *n* is the length of the shorter sequence /// between `self` and `source`. + @discardableResult + @inlinable public mutating func overwrite( prefixWith source: S - ) -> (copyEnd: Index, sourceTail: S.Iterator) where S.Element == Element { - var current = startIndex, iterator = source.makeIterator() - let end = endIndex - while current < end, let source = iterator.next() { - self[current] = source - formIndex(after: ¤t) - } - return (current, iterator) + ) -> Index where S.Element == Element { + var iterator = source.makeIterator() + return overwrite(prefixUsing: &iterator) } /// Copies the prefix of the given collection on top of the prefix of this @@ -193,49 +153,12 @@ extension MutableCollection { } //===----------------------------------------------------------------------===// -// overwrite(suffixUsing:_:), overwrite(suffixUsing:), overwrite(suffixWith:), +// overwrite(suffixUsing:), overwrite(suffixWith:), // overwrite(suffixWithCollection:), overwrite(backwards:), // overwrite(backwardsFrom:to:) //===----------------------------------------------------------------------===// extension MutableCollection where Self: BidirectionalCollection { - /// Copies the transformed prefix of the given iterator's virtual sequence on - /// top of the suffix of this collection, using the given closure for mapping. - /// - /// Copying stops when either the iterator runs out of elements or every - /// element of this collection has been overwritten. If you want to limit how - /// much of this collection can be overrun, call this method on the limiting - /// subsequence instead. - /// - /// - Parameters: - /// - source: The iterator with the virtual sequence of the seeds for the - /// replacement values. - /// - transform: The closure mapping seed values to the actual replacement - /// values. - /// - Returns: The index for the first element of this collection that was - /// overwritten. It will be `startIndex` if all elements of this collection - /// were touched, but `endIndex` if none were. - /// - Postcondition: Let *k* be the lesser of `count` and the number of - /// elements in `source`'s virtual sequence. Then `suffix(k)` will be - /// equivalent to the first *k* elements emitted from `source` and mapped - /// with `transform`, while `dropLast(k)` is unchanged. - /// - /// - Complexity: O(*n*), where *n* is is the length of the shorter between - /// `self` and `source`'s virtual sequence. - fileprivate mutating func overwrite( - suffixUsing source: inout I, - _ transform: (I.Element) -> Element - ) -> Index { - var current = endIndex - let start = startIndex - while current > start, let seed = source.next() { - formIndex(before: ¤t) - self[current] = transform(seed) - } - self[current...].reverse() - return current - } - /// Copies the prefix of the given iterator's virtual sequence on top of the /// suffix of this collection. /// @@ -272,34 +195,28 @@ extension MutableCollection where Self: BidirectionalCollection { /// Copying stops when either the sequence is exhausted or every element of /// this collection is touched. If you want to limit how much of this /// collection can be overrun, call this method on the limiting subsequence - /// instead. The elements in the mutated suffix preserve the order they had - /// in `source`. + /// instead. If you need access to the elements of `source` that were not + /// read, make an iterator from `source` and call `overwrite(suffixUsing:)` + /// instead. /// /// - Parameters: /// - source: The sequence to read the replacement values from. - /// - Returns: A two-member tuple where the first member is the index of the - /// earliest element of this collection that was assigned a copy. It will - /// be `endIndex` if no copying was done and `startIndex` if every element - /// was written over. The second member is an iterator covering all the - /// elements of `source` that where not used as part of the copying. It - /// will be empty if every element was used. + /// - Returns: The index for the first element of the overwritten sufffix. It + /// will be `startIndex` if every element of this collection was touched, + /// but `endIndex` if none were. /// - Postcondition: Let *k* be the element count of the shorter of `self` and /// `source`. Then `suffix(k)` will be equivalent to `source.prefix(k)`, - /// while `dropLast(k)` is unchanged. + /// while `dropLast(k)` will be unchanged. /// /// - Complexity: O(*n*), where *n* is the length of the shorter sequence /// between `self` and `source`. + @discardableResult + @inlinable public mutating func overwrite( suffixWith source: S - ) -> (copyStart: Index, sourceTail: S.Iterator) where S.Element == Element { - var current = endIndex, iterator = source.makeIterator() - let start = startIndex - while current > start, let source = iterator.next() { - formIndex(before: ¤t) - self[current] = source - } - self[current...].reverse() - return (current, iterator) + ) -> Index where S.Element == Element { + var iterator = source.makeIterator() + return overwrite(suffixUsing: &iterator) } /// Copies the prefix of the given collection on top of the suffix of this @@ -413,3 +330,84 @@ extension MutableCollection where Self: BidirectionalCollection { destinationIndex ..< rangeD.upperBound) } } + +//===----------------------------------------------------------------------===// +// overwrite(prefixUsing:_:), overwrite(suffixUsing:_:) +//===----------------------------------------------------------------------===// + +fileprivate extension MutableCollection { + /// Copies the transformed prefix of the given iterator's virtual sequence on + /// top of the prefix of this collection, using the given closure for mapping. + /// + /// Copying stops when either the iterator runs out of elements or every + /// element of this collection has been overwritten. If you want to limit how + /// much of this collection can be overrun, call this method on the limiting + /// subsequence instead. + /// + /// - Parameters: + /// - source: The iterator with the virtual sequence of the seeds for the + /// replacement values. + /// - transform: The closure mapping seed values to the actual replacement + /// values. + /// - Returns: The index that is one past-the-end of the elements of this + /// collection that were overwritten. It will be `endIndex` if all elements + /// of this collection were touched, but `startIndex` if none were. + /// - Postcondition: Let *k* be the lesser of `count` and the number of + /// elements in `source`'s virtual sequence. Then `prefix(k)` will be + /// equivalent to the first *k* elements emitted from `source` and mapped + /// with `transform`, while `dropFirst(k)` is unchanged. + /// + /// - Complexity: O(*n*), where *n* is is the length of the shorter between + /// `self` and `source`'s virtual sequence. + mutating func overwrite( + prefixUsing source: inout I, + _ transform: (I.Element) -> Element + ) -> Index { + var current = startIndex + let end = endIndex + while current < end, let seed = source.next() { + self[current] = transform(seed) + formIndex(after: ¤t) + } + return current + } +} + +fileprivate extension MutableCollection where Self: BidirectionalCollection { + /// Copies the transformed prefix of the given iterator's virtual sequence on + /// top of the suffix of this collection, using the given closure for mapping. + /// + /// Copying stops when either the iterator runs out of elements or every + /// element of this collection has been overwritten. If you want to limit how + /// much of this collection can be overrun, call this method on the limiting + /// subsequence instead. + /// + /// - Parameters: + /// - source: The iterator with the virtual sequence of the seeds for the + /// replacement values. + /// - transform: The closure mapping seed values to the actual replacement + /// values. + /// - Returns: The index for the first element of this collection that was + /// overwritten. It will be `startIndex` if all elements of this collection + /// were touched, but `endIndex` if none were. + /// - Postcondition: Let *k* be the lesser of `count` and the number of + /// elements in `source`'s virtual sequence. Then `suffix(k)` will be + /// equivalent to the first *k* elements emitted from `source` and mapped + /// with `transform`, while `dropLast(k)` is unchanged. + /// + /// - Complexity: O(*n*), where *n* is is the length of the shorter between + /// `self` and `source`'s virtual sequence. + mutating func overwrite( + suffixUsing source: inout I, + _ transform: (I.Element) -> Element + ) -> Index { + var current = endIndex + let start = startIndex + while current > start, let seed = source.next() { + formIndex(before: ¤t) + self[current] = transform(seed) + } + self[current...].reverse() + return current + } +} diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/CopyTests.swift index 53f5f21c..001e9a5b 100644 --- a/Tests/SwiftAlgorithmsTests/CopyTests.swift +++ b/Tests/SwiftAlgorithmsTests/CopyTests.swift @@ -127,6 +127,70 @@ final class CopyTests: XCTestCase { XCTAssertEqualSequences(destination8, "abcNOPQhijklm") } + /// Test using a sequence as the source for prefix copying. + func testSequenceSourcePrefix() { + // Empty source and destination + var destination1 = EmptyCollection() + XCTAssertEqualSequences(destination1, []) + XCTAssertEqual(destination1.overwrite(prefixWith: EmptyCollection()), + destination1.startIndex) + XCTAssertEqualSequences(destination1, []) + + // Nonempty source with empty destination + var destination2 = EmptyCollection() + XCTAssertEqualSequences(destination2, []) + XCTAssertEqual(destination2.overwrite(prefixWith: CollectionOfOne(1.1)), + destination2.startIndex) + XCTAssertEqualSequences(destination2, []) + + // Empty source with nonempty destination + var destination3 = CollectionOfOne(2.2) + XCTAssertEqualSequences(destination3, [2.2]) + XCTAssertEqual(destination3.overwrite(prefixWith: EmptyCollection()), + destination3.startIndex) + XCTAssertEqualSequences(destination3, [2.2]) + + // Two one-element collections + var destination4 = CollectionOfOne(3.3) + XCTAssertEqualSequences(destination4, [3.3]) + XCTAssertEqual(destination4.overwrite(prefixWith: CollectionOfOne(4.4)), + destination4.endIndex) + XCTAssertEqualSequences(destination4, [4.4]) + + // Two equal-length multi-element collections + var destination5 = Array(6...10) + XCTAssertEqualSequences(destination5, 6...10) + XCTAssertEqual(destination5.overwrite(prefixWith: 1...5), + destination5.endIndex) + XCTAssertEqualSequences(destination5, 1...5) + + // Source longer than multi-element destination + var destination6 = Array(1...5) + XCTAssertEqualSequences(destination6, 1...5) + XCTAssertEqual(destination6.overwrite(prefixWith: 10..<20), + destination6.endIndex) + XCTAssertEqualSequences(destination6, 10..<15) + + // Multi-element source shorter than destination + var destination7 = Array(0..<10) + XCTAssertEqualSequences(destination7, 0..<10) + XCTAssertEqual(destination7.overwrite(prefixWith: -5..<1), 6) + XCTAssertEqualSequences(destination7, [-5, -4, -3, -2, -1, 0, 6, 7, 8, 9]) + + // Copying over part of the destination + var destination8 = Array("abcdefghijklm") + XCTAssertEqualSequences(destination8, "abcdefghijklm") + XCTAssertEqual(destination8[3..<7].overwrite(prefixWith: EmptyCollection()), + 3) + XCTAssertEqualSequences(destination8, "abcdefghijklm") + XCTAssertEqual(destination8[3..<7].overwrite(prefixWith: "12"), 5) + XCTAssertEqualSequences(destination8, "abc12fghijklm") + XCTAssertEqual(destination8[3..<7].overwrite(prefixWith: "!@#$"), 7) + XCTAssertEqualSequences(destination8, "abc!@#$hijklm") + XCTAssertEqual(destination8[3..<7].overwrite(prefixWith: "NOPQRST"), 7) + XCTAssertEqualSequences(destination8, "abcNOPQhijklm") + } + /// Test using an iterator as the source for suffix copying. func testIteratorSourceSuffix() { // Empty source and destination @@ -240,17 +304,75 @@ final class CopyTests: XCTestCase { XCTAssertEqualSequences(destination8, "abcNOPQhijklm") } + /// Test using a sequence as the source for suffix copying. + func testSequenceSourceSuffix() { + // Empty source and destination + var destination1 = EmptyCollection() + XCTAssertEqualSequences(destination1, []) + XCTAssertEqual(destination1.overwrite(suffixWith: []), + destination1.endIndex) + XCTAssertEqualSequences(destination1, []) + + // Nonempty source with empty destination + var destination2 = EmptyCollection() + XCTAssertEqualSequences(destination2, []) + XCTAssertEqual(destination2.overwrite(suffixWith: CollectionOfOne(1.1)), + destination2.endIndex) + XCTAssertEqualSequences(destination2, []) + + // Empty source with nonempty destination + var destination3 = CollectionOfOne(2.2) + XCTAssertEqualSequences(destination3, [2.2]) + XCTAssertEqual(destination3.overwrite(suffixWith: []), + destination3.endIndex) + XCTAssertEqualSequences(destination3, [2.2]) + + // Two one-element collections + var destination4 = CollectionOfOne(4.4) + XCTAssertEqualSequences(destination4, [4.4]) + XCTAssertEqual(destination4.overwrite(suffixWith: CollectionOfOne(3.3)), + destination4.startIndex) + XCTAssertEqualSequences(destination4, [3.3]) + + // Two equal-length multi-element collections + var destination5 = Array(6...10) + XCTAssertEqualSequences(destination5, 6...10) + XCTAssertEqual(destination5.overwrite(suffixWith: 1...5), + destination5.startIndex) + XCTAssertEqualSequences(destination5, 1...5) + + // Source longer than multi-element destination + var destination6 = Array(1...5) + XCTAssertEqualSequences(destination6, 1...5) + XCTAssertEqual(destination6.overwrite(suffixWith: 10..<20), + destination6.startIndex) + XCTAssertEqualSequences(destination6, 10..<15) + + // Multi-element source shorter than destination + var destination7 = Array(0..<10) + XCTAssertEqualSequences(destination7, 0..<10) + XCTAssertEqual(destination7.overwrite(suffixWith: -5..<1), 4) + XCTAssertEqualSequences(destination7, [0, 1, 2, 3, -5, -4, -3, -2, -1, 0]) + + // Copying over part of the destination + var destination8 = Array("abcdefghijklm") + XCTAssertEqualSequences(destination8, "abcdefghijklm") + XCTAssertEqual(destination8[3..<7].overwrite(suffixWith: []), 7) + XCTAssertEqualSequences(destination8, "abcdefghijklm") + XCTAssertEqual(destination8[3..<7].overwrite(suffixWith: "12"), 5) + XCTAssertEqualSequences(destination8, "abcde12hijklm") + XCTAssertEqual(destination8[3..<7].overwrite(suffixWith: "!@#$"), 3) + XCTAssertEqualSequences(destination8, "abc!@#$hijklm") + XCTAssertEqual(destination8[3..<7].overwrite(suffixWith: "NOPQRST"), 3) + XCTAssertEqualSequences(destination8, "abcNOPQhijklm") + } + /// Test empty source and destination. func testBothEmpty() { var empty1 = EmptyCollection() let empty2 = EmptyCollection() XCTAssertEqualSequences(empty1, []) - let result = empty1.overwrite(prefixWith: empty2) - XCTAssertEqual(result.copyEnd, empty1.startIndex) - XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) - XCTAssertEqualSequences(empty1, []) - let result2 = empty1.overwrite(prefixWithCollection: empty2) XCTAssertEqual(result2.copyEnd, empty1.startIndex) XCTAssertEqual(result2.sourceTailStart, empty2.startIndex) @@ -258,11 +380,6 @@ final class CopyTests: XCTestCase { XCTAssertEqualSequences(empty1[..() XCTAssertEqualSequences(single, [2.2]) - let result = single.overwrite(prefixWith: empty) - XCTAssertEqual(result.copyEnd, single.startIndex) - XCTAssertEqualSequences(IteratorSequence(result.sourceTail), []) - XCTAssertEqualSequences(single, [2.2]) - let result2 = single.overwrite(prefixWithCollection: empty) XCTAssertEqual(result2.copyEnd, single.startIndex) XCTAssertEqual(result2.sourceTailStart, empty.startIndex) @@ -334,11 +436,6 @@ final class CopyTests: XCTestCase { XCTAssertEqualSequences(single[.. Date: Wed, 30 Dec 2020 18:14:43 -0500 Subject: [PATCH 13/17] Add direction parameter when copying from an iterator For the secret implementation to copy from an iterator to the suffix of a collection, add a parameter controlling which direction copying occurs. Now the replaced elements may stay in their original order, or be stored in reverse. --- Sources/Algorithms/Copy.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift index 2aad393a..9c7fd2ab 100644 --- a/Sources/Algorithms/Copy.swift +++ b/Sources/Algorithms/Copy.swift @@ -186,7 +186,7 @@ extension MutableCollection where Self: BidirectionalCollection { ) -> Index where I.Element == Element { // The second argument should be "\Element.self," but that's blocked by bug // SR-12897. - return overwrite(suffixUsing: &source, { $0 }) + return overwrite(suffixUsing: &source, doCorrect: true, { $0 }) } /// Copies the prefix of the given sequence on top of the suffix of this @@ -332,7 +332,7 @@ extension MutableCollection where Self: BidirectionalCollection { } //===----------------------------------------------------------------------===// -// overwrite(prefixUsing:_:), overwrite(suffixUsing:_:) +// overwrite(prefixUsing:_:), overwrite(suffixUsing:doCorrect:_:) //===----------------------------------------------------------------------===// fileprivate extension MutableCollection { @@ -385,6 +385,8 @@ fileprivate extension MutableCollection where Self: BidirectionalCollection { /// - Parameters: /// - source: The iterator with the virtual sequence of the seeds for the /// replacement values. + /// - doCorrect: Whether to reverse the elements after replacement so their + /// order maintains their orientation from `source`, or not. /// - transform: The closure mapping seed values to the actual replacement /// values. /// - Returns: The index for the first element of this collection that was @@ -399,6 +401,7 @@ fileprivate extension MutableCollection where Self: BidirectionalCollection { /// `self` and `source`'s virtual sequence. mutating func overwrite( suffixUsing source: inout I, + doCorrect: Bool, _ transform: (I.Element) -> Element ) -> Index { var current = endIndex @@ -407,7 +410,9 @@ fileprivate extension MutableCollection where Self: BidirectionalCollection { formIndex(before: ¤t) self[current] = transform(seed) } - self[current...].reverse() + if doCorrect { + self[current...].reverse() + } return current } } From 27b61e2ea963ad264e3e91b7115b0eff276b0213 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Thu, 31 Dec 2020 02:13:01 -0500 Subject: [PATCH 14/17] Replace methods to copy from collections Change the name, return order, and implementation of the method of element-mutable collections that can replace their prefix with elements from a collection. Do the same thing for the method that copies across collection suffixes backwards, while removing the method that copies a collection's prefix to another's suffix. --- Guides/Copy.md | 74 ++-- Sources/Algorithms/Copy.swift | 113 ++---- Tests/SwiftAlgorithmsTests/CopyTests.swift | 450 ++++++++++----------- 3 files changed, 295 insertions(+), 342 deletions(-) diff --git a/Guides/Copy.md b/Guides/Copy.md index 2788569f..3767b0a2 100644 --- a/Guides/Copy.md +++ b/Guides/Copy.md @@ -15,23 +15,21 @@ print(String(destination)) // "123de" print(String(destination[changedEnd...])) // "de" ``` -`overwrite(prefixWith:)` takes a source sequence and overlays its first *k* -elements' values over the first `k` elements of the receiver, where `k` is the -smaller of the two sequences' lengths. The `overwrite(prefixWithCollection:)` -variant uses a collection for the source sequence. The -`overwrite(suffixWith:)` and `overwrite(suffixWithCollection:)` methods work -similar to the first two methods except the last `k` elements of the receiver -are overlaid instead. The `overwrite(backwards:)` method is like the previous -method, except both the source and destination collections are traversed from -the end. The `overwrite(prefixUsing:)` and `overwrite(suffixUsing:)` methods -are the core copying routines, extracting elements from their iterator argument -to copy on top of the elements of the targeted end of the receiver, returning -the index of the non-anchored endpoint of the overwritten subsequence. - -Since the Swift memory model prevents a collection from being used multiple -times in code where at least one use is mutable, the `overwrite(forwardsFrom:to:)` -and `overwrite(backwardsFrom:to:)` methods permit copying elements across -subsequences of the same collection. +`overwrite(prefixWith:)` takes a source sequence and replaces the first `k` +elements of the receiver with the first `k` elements of the source, where *k* +is the length of the shorter sequence. `overwrite(forwardsWith:)` does the same +thing with a source collection, and `overwrite(prefixUsing:)` with an `inout` +source iterator. To preserve memory exclusivity, the +`overwrite(forwardsFrom:to:)` overload is required to copy between subsequences +of the same collection, where the source and destination are given as index +ranges. + +When the receiving element-mutable collection supports bidirectional traversal, +variants of the previous methods are defined that copy the source elements on +top of the receiver's suffix instead. The `overwrite(suffixWith:)` and +`overwrite(suffixUsing:)` methods use their source's prefix, while the +`overwrite(backwardsWith:)` and `overwrite(backwardsFrom:to:)` methods use +their source's suffix. ## Detailed Design @@ -45,8 +43,8 @@ extension MutableCollection { mutating func overwrite(prefixWith source: S) -> Index where S : Sequence, Self.Element == S.Element - mutating func overwrite(prefixWithCollection collection: C) - -> (copyEnd: Index, sourceTailStart: C.Index) + mutating func overwrite(forwardsWith source: C) + -> (readEnd: C.Index, writtenEnd: Index) where C : Collection, Self.Element == C.Element mutating func overwrite(forwardsFrom source: R, to destination: S) @@ -62,13 +60,9 @@ extension MutableCollection where Self: BidirectionalCollection { mutating func overwrite(suffixWith source: S) -> Index where S : Sequence, Self.Element == S.Element - mutating func overwrite(suffixWithCollection source: C) - -> (copyStart: Index, sourceTailStart: C.Index) - where C : Collection, Self.Element == C.Element - - mutating func overwrite(backwards source: C) - -> (writtenStart: Index, readStart: C.Index) - where C : BidirectionalCollection, Self.Element == C.Element + mutating func overwrite(backwardsWith source: C) + -> (readStart: C.Index, writtenStart: Index) + where C : BidirectionalCollection, Self.Element == C.Element mutating func overwrite(backwardsFrom source: R, to destination: S) -> (sourceRead: Range, destinationWritten: Range) @@ -77,20 +71,28 @@ extension MutableCollection where Self: BidirectionalCollection { } ``` -Each method returns two values. For the two-sequence methods, the first member -is the index for the non-endpoint bound for the destination adfix. For the -two-sequence methods where the non-receiver is a `Sequence`, the second member -is an iterator for the elements of the source's suffix that were never read in -for copying. For the two-sequence methods where the non-receiver is a -`Collection`, the second member is the index for the first element of the -source's suffix that was never read in for copying. For the two-subsequences -methods, the members are the ranges for the parts of the subsequence operands -that were actually touched during copying. +When the source is an iterator or sequence, the return value from `overwrite` +is a single index value within the receiver that is the non-anchored end of the +range of overwritten elements. The prefix-overwriting methods return the upper +bound, *i.e.* the index after the last touched element, and assume the lower +bound is the receiver's `startIndex`. The suffix-overwriting methods return the +lower bound, *i.e.* the index of the first touched element, and assume the +upper bound is the receiver's `endIndex`. Use of the return value is optional +to support casual use of copying without caring about the precise range of +effect. + +When the source is a collection, the return value from `overwrite` has two +components. The second component is the same as the sole value returned from +the overloads with iterator or sequence sources. The first component is the +non-anchored end of the range of the elements actually read from the source. +When the source is a subsequence, the return value's components are index +ranges fully bounding the touched elements instead of ranges implied from +isolated indices. ### Complexity Calling these methods is O(_k_), where _k_ is the length of the shorter -sequence between the receiver and `source`. +(virtual) sequence between the receiver (subsequence) and the source. ### Naming diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Copy.swift index 9c7fd2ab..b4d64c32 100644 --- a/Sources/Algorithms/Copy.swift +++ b/Sources/Algorithms/Copy.swift @@ -10,8 +10,8 @@ //===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// -// overwrite(prefixUsing:), overwrite(prefixWith:), -// overwrite(prefixWithCollection:), overwrite(forwardsFrom:to:) +// overwrite(prefixUsing:), overwrite(prefixWith:), overwrite(forwardsWith:), +// overwrite(forwardsFrom:to:) //===----------------------------------------------------------------------===// extension MutableCollection { @@ -82,30 +82,26 @@ extension MutableCollection { /// on the limiting subsequence instead. /// /// - Parameters: - /// - collection: The collection to read the replacement values from. - /// - Returns: A two-member tuple where the first member is the index of the - /// first element of this collection that was not assigned a copy and the - /// second member is the index of the first element of `collection` that was - /// not used for the source of a copy. They will be their collection's - /// `startIndex` if no copying was done and their collection's `endIndex` if - /// every element of that collection participated in a copy. - /// - Postcondition: Let *k* be the element count of the shorter of `self` and - /// `collection`. Then `prefix(k)` will be equivalent to - /// `collection.prefix(k)`, while `dropFirst(k)` is unchanged. + /// - source: The collection to read the replacement values from. + /// - Returns: A two-member tuple where the first member is the past-the-end + /// index for the range of `source` elements that were actually read and the + /// second member is the past-the-end index for the range of `self` elements + /// that were actually overwritten. The lower bound for each range is the + /// corresponding collection's `startIndex`. + /// - Postcondition: Let *r* be the returned value from the call to this + /// method. Then `self[..( - prefixWithCollection collection: C - ) -> (copyEnd: Index, sourceTailStart: C.Index) where C.Element == Element { - var selfIndex = startIndex, collectionIndex = collection.startIndex - let end = endIndex, sourceEnd = collection.endIndex - while selfIndex < end, collectionIndex < sourceEnd { - self[selfIndex] = collection[collectionIndex] - formIndex(after: &selfIndex) - collection.formIndex(after: &collectionIndex) - } - return (selfIndex, collectionIndex) + forwardsWith source: C + ) -> (readEnd: C.Index, writtenEnd: Index) + where C.Element == Element { + var indexIterator = source.indices.makeIterator() + let end = overwrite(prefixUsing: &indexIterator) { source[$0] } + return (indexIterator.next() ?? source.endIndex, end) } /// Copies, in forward traversal, the prefix of a subsequence on top of the @@ -153,8 +149,7 @@ extension MutableCollection { } //===----------------------------------------------------------------------===// -// overwrite(suffixUsing:), overwrite(suffixWith:), -// overwrite(suffixWithCollection:), overwrite(backwards:), +// overwrite(suffixUsing:), overwrite(suffixWith:), overwrite(backwardsWith:), // overwrite(backwardsFrom:to:) //===----------------------------------------------------------------------===// @@ -219,41 +214,6 @@ extension MutableCollection where Self: BidirectionalCollection { return overwrite(suffixUsing: &iterator) } - /// Copies the prefix of the given collection on top of the suffix of this - /// collection. - /// - /// Copying stops when at least one of the collections has had all of its - /// elements touched. If you want to limit how much of this collection can be - /// overrun, call this method on the limiting subsequence instead. The - /// elements in the mutated suffix preserve the order they had in `source`. - /// - /// - Parameters: - /// - source: The collection to read the replacement values from. - /// - Returns: A two-member tuple. The first member is the index of the - /// earliest element of this collection that was assigned a copy; or - /// `endIndex` if no copying was done. The second member is the index - /// immediately after the latest element of `source` read for a copy; or - /// `startIndex` if no copying was done. - /// - Postcondition: Let *k* be the element count of the shorter of `self` and - /// `source`. Then `suffix(k)` will be equivalent to `source.prefix(k)`, - /// while `dropLast(k)` is unchanged. - /// - /// - Complexity: O(*n*), where *n* is the length of the shorter collection - /// between `self` and `source`. - public mutating func overwrite( - suffixWithCollection source: C - ) -> (copyStart: Index, sourceTailStart: C.Index) where C.Element == Element { - var selfIndex = endIndex, sourceIndex = source.startIndex - let start = startIndex, sourceEnd = source.endIndex - while selfIndex > start, sourceIndex < sourceEnd { - formIndex(before: &selfIndex) - self[selfIndex] = source[sourceIndex] - source.formIndex(after: &sourceIndex) - } - self[selfIndex...].reverse() - return (selfIndex, sourceIndex) - } - /// Copies the suffix of the given collection on top of the suffix of this /// collection. /// @@ -263,28 +223,27 @@ extension MutableCollection where Self: BidirectionalCollection { /// /// - Parameters: /// - source: The collection to read the replacement values from. - /// - Returns: A two-member tuple. The first member is the index of the - /// earliest element of this collection that was assigned a copy. The - /// second member is the index of the earliest element of `source` that was - /// read for copying. If no copying was done, both returned indices are at - /// their respective owner's `endIndex`. - /// - Postcondition: Let *k* be the element count of the shorter of `self` and - /// `source`. Then `suffix(k)` will be equivalent to `source.suffix(k)`, - /// while `dropLast(k)` is unchanged. + /// - Returns: A two-member tuple where the first member is the starting index + /// for the range of `source` elements that were actually read and the + /// second member is the starting index for the range of `self` elements + /// that were actually overwritten. The upper bound for each range is the + /// corresponding collection's `endIndex`. + /// - Postcondition: Let *r* be the returned value from the call to this + /// method. Then `self[r.writtenStart...]` will be equivalent to + /// `source[r.readStart...]`. Both subsequences will have an element count + /// of *k*, where *k* is minimum of `count` and `source.count`. /// /// - Complexity: O(*n*), where *n* is the length of the shorter collection /// between `self` and `source`. public mutating func overwrite( - backwards source: C - ) -> (writtenStart: Index, readStart: C.Index) where C.Element == Element { - var selfIndex = endIndex, sourceIndex = source.endIndex - let start = startIndex, sourceStart = source.startIndex - while selfIndex > start, sourceIndex > sourceStart { - formIndex(before: &selfIndex) - source.formIndex(before: &sourceIndex) - self[selfIndex] = source[sourceIndex] + backwardsWith source: C + ) -> (readStart: C.Index, writtenStart: Index) + where C.Element == Element { + var indexIterator = source.reversed().indices.makeIterator() + let start = overwrite(suffixUsing: &indexIterator, doCorrect: false) { + source[source.index(before: $0.base)] } - return (selfIndex, sourceIndex) + return (indexIterator.next().map(\.base) ?? source.startIndex, start) } /// Copies, in reverse traversal, the suffix of a subsequence on top of the diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/CopyTests.swift index 001e9a5b..74d65241 100644 --- a/Tests/SwiftAlgorithmsTests/CopyTests.swift +++ b/Tests/SwiftAlgorithmsTests/CopyTests.swift @@ -191,6 +191,119 @@ final class CopyTests: XCTestCase { XCTAssertEqualSequences(destination8, "abcNOPQhijklm") } + /// Test using a collection as the source for prefix copying. + func testCollectionSourcePrefix() { + // Empty source and destination + var destination1 = EmptyCollection() + XCTAssertEqualSequences(destination1, []) + + let source1 = EmptyCollection() + let (sEnd1, dEnd1) = destination1.overwrite(forwardsWith: source1) + XCTAssertEqual(sEnd1, source1.startIndex) + XCTAssertEqual(dEnd1, destination1.startIndex) + XCTAssertEqualSequences(destination1, []) + XCTAssertEqualSequences(source1[..() + XCTAssertEqualSequences(destination2, []) + + let source2 = CollectionOfOne(1.1) + let (sEnd2, dEnd2) = destination2.overwrite(forwardsWith: source2) + XCTAssertEqual(sEnd2, source2.startIndex) + XCTAssertEqual(dEnd2, destination2.startIndex) + XCTAssertEqualSequences(destination2, []) + XCTAssertEqualSequences(source2[..() + let (sEnd3, dEnd3) = destination3.overwrite(forwardsWith: source3) + XCTAssertEqual(sEnd3, source3.startIndex) + XCTAssertEqual(dEnd3, destination3.startIndex) + XCTAssertEqualSequences(destination3, [2.2]) + XCTAssertEqualSequences(source3[..() - let empty2 = EmptyCollection() - XCTAssertEqualSequences(empty1, []) - - let result2 = empty1.overwrite(prefixWithCollection: empty2) - XCTAssertEqual(result2.copyEnd, empty1.startIndex) - XCTAssertEqual(result2.sourceTailStart, empty2.startIndex) - XCTAssertEqualSequences(empty1, []) - XCTAssertEqualSequences(empty1[..() + XCTAssertEqualSequences(destination1, []) - /// Test nonempty source and empty destination. - func testOnlyDestinationEmpty() { - var empty = EmptyCollection() - let single = CollectionOfOne(1.1) - XCTAssertEqualSequences(empty, []) - - let result2 = empty.overwrite(prefixWithCollection: single) - XCTAssertEqual(result2.copyEnd, empty.startIndex) - XCTAssertEqual(result2.sourceTailStart, single.startIndex) - XCTAssertEqualSequences(empty, []) - XCTAssertEqualSequences(empty[..() + let (sStart1, dStart1) = destination1.overwrite(backwardsWith: source1) + XCTAssertEqual(sStart1, source1.endIndex) + XCTAssertEqual(dStart1, destination1.endIndex) + XCTAssertEqualSequences(destination1, []) + XCTAssertEqualSequences(source1[sStart1...], destination1[dStart1...]) - /// Test empty source and nonempty destination. - func testOnlySourceEmpty() { - var single = CollectionOfOne(2.2) - let empty = EmptyCollection() - XCTAssertEqualSequences(single, [2.2]) - - let result2 = single.overwrite(prefixWithCollection: empty) - XCTAssertEqual(result2.copyEnd, single.startIndex) - XCTAssertEqual(result2.sourceTailStart, empty.startIndex) - XCTAssertEqualSequences(single, [2.2]) - XCTAssertEqualSequences(single[..() + XCTAssertEqualSequences(destination2, []) - /// Test two one-element collections. - func testTwoSingles() { - var destination = CollectionOfOne(3.3) - //let source = CollectionOfOne(4.4) - XCTAssertEqualSequences(destination, [3.3]) - - let source2 = CollectionOfOne(5.5), - result2 = destination.overwrite(prefixWithCollection: source2) - XCTAssertEqual(result2.copyEnd, destination.endIndex) - XCTAssertEqual(result2.sourceTailStart, source2.endIndex) - XCTAssertEqualSequences(destination, [5.5]) - XCTAssertEqualSequences(destination[..() + let (sStart3, dStart3) = destination3.overwrite(backwardsWith: source3) + XCTAssertEqual(sStart3, source3.endIndex) + XCTAssertEqual(dStart3, destination3.endIndex) + XCTAssertEqualSequences(destination3, [2.2]) + XCTAssertEqualSequences(source3[sStart3...], destination3[dStart3...]) - /// Test a multi-element source shorter than the destination. - func testShorterSource() { - var destination = Array("abcdefghijklm") - //let source = "NOPQR" - XCTAssertEqualSequences(destination, "abcdefghijklm") - - let source2 = "123", result2 = destination.overwrite(prefixWithCollection: source2) - XCTAssertEqual(result2.copyEnd, 3) - XCTAssertEqual(result2.sourceTailStart, source2.endIndex) - XCTAssertEqualSequences(destination, "123defghijklm") - XCTAssertEqualSequences(destination[.. Date: Thu, 31 Dec 2020 02:17:51 -0500 Subject: [PATCH 15/17] Update documentation Acknowledge that the base name of the methods was changed, and give reason for the change. --- Guides/Copy.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Guides/Copy.md b/Guides/Copy.md index 3767b0a2..7088a308 100644 --- a/Guides/Copy.md +++ b/Guides/Copy.md @@ -96,8 +96,11 @@ Calling these methods is O(_k_), where _k_ is the length of the shorter ### Naming -This method’s name matches the term of art used in other languages and -libraries. +The initial development version of this library used the term-of-art "`copy`" +as the base name of this family of methods. But since the insertion-copying +methods (in `RangeReplaceableCollection`) do not use the term, and the term is +used for object copying in Foundation, a subsitute term was chosen here. The +term "`overwrite`" gives a precise description of the kind of copying employed. ### Comparison with other languages From 79af788bc9651ca71c0ab91adac20dcd4e168606 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Thu, 31 Dec 2020 02:27:57 -0500 Subject: [PATCH 16/17] Rename the files from "Copy" to "Overwrite" --- Guides/{Copy.md => Overwrite.md} | 28 +++++++++---------- README.md | 2 +- .../{Copy.swift => Overwrite.swift} | 0 .../{CopyTests.swift => OverwriteTests.swift} | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) rename Guides/{Copy.md => Overwrite.md} (86%) rename Sources/Algorithms/{Copy.swift => Overwrite.swift} (100%) rename Tests/SwiftAlgorithmsTests/{CopyTests.swift => OverwriteTests.swift} (99%) diff --git a/Guides/Copy.md b/Guides/Overwrite.md similarity index 86% rename from Guides/Copy.md rename to Guides/Overwrite.md index 7088a308..a90703c5 100644 --- a/Guides/Copy.md +++ b/Guides/Overwrite.md @@ -1,7 +1,7 @@ -# Copy +# Overwrite -[[Source](../Sources/Algorithms/Copy.swift) | - [Tests](../Tests/SwiftAlgorithmsTests/CopyTests.swift)] +[[Source](../Sources/Algorithms/Overwrite.swift) | + [Tests](../Tests/SwiftAlgorithmsTests/OverwriteTests.swift)] Copy a sequence onto an element-mutable collection. @@ -54,20 +54,20 @@ extension MutableCollection { } extension MutableCollection where Self: BidirectionalCollection { - mutating func overwrite(suffixUsing source: inout I) -> Index - where I : IteratorProtocol, Self.Element == I.Element + mutating func overwrite(suffixUsing source: inout I) -> Index + where I : IteratorProtocol, Self.Element == I.Element - mutating func overwrite(suffixWith source: S) -> Index - where S : Sequence, Self.Element == S.Element + mutating func overwrite(suffixWith source: S) -> Index + where S : Sequence, Self.Element == S.Element - mutating func overwrite(backwardsWith source: C) - -> (readStart: C.Index, writtenStart: Index) - where C : BidirectionalCollection, Self.Element == C.Element + mutating func overwrite(backwardsWith source: C) + -> (readStart: C.Index, writtenStart: Index) + where C : BidirectionalCollection, Self.Element == C.Element - mutating func overwrite(backwardsFrom source: R, to destination: S) - -> (sourceRead: Range, destinationWritten: Range) - where R : RangeExpression, S : RangeExpression, Self.Index == R.Bound, - R.Bound == S.Bound + mutating func overwrite(backwardsFrom source: R, to destination: S) + -> (sourceRead: Range, destinationWritten: Range) + where R : RangeExpression, S : RangeExpression, Self.Index == R.Bound, + R.Bound == S.Bound } ``` diff --git a/README.md b/README.md index ea7a414a..ddb34cdc 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`rotate(toStartAt:)`, `rotate(subrange:toStartAt:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Rotate.md): In-place rotation of elements. - [`stablePartition(by:)`, `stablePartition(subrange:by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Partition.md): A partition that preserves the relative order of the resulting prefix and suffix. -- [`overwrite(prefixWith:)`, `overwrite(suffixWith:)`](./Guides/Copy.md): Copying from a sequence via overwriting elements. +- [`overwrite(prefixWith:)`, `overwrite(suffixWith:)`](./Guides/Overwrite.md): Copying from a sequence via overwriting elements. #### Combining collections diff --git a/Sources/Algorithms/Copy.swift b/Sources/Algorithms/Overwrite.swift similarity index 100% rename from Sources/Algorithms/Copy.swift rename to Sources/Algorithms/Overwrite.swift diff --git a/Tests/SwiftAlgorithmsTests/CopyTests.swift b/Tests/SwiftAlgorithmsTests/OverwriteTests.swift similarity index 99% rename from Tests/SwiftAlgorithmsTests/CopyTests.swift rename to Tests/SwiftAlgorithmsTests/OverwriteTests.swift index 74d65241..40db5050 100644 --- a/Tests/SwiftAlgorithmsTests/CopyTests.swift +++ b/Tests/SwiftAlgorithmsTests/OverwriteTests.swift @@ -13,7 +13,7 @@ import XCTest import Algorithms /// Unit tests for the `overwrite` methods. -final class CopyTests: XCTestCase { +final class OverwriteTests: XCTestCase { /// Test using an iterator as the source for prefix copying. func testIteratorSourcePrefix() { // Empty source and destination From 99d436b87680778d5e057be282b6fdc129e68025 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Wed, 13 Jan 2021 12:14:23 -0500 Subject: [PATCH 17/17] Correct spelling error Co-authored-by: Luciano Almeida --- Guides/Overwrite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guides/Overwrite.md b/Guides/Overwrite.md index a90703c5..5617a771 100644 --- a/Guides/Overwrite.md +++ b/Guides/Overwrite.md @@ -99,7 +99,7 @@ Calling these methods is O(_k_), where _k_ is the length of the shorter The initial development version of this library used the term-of-art "`copy`" as the base name of this family of methods. But since the insertion-copying methods (in `RangeReplaceableCollection`) do not use the term, and the term is -used for object copying in Foundation, a subsitute term was chosen here. The +used for object copying in Foundation, a substitute term was chosen here. The term "`overwrite`" gives a precise description of the kind of copying employed. ### Comparison with other languages