Skip to content

Copy #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open

Copy #52

Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,25 @@ 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. 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 `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
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.

---

Expand Down
110 changes: 110 additions & 0 deletions Guides/Copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# 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. The `copy(collection:)` variant uses a collection
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:)`
and `copy(backwardsFrom:to:)` methods permit copying elements across
subsequences of the same collection.

## Detailed Design

New methods are added to element-mutable collections:

```swift
extension MutableCollection {
mutating func copy<S: Sequence>(from source: S)
-> (copyEnd: Index, sourceTail: S.Iterator) where S.Element == Element

mutating func copy<C>(collection: C)
-> (copyEnd: Index, sourceTailStart: C.Index)
where C : Collection, Self.Element == C.Element

mutating func copy<R, S>(forwardsFrom source: R, to destination: S)
-> (sourceRead: Range<Index>, destinationWritten: Range<Index>)
where R : RangeExpression, S : RangeExpression, Self.Index == R.Bound,
R.Bound == S.Bound
}

extension MutableCollection where Self: BidirectionalCollection {
mutating func copy<S>(asSuffix source: S)
-> (copyStart: Index, sourceTail: S.Iterator)
where S : Sequence, Self.Element == S.Element

mutating func copy<C>(collectionAsSuffix source: C)
-> (copyStart: Index, sourceTailStart: C.Index)
where C : Collection, Self.Element == C.Element

mutating func copy<C>(backwards source: C)
-> (writtenStart: Index, readStart: C.Index)
where C : BidirectionalCollection, Self.Element == C.Element

mutating func copy<R, S>(backwardsFrom source: R, to destination: S)
-> (sourceRead: Range<Index>, destinationWritten: Range<Index>)
where R : RangeExpression, S : RangeExpression, Self.Index == R.Bound,
R.Bound == S.Bound
}
```

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

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. 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. 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.

<!-- Link references for other languages -->

[C++Copy]: https://en.cppreference.com/w/cpp/algorithm/copy
[C++CopyBackward]: https://en.cppreference.com/w/cpp/algorithm/copy_backward
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:)`, `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

Expand Down
Loading