Skip to content

Commit 87115f8

Browse files
committed
Merge pull request #2717 from kballard/sequence-first-state-next
[Stdlib] Implement sequence(first:next:) and sequence(state:next:)
2 parents 83da10b + d1d0e05 commit 87115f8

File tree

4 files changed

+208
-0
lines changed

4 files changed

+208
-0
lines changed

Diff for: stdlib/public/core/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ set(SWIFTLIB_SOURCES
132132
Process.swift
133133
SliceBuffer.swift
134134
Tuple.swift.gyb
135+
UnfoldSequence.swift
135136
VarArgs.swift
136137
Zip.swift
137138
)

Diff for: stdlib/public/core/GroupInfo.json

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"Indices.swift",
5050
"Existential.swift",
5151
"WriteBackMutableSlice.swift",
52+
"UnfoldSequence.swift",
5253
{
5354
"Type-erased": [
5455
"ExistentialCollection.swift"

Diff for: stdlib/public/core/UnfoldSequence.swift

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// Returns a sequence formed from `first` and repeated lazy applications of
14+
/// `next`.
15+
///
16+
/// The first element in the sequence is always `first`, and each successive
17+
/// element is the result of invoking `next` with the previous element. The
18+
/// sequence ends when `next` returns `nil`. If `next` never returns `nil`, the
19+
/// sequence is infinite.
20+
///
21+
/// This function can be used to replace many cases that were previously handled
22+
/// using C-style `for` loops.
23+
///
24+
/// Example:
25+
///
26+
/// // Walk the elements of a tree from a node up to the root
27+
/// for node in sequence(first: leaf, next: { $0.parent }) {
28+
/// // node is leaf, then leaf.parent, then leaf.parent.parent, etc.
29+
/// }
30+
///
31+
/// // Iterate over all powers of two (ignoring overflow)
32+
/// for value in sequence(first: 1, next: { $0 * 2 }) {
33+
/// // value is 1, then 2, then 4, then 8, etc.
34+
/// }
35+
///
36+
/// - Parameter first: The first element to be returned from the sequence.
37+
/// - Parameter next: A closure that accepts the previous sequence element and
38+
/// returns the next element.
39+
/// - Returns: A sequence that starts with `first` and continues with every
40+
/// value returned by passing the previous element to `next`.
41+
///
42+
/// - SeeAlso: `sequence(state:next:)`
43+
public func sequence<T>(first: T, next: (T) -> T?) -> UnfoldFirstSequence<T> {
44+
// The trivial implementation where the state is the next value to return
45+
// has the downside of being unnecessarily eager (it evaluates `next` one
46+
// step in advance). We solve this by using a boolean value to disambiguate
47+
// between the first value (that's computed in advance) and the rest.
48+
return sequence(state: (first, true), next: { (state: inout (T?, Bool)) -> T? in
49+
switch state {
50+
case (let value, true):
51+
state.1 = false
52+
return value
53+
case (let value?, _):
54+
let nextValue = next(value)
55+
state.0 = nextValue
56+
return nextValue
57+
case (nil, _):
58+
return nil
59+
}
60+
})
61+
}
62+
63+
/// Returns a sequence formed from repeated lazy applications of `next` to a
64+
/// mutable `state`.
65+
///
66+
/// The elements of the sequence are obtaned by invoking `next` with a mutable
67+
/// state. The same state is passed to all invocations of `next`, so subsequent
68+
/// calls will see any mutations made by previous calls. The sequence ends when
69+
/// `next` returns `nil`. If `next` never returns `nil`, the sequence is
70+
/// infinite.
71+
///
72+
/// This function can be used to replace many instances of `AnyIterator` that
73+
/// wrap a closure.
74+
///
75+
/// Example:
76+
///
77+
/// // Interleave two sequences that yield the same element type
78+
/// sequence(state: (false, seq1.makeIterator(), seq2.makeIterator()), next: { iters in
79+
/// iters.0 = !iters.0
80+
/// return iters.0 ? iters.1.next() : iters.2.next()
81+
/// })
82+
///
83+
/// - Parameter state: The initial state that will be passed to the closure.
84+
/// - Parameter next: A closure that accepts an `inout` state and returns the
85+
/// next element of the sequence.
86+
/// - Returns: A sequence that yields each successive value from `next`.
87+
///
88+
/// - SeeAlso: `sequence(first:next:)`
89+
public func sequence<T, State>(state: State, next: (inout State) -> T?)
90+
-> UnfoldSequence<T, State> {
91+
return UnfoldSequence(_state: state, _next: next)
92+
}
93+
94+
/// The return type of `sequence(first:next:)`.
95+
public typealias UnfoldFirstSequence<T> = UnfoldSequence<T, (T?, Bool)>
96+
97+
/// A sequence whose elements are produced via repeated applications of a
98+
/// closure to some mutable state.
99+
///
100+
/// The elements of the sequence are computed lazily and the sequence may
101+
/// potentially be infinite in length.
102+
///
103+
/// Instances of `UnfoldSequence` are created with the functions
104+
/// `sequence(first:next:)` and `sequence(state:next:)`.
105+
///
106+
/// - SeeAlso: `sequence(first:next:)`, `sequence(state:next:)`
107+
public struct UnfoldSequence<Element, State> : Sequence, IteratorProtocol {
108+
public mutating func next() -> Element? {
109+
guard !_done else { return nil }
110+
if let elt = _next(&_state) {
111+
return elt
112+
} else {
113+
_done = true
114+
return nil
115+
}
116+
}
117+
118+
internal init(_state: State, _next: (inout State) -> Element?) {
119+
self._state = _state
120+
self._next = _next
121+
}
122+
123+
internal var _state: State
124+
internal let _next: (inout State) -> Element?
125+
internal var _done = false
126+
}

Diff for: validation-test/stdlib/UnfoldSequence.swift

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
4+
import StdlibUnittest
5+
import StdlibCollectionUnittest
6+
7+
var UnfoldTests = TestSuite("UnfoldSequence")
8+
9+
UnfoldTests.test("sequence(state:next:)") {
10+
// FIXME: The full type signatures on these closures should not be
11+
// necessary, but at the moment the compiler gives very confusing errors if
12+
// we don't have them.
13+
14+
let s0 = sequence(state: 1, next: { (val: inout Int) -> Int? in
15+
defer { val *= 2 }; return val > 16 ? nil : val
16+
})
17+
checkSequence([1,2,4,8,16], s0)
18+
19+
let s1 = sequence(state: (), next: { (_: inout ()) -> Int? in 1 })
20+
checkSequence([1, 1, 1], s1.prefix(3))
21+
22+
let s2 = sequence(state: (1..<6).makeIterator(), next: {
23+
(iter: inout CountableRange<Int>.Iterator) in
24+
iter.next()
25+
})
26+
checkSequence(1..<6, s2)
27+
28+
// Make sure we don't evaluate any step in advance
29+
var calls = 0
30+
var s3 = sequence(state: 0, next: { (val: inout Int) -> Int? in
31+
calls += 1; val += 1; return val
32+
})
33+
for i in 1..<6 {
34+
expectEqual(i, s3.next())
35+
expectEqual(i, calls)
36+
}
37+
38+
// Make sure we don't invoke next() after it returns nil
39+
calls = 0
40+
var s4 = sequence(state: 1, next : { (val: inout Int) -> Int? in
41+
calls += 1; defer { val *= 2 }; return val > 16 ? nil : val
42+
})
43+
checkSequence([1,2,4,8,16], s4)
44+
expectEqual(6, calls)
45+
46+
let s5 = sequence(state: (), next: { (_: inout ()) -> Int? in nil })
47+
checkSequence([], s5)
48+
49+
// This is similar to s0 except calling the `next` closure after it returns
50+
// nil starts returning values again.
51+
let s6 = sequence(state: 1, next: { (val: inout Int) -> Int? in
52+
defer { val *= 2 }
53+
return val == 32 ? nil : val
54+
})
55+
checkSequence([1,2,4,8,16], s6)
56+
}
57+
58+
UnfoldTests.test("sequence(first:next:)") {
59+
let s0 = sequence(first: 1, next: { $0 < 50 ? $0 * 2 : nil })
60+
expectEqualSequence([1, 2, 4, 8, 16, 32, 64], s0)
61+
62+
// Make sure we don't evaluate any step in advance
63+
var calls = 0
64+
var s1 = sequence(first: 0, next: { calls += 1; return $0 + 1 })
65+
for i in 0..<5 {
66+
expectEqual(i, s1.next())
67+
expectEqual(i, calls)
68+
}
69+
70+
// Make sure we don't invoke next() after it returns nil
71+
calls = 0
72+
let s2 = sequence(first: 1, next: {
73+
calls += 1
74+
return $0 >= 16 ? nil : $0 * 2
75+
})
76+
checkSequence([1,2,4,8,16], s2)
77+
expectEqual(5, calls)
78+
}
79+
80+
runAllTests()

0 commit comments

Comments
 (0)