Skip to content

Commit

Permalink
[Stride] Check boundaries before calculating next step
Browse files Browse the repository at this point in the history
The current implementation of `Stride(To|Through)Iterator` verifies the
completion of the sequence by pre-calculating the current value and
checking if it exceeds the end boundary before returning it.

It, then, requires for a value beyond the boundary to be returned by
`Strideable.advanced(by:)`.

Instead of stepping one value after the boundaries, this implementation
verifies if the distance between the current element and the end element
is smaller than the stride step. This allows stopping the iterator before
that element is required.

After this change two usages of `Strideable` conformance become available:

* The use of frontier values on `stride(from:(to|through):by:)`

As described in [SR-2016](https://bugs.swift.org/browse/SR-2016), this would now function properly as it'd be no
longer requried for the "impossible" value `UInt8(256)` to briefly exist
in order to complete the iteration:

```
stride(from:0 as UInt8, through: 255, by: 2)
```

* The use of `Strideable` on enums with a finite number of cases

While it is currently possible to support it on enums, it requires a
special `case` to handle the past-frontier case. This enables the
following use-case:

```
enum Test: Int, Strideable {
	case one = 1, two, three, four

	typealias Stride = Int

	func distance(to other: Self) -> Int {
		return other.rawValue - self.rawValue
	}

	func advanced(by: Int) -> Self {
		return Self(rawValue: self.rawValue + by)!
	}
}

stride(from: Test.one, through: Test.four, by: 2)
```
  • Loading branch information
gonzalolarralde committed Mar 9, 2021
1 parent da37637 commit b846dba
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 20 deletions.
45 changes: 25 additions & 20 deletions stdlib/public/core/Stride.swift
Expand Up @@ -215,14 +215,14 @@ public struct StrideToIterator<Element: Strideable> {
internal let _stride: Element.Stride

@usableFromInline
internal var _current: (index: Int?, value: Element)
internal var _current: (index: Int?, value: Element?)

@inlinable
internal init(_start: Element, end: Element, stride: Element.Stride) {
self._start = _start
_end = end
_stride = stride
_current = (0, _start)
_current = (nil, nil)
}
}

Expand All @@ -233,12 +233,18 @@ extension StrideToIterator: IteratorProtocol {
/// Once `nil` has been returned, all subsequent calls return `nil`.
@inlinable
public mutating func next() -> Element? {
let result = _current.value
if _stride > 0 ? result >= _end : result <= _end {
return nil
if let value = _current.value {
let deltaEnd = value.distance(to: _end)
if _stride > 0 ? deltaEnd > _stride : deltaEnd < _stride {
_current = Element._step(after: (_current.index, value), from: _start, by: _stride)
} else {
return nil
}
} else {
_current = (0, _start)
}
_current = Element._step(after: _current, from: _start, by: _stride)
return result

return _current.value
}
}

Expand Down Expand Up @@ -416,7 +422,7 @@ public struct StrideThroughIterator<Element: Strideable> {
internal let _stride: Element.Stride

@usableFromInline
internal var _current: (index: Int?, value: Element)
internal var _current: (index: Int?, value: Element?)

@usableFromInline
internal var _didReturnEnd: Bool = false
Expand All @@ -426,7 +432,7 @@ public struct StrideThroughIterator<Element: Strideable> {
self._start = _start
_end = end
_stride = stride
_current = (0, _start)
_current = (nil, nil)
}
}

Expand All @@ -437,19 +443,18 @@ extension StrideThroughIterator: IteratorProtocol {
/// Once `nil` has been returned, all subsequent calls return `nil`.
@inlinable
public mutating func next() -> Element? {
let result = _current.value
if _stride > 0 ? result >= _end : result <= _end {
// This check is needed because if we just changed the above operators
// to > and <, respectively, we might advance current past the end
// and throw it out of bounds (e.g. above Int.max) unnecessarily.
if result == _end && !_didReturnEnd {
_didReturnEnd = true
return result
if let value = _current.value {
let deltaEnd = value.distance(to: _end)
if _stride > 0 ? deltaEnd >= self._stride : deltaEnd <= self._stride {
_current = Element._step(after: (_current.index, value), from: _start, by: _stride)
} else {
return nil
}
return nil
} else {
_current = (0, _start)
}
_current = Element._step(after: _current, from: _start, by: _stride)
return result

return _current.value
}
}

Expand Down
26 changes: 26 additions & 0 deletions test/stdlib/Strideable.swift
Expand Up @@ -61,6 +61,17 @@ struct R : Strideable {
}
}

enum E : Int, Strideable {
case one = 1, two, three, four

func distance(to other: Self) -> Int {
return other.rawValue - self.rawValue
}
func advanced(by n: Int) -> Self {
return Self(rawValue: self.rawValue + n)!
}
}

StrideTestSuite.test("Double") {
func checkOpen(from start: Double, to end: Double, by stepSize: Double, sum: Double) {
// Work on Doubles
Expand Down Expand Up @@ -234,5 +245,20 @@ StrideTestSuite.test("StrideToIterator/past end/backward") {
strideIteratorTest(stride(from: 3, to: 0, by: -1), nonNilResults: 3)
}

StrideTestSuite.test("UInt8") {
// SR-2016
strideIteratorTest(stride(from:253 as UInt8, to: 255, by: 2), nonNilResults: 1)
strideIteratorTest(stride(from:253 as UInt8, through: 255, by: 2), nonNilResults: 2)
strideIteratorTest(stride(from:2 as UInt8, to: 0, by: -2), nonNilResults: 1)
strideIteratorTest(stride(from:2 as UInt8, through: 0, by: -2), nonNilResults: 2)
}

StrideTestSuite.test("Enum") {
strideIteratorTest(stride(from:E.one as UInt8, to: E.four, by: 2), nonNilResults: 1)
strideIteratorTest(stride(from:E.one as UInt8, through: E.four, by: 2), nonNilResults: 2)
strideIteratorTest(stride(from:E.four as UInt8, to: E.one, by: -2), nonNilResults: 1)
strideIteratorTest(stride(from:E.four UInt8, through: E.one, by: -2), nonNilResults: 2)
}

runAllTests()

0 comments on commit b846dba

Please sign in to comment.