Skip to content

Commit

Permalink
Prepare for Swift Algorithms 1.0 (#167)
Browse files Browse the repository at this point in the history
* Rename types to include a Sequence or Collection suffix, and clean up some guides

* Various refactors

* Replace `validateIndexTraversals` test method with `IndexValidator` type

* Add LazySequenceProtocol to StridingSequence, StridingCollection

* Update the Numerics dependency to 1.0

* Set `IndexedCollection.Indices` to `Base.Indices`

* Remove deprecations

* Update the changelog/README for the 1.0.0 release

* Fix compiler error on Linux

* Rename `WindowsCollection` to `WindowsOfCountCollection`
  • Loading branch information
Tim Vermeulen authored Sep 8, 2021
1 parent e195266 commit c5ea9e3
Show file tree
Hide file tree
Showing 61 changed files with 1,615 additions and 1,280 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/BUG_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ about: Something isn't working as expected

Replace this paragraph with a short description of the incorrect incorrect behavior. If this is a regression, please note the last version that the behavior was correct in addition to your current version.

This comment has been minimized.

Copy link
@joeallenapp

joeallenapp Dec 22, 2022

single {.0.1.0=


**Swift Algorithms version:** `0.0.1` or the `main` branch, for example.

This comment has been minimized.

Copy link
@joeallenapp

joeallenapp Dec 22, 2022

** { 0.1.2}

**Swift Algorithms version:** `1.0.0` or the `main` branch, for example.
**Swift version:** Paste the output of `swift --version` here.

### Checklist
Expand Down
78 changes: 72 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,81 @@
Add new items at the end of the relevant section under **Unreleased**.
-->

This project follows semantic versioning. While still in major version `0`,
source-stability is only guaranteed within minor versions (e.g. between
`0.0.3` and `0.0.4`). If you want to guard against potentially source-breaking
package updates, you can specify your package dependency using
`.upToNextMinor(from: "0.1.0")` as the requirement.
This project follows semantic versioning.

## [Unreleased]

*No new changes.*

---

## [1.0.0] - 2021-09-08

### Changes

- Most sequence and collection types have been renamed, following a more
consistent naming structure:
- The `Lazy` prefix was dropped.
- Either a `Sequence` or `Collection` suffix was added depending on whether or
not the type is unconditionally a collection.
- The base name was derived from the name of the method that produces it,
including an argument label to disambiguate if necessary.
```
Chain2 -> Chain2Sequence
ChunkedBy -> ChunkedByCollection
ChunkedOn -> ChunkedOnCollection
ChunkedByCount -> ChunksOfCountCollection
Combinations -> CombinationsSequence
Cycle -> CycledSequence
FiniteCycle -> CycledTimesCollection
Indexed -> IndexedCollection
Intersperse -> InterspersedSequence
LazySplitSequence -> SplitSequence
LazySplitCollection -> SplitCollection
Permutations -> PermutationsSequence
UniquePermutations -> UniquePermutationsSequence
Product2 -> Product2Sequence
ExclusiveReductions -> ExclusiveReductionsSequence
InclusiveReductions -> InclusiveReductionsSequence
StrideSequence -> StridingSequence
StrideCollection -> StridingCollection
Uniqued -> UniquedSequence
Windows -> WindowsOfCountCollection
```
- Types that can only be produced from a lazy sequence chain now unconditionally
conform to `LazySequenceProtocol` and wrap the base sequence instead of the
lazy wrapper, making some return types slightly simpler.
- e.g. `[1, 2, 3].lazy.reductions(+)` now returns
`ExclusiveReductionsSequence<[Int]>`, not
`ExclusiveReductionsSequence<LazySequence<[Int]>>`.
- This concerns `JoinedByClosureSequence`, `JoinedByClosureCollection`,
`ExclusiveReductionsSequence`, `InclusiveReductionsSequence`.
- The generic parameters of the `ExclusiveReductions` type have been swapped,
putting the base collection first and the result type second.
- The `Indices` associated type of `IndexedCollection` now matches
`Base.Indices`.

### Removals

- Previously deprecated type and method names have been removed:
- The `Chain` type alias for `Chain2Sequence`
- The `chained(with:)` method which was replaced with the `chain(_:_:)` free
function
- The `LazyChunked` and `Chunked` type aliases for `ChunkedByCollection`
- The `rotate(subrange:at:)` and `rotate(at:)` methods which were renamed to
`rotate(subrange:toStartAt:)` and `rotate(toStartAt:)` respectively

### Fixes

- The `StridingSequence` and `StridingCollection` types now conditionally
conform to `LazySequenceProtocol`, allowing the `striding(by:)` method to
properly propagate laziness in a lazy sequence chain.
- Fixed `chunked(by:)` to always compare two consecutive elements rather than
each element with the first element of the current chunk. ([#162])

The 1.0.0 release includes contributions from [iainsmith], [mdznr], and
[timvermeulen]. Thank you!

## [0.2.1] - 2021-06-01

### Additions
Expand Down Expand Up @@ -206,7 +269,8 @@ This changelog's format is based on [Keep a Changelog](https://keepachangelog.co

<!-- Link references for releases -->

[Unreleased]: https://github.com/apple/swift-algorithms/compare/0.2.1...HEAD
[Unreleased]: https://github.com/apple/swift-algorithms/compare/1.0.0...HEAD
[1.0.0]: https://github.com/apple/swift-algorithms/compare/0.2.1...1.0.0
[0.2.1]: https://github.com/apple/swift-algorithms/compare/0.2.0...0.2.1
[0.2.0]: https://github.com/apple/swift-algorithms/compare/0.1.1...0.2.0
[0.1.1]: https://github.com/apple/swift-algorithms/compare/0.1.0...0.1.1
Expand Down Expand Up @@ -243,6 +307,7 @@ This changelog's format is based on [Keep a Changelog](https://keepachangelog.co
[#125]: https://github.com/apple/swift-algorithms/pull/125
[#130]: https://github.com/apple/swift-algorithms/pull/130
[#138]: https://github.com/apple/swift-algorithms/pull/138
[#162]: https://github.com/apple/swift-algorithms/pull/162

<!-- Link references for contributors -->

Expand All @@ -256,6 +321,7 @@ This changelog's format is based on [Keep a Changelog](https://keepachangelog.co
[fedeci]: https://github.com/apple/swift-algorithms/commits?author=fedeci
[hashemi]: https://github.com/apple/swift-algorithms/commits?author=hashemi
[IanKeen]: https://github.com/apple/swift-algorithms/commits?author=IanKeen
[iainsmith]: https://github.com/apple/swift-algorithms/commits?author=iainsmith
[iSame7]: https://github.com/apple/swift-algorithms/commits?author=iSame7
[karwa]: https://github.com/apple/swift-algorithms/commits?author=karwa
[kylemacomber]: https://github.com/apple/swift-algorithms/commits?author=kylemacomber
Expand Down
33 changes: 22 additions & 11 deletions Guides/AdjacentPairs.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

Lazily iterates over tuples of adjacent elements.

This operation is available for any sequence by calling the `adjacentPairs()` method.
This operation is available for any sequence by calling the `adjacentPairs()`
method.

```swift
let numbers = (1...5)
Expand All @@ -15,38 +16,48 @@ let pairs = numbers.adjacentPairs()

## Detailed Design

The `adjacentPairs()` method is declared as a `Sequence` extension returning `AdjacentPairsSequence` and as a `Collection` extension returning `AdjacentPairsCollection`.
The `adjacentPairs()` method is declared as a `Sequence` extension returning
`AdjacentPairsSequence` and as a `Collection` extension returning
`AdjacentPairsCollection`.

```swift
extension Sequence {
public func adjacentPairs() -> AdjacentPairsSequence<Self>
public func adjacentPairs() -> AdjacentPairsSequence<Self>
}
```

```swift
extension Collection {
public func adjacentPairs() -> AdjacentPairsCollection<Self>
public func adjacentPairs() -> AdjacentPairsCollection<Self>
}
```

The `AdjacentPairsSequence` type is a sequence, and the `AdjacentPairsCollection` type is a collection with conditional conformance to `BidirectionalCollection` and `RandomAccessCollection` when the underlying collection conforms.
The `AdjacentPairsSequence` type is a sequence, and the
`AdjacentPairsCollection` type is a collection with conditional conformance to
`BidirectionalCollection` and `RandomAccessCollection` when the underlying
collection conforms.

### Complexity

Calling `adjacentPairs` is an O(1) operation.

### Naming

This method is named for clarity while remaining agnostic to any particular domain of programming. In natural language processing, this operation is akin to computing a list of bigrams; however, this algorithm is not specific to this use case.

[naming]: https://forums.swift.org/t/naming-of-chained-with/40999/
This method is named for clarity while remaining agnostic to any particular
domain of programming. In natural language processing, this operation is akin to
computing a list of bigrams; however, this algorithm is not specific to this use
case.

### Comparison with other languages

This function is often written as a `zip` of a sequence together with itself, minus its first element.
This function is often written as a `zip` of a sequence together with itself,
minus its first element.

**Haskell:** This operation is spelled ``s `zip` tail s``.

**Python:** Python users may write `zip(s, s[1:])` for a list with at least one element. For natural language processing, the `nltk` package offers a `bigrams` function akin to this method.
**Python:** Python users may write `zip(s, s[1:])` for a list with at least one
element. For natural language processing, the `nltk` package offers a `bigrams`
function akin to this method.

Note that in Swift, the spelling `zip(s, s.dropFirst())` is undefined behavior for a single-pass sequence `s`.
Note that in Swift, the spelling `zip(s, s.dropFirst())` is undefined behavior
for a single-pass sequence `s`.
8 changes: 4 additions & 4 deletions Guides/Chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ the shared conformances of the two underlying types.
The `chain(_:_:)` function takes two sequences as arguments:

```swift
public func chain<S1, S2>(_ s1: S1, _ s2: S2) -> Chain2<S1, S2>
public func chain<S1, S2>(_ s1: S1, _ s2: S2) -> Chain2Sequence<S1, S2>
where S1.Element == S2.Element
```

The resulting `Chain2` type is a sequence, with conditional conformance to
`Collection`, `BidirectionalCollection`, and `RandomAccessCollection` when both
the first and second arguments conform.
The resulting `Chain2Sequence` type is a sequence, with conditional conformance
to `Collection`, `BidirectionalCollection`, and `RandomAccessCollection` when
both the first and second arguments conform.

### Naming

Expand Down
93 changes: 53 additions & 40 deletions Guides/Chunked.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/ChunkedTests.swift)]

Break a collection into subsequences where consecutive elements pass a binary
predicate, or where all elements in each chunk project to the same value.
predicate, or where all elements in each chunk project to the same value.

Also, includes a `chunks(ofCount:)` that breaks a collection into subsequences
Also includes a `chunks(ofCount:)` that breaks a collection into subsequences
of a given `count`.

There are two variations of the `chunked` method: `chunked(by:)` and
Expand All @@ -20,22 +20,22 @@ let chunks = numbers.chunked(by: { $0 <= $1 })
// [[10, 20, 30], [10, 40, 40], [10, 20]]
```

The `chunk(on:)` method, by contrast, takes a projection of each element and
The `chunked(on:)` method, by contrast, takes a projection of each element and
separates chunks where the projection of two consecutive elements is not equal.
The result includes both the projected value and the subsequence
that groups elements with that projected value:
The result includes both the projected value and the subsequence that groups
elements with that projected value:

```swift
let names = ["David", "Kyle", "Karoy", "Nate"]
let chunks = names.chunked(on: \.first!)
// [("D", ["David"]), ("K", ["Kyle", "Karoy"]), ("N", ["Nate"])]
```

The `chunks(ofCount:)` method takes a `count` parameter (greater than zero)
and separates the collection into chunks of this given count.
If the `count` parameter is evenly divided by the count of the base `Collection`,
all the chunks will have a count equal to the parameter.
Otherwise, the last chunk will contain the remaining elements.
The `chunks(ofCount:)` method takes a `count` parameter (greater than zero)
and separates the collection into chunks of this given count. If the `count`
parameter is evenly divided by the count of the base `Collection`, all the
chunks will have a count equal to the parameter. Otherwise, the last chunk will
contain the remaining elements.

```swift
let names = ["David", "Kyle", "Karoy", "Nate"]
Expand All @@ -46,11 +46,11 @@ let remaining = names.chunks(ofCount: 3)
// equivalent to [["David", "Kyle", "Karoy"], ["Nate"]]
```

The `chunks(ofCount:)` is the subject of an [existing SE proposal][proposal].
The `chunks(ofCount:)` is the subject of an [existing SE proposal][proposal].

When "chunking" a collection, the entire collection is included in the result,
When "chunking" a collection, the entire collection is included in the result,
unlike the `split` family of methods, where separators are dropped.
Joining the result of a chunking method call recreates the original collection.
Joining the result of a chunking method call recreates the original collection.

```swift
c.elementsEqual(c.chunked(...).joined())
Expand All @@ -61,46 +61,59 @@ c.elementsEqual(c.chunked(...).joined())

## Detailed Design

The two methods are added as extension to `Collection`, with two matching
versions that return a lazy wrapper added to `LazyCollectionProtocol`.
The three methods are added as extension to `Collection`. `chunked(by:)` and
`chunked(on:)` are eager by default, both with a matching version that return a
lazy wrapper added to `LazySequenceProtocol`.

```swift
extension Collection {
public func chunked(
by belongInSameGroup: (Element, Element) -> Bool
) -> [SubSequence]

public func chunked<Subject: Equatable>(
on projection: (Element) -> Subject
) -> [(Subject, SubSequence)]
}

extension LazyCollectionProtocol {
public func chunked(
by belongInSameGroup: @escaping (Element, Element) -> Bool
) -> ChunkedBy<Elements, Element>

public func chunked<Subject: Equatable>(
on projection: @escaping (Element) -> Subject
) -> ChunkedOn<Elements, Subject>
public func chunked(
by belongInSameGroup: (Element, Element) -> Bool
) -> [SubSequence]

public func chunked<Subject: Equatable>(
on projection: (Element) -> Subject
) -> [(Subject, SubSequence)]

public func chunks(ofCount count: Int) -> ChunksOfCountCollection<Self>
}

extension LazySequenceProtocol where Self: Collection, Elements: Collection {
public func chunked(
by belongInSameGroup: @escaping (Element, Element) -> Bool
) -> ChunkedByCollection<Elements, Element>

public func chunked<Subject: Equatable>(
on projection: @escaping (Element) -> Subject
) -> ChunkedOnCollection<Elements, Subject>
}
```

The `ChunkedBy` and `ChunkedOn` types are bidirectional when the wrapped collection is
bidirectional.
The `ChunkedByCollection`, `ChunkedOnCollection`, and `ChunksOfCountCollection`
types are bidirectional when the wrapped collection is bidirectional.
`ChunksOfCountCollection` also conforms to `LazySequenceProtocol` when the base
collection conforms.

### Complexity

The eager methods are O(_n_), the lazy methods are O(_1_).

### Naming

The operation performed by these methods is similar to other ways of breaking a collection up into subsequences. In particular, the predicate-based `split(where:)` method looks similar to `chunked(on:)`. You can draw a distinction between these different operations based on the resulting subsequences:

- `split`: *In the standard library.* Breaks a collection into subsequences, removing any elements that are considered "separators". The original collection cannot be recovered from the result of splitting.
- `chunked`: *In this package.* Breaks a collection into subsequences, preserving each element in its initial ordering. Joining the resulting subsequences re-forms the original collection.
- `sliced`: *Not included in this package or the stdlib.* Breaks a collection into potentially overlapping subsequences.

The operation performed by these methods is similar to other ways of breaking a
collection up into subsequences. In particular, the predicate-based
`split(where:)` method looks similar to `chunked(on:)`. You can draw a
distinction between these different operations based on the resulting
subsequences:

- `split`: *In the standard library.* Breaks a collection into subsequences,
removing any elements that are considered "separators". The original collection
cannot be recovered from the result of splitting.
- `chunked`: *In this package.* Breaks a collection into subsequences,
preserving each element in its initial ordering. Joining the resulting
subsequences re-forms the original collection.
- `sliced`: *Not included in this package or the stdlib.* Breaks a collection
into potentially overlapping subsequences.

### Comparison with other languages

Expand Down
21 changes: 11 additions & 10 deletions Guides/Combinations.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,27 +60,28 @@ for combo in numbers.combinations(ofCount: 2...3) {
## Detailed Design

The `combinations(ofCount:)` method is declared as a `Collection` extension,
and returns a `Combinations` type:
and returns a `CombinationsSequence` type:

```swift
extension Collection {
public func combinations(ofCount k: Int) -> Combinations<Self>
public func combinations(ofCount k: Int) -> CombinationsSequence<Self>
}
```

Since the `Combinations` type needs to store an array of the collection’s
indices and mutate the array to generate each permutation, `Combinations` only
has `Sequence` conformance. Adding `Collection` conformance would require
storing the array in the index type, which would in turn lead to copying the
array at every index advancement. `Combinations` does conform to
`LazySequenceProtocol` when the base type conforms.
Since the `CombinationsSequence` type needs to store an array of the
collection’s indices and mutate the array to generate each permutation,
`CombinationsSequence` only has `Sequence` conformance. Adding `Collection`
conformance would require storing the array in the index type, which would in
turn lead to copying the array at every index advancement.
`CombinationsSequence` does conform to `LazySequenceProtocol` when the base type
conforms.

### Complexity

Calling `combinations(ofCount:)` accesses the count of the collection, so it’s
an O(1) operation for random-access collections, or an O(_n_) operation
otherwise. Creating the iterator for a `Combinations` instance and each call to
`Combinations.Iterator.next()` is an O(_n_) operation.
otherwise. Creating the iterator for a `CombinationsSequence` instance and each
call to `CombinationsSequence.Iterator.next()` is an O(_n_) operation.

### Naming

Expand Down
Loading

0 comments on commit c5ea9e3

Please sign in to comment.