diff --git a/Sources/Genything/Generators/Combinations/Generators+collect.swift b/Sources/Genything/Generators/Combinations/Generators+collect.swift index 02139ce1..305a951f 100644 --- a/Sources/Genything/Generators/Combinations/Generators+collect.swift +++ b/Sources/Genything/Generators/Combinations/Generators+collect.swift @@ -20,10 +20,17 @@ extension Generators { /// - Returns: The generator of Arrays containing values selected in order from the provided generators public static func collect(_ generators: [G], _ transform: @escaping (G.T) -> R) -> AnyGenerator<[R]> where G: Generator { - AnyGenerator<[R]> { ctx in - generators.map { - $0.next(ctx) - }.map(transform) - } + Collect(sources: generators, transform: transform).eraseToAnyGenerator() + } +} + +struct Collect: Generator where Source: Generator { + let sources: [Source] + let transform: (Source.T) -> T + + func next(_ randomSource: RandomSource) -> [T] { + sources.map { + $0.next(randomSource) + }.map(transform) } } diff --git a/Sources/Genything/Generators/Combinations/Generators+join.swift b/Sources/Genything/Generators/Combinations/Generators+join.swift index d1e2f686..a373c7d8 100644 --- a/Sources/Genything/Generators/Combinations/Generators+join.swift +++ b/Sources/Genything/Generators/Combinations/Generators+join.swift @@ -10,8 +10,7 @@ extension Generators { /// - Returns: The generator public static func join(_ generators: [G], separator: String = "") -> AnyGenerator where G: Generator, G.T == String { - Generators - .collect(generators) + collect(generators) .map { $0.joined(separator: separator) } } } diff --git a/Sources/Genything/Generators/Combinations/Generators+merge.swift b/Sources/Genything/Generators/Combinations/Generators+merge.swift new file mode 100644 index 00000000..9abe8b1d --- /dev/null +++ b/Sources/Genything/Generators/Combinations/Generators+merge.swift @@ -0,0 +1,31 @@ +// MARK: Combine + +extension Generators { + /// Returns: A generator which produces results by looping over the provided generators in order + /// + /// # Example + /// + /// ```swift + /// let genA = Generators.constant("A") + /// let genB = Generators.constant("B") + /// + /// Generators.merge([genA, genB]) // Produces values ABABABABABAB... + /// ``` + /// + /// - Returns: The generator + public static func merge(_ generators: [G]) -> AnyGenerator where G: Generator { + Merge(generators).eraseToAnyGenerator() + } +} + +class Merge: Generator where Source: Generator { + private let loop: Loop<[Source]> + + init(_ sources: [Source]) { + self.loop = Loop(sources) + } + + func next(_ randomSource: RandomSource) -> Source.T { + loop.next(randomSource).next(randomSource) + } +} diff --git a/Sources/Genything/Generators/Generators+iterate.swift b/Sources/Genything/Generators/Generators+iterate.swift index e5a2ff77..23b06f31 100644 --- a/Sources/Genything/Generators/Generators+iterate.swift +++ b/Sources/Genything/Generators/Generators+iterate.swift @@ -9,7 +9,7 @@ extension Generators { /// A Generator that generates a given `Sequence` of elements. When the generator exhausts the elements in the sequence, it will begin to return nil. /// -/// - Warning: This generator is stateful and cannot be restarted. Pay attention to how it to how the reference is being retained and do not share the generator to unsuspecting consumers. +/// - Warning: This generator is stateful and cannot be restarted. Pay attention to how the reference is being retained and do not share the generator to unsuspecting consumers. private class Iterate: Generator where Elements: Swift.Sequence { /// Creates a Generator for a sequence of elements. diff --git a/Sources/Genything/Generators/Generators+loop.swift b/Sources/Genything/Generators/Generators+loop.swift index e38fc055..5fa98e4a 100644 --- a/Sources/Genything/Generators/Generators+loop.swift +++ b/Sources/Genything/Generators/Generators+loop.swift @@ -13,8 +13,8 @@ extension Generators { /// /// Since this generator will comprehensibly examine all elements of the collection it can be used to display all possible configurations or test all possible values when the problem space is small and known. /// -/// - Warning: This generator is stateful and cannot be restarted. Pay attention to how it to how the reference is being retained and do not share the generator to unsuspecting consumers. -private class Loop: Generator where Elements: Swift.Collection { +/// - Warning: This generator is stateful and cannot be restarted. Pay attention to how the reference is being retained and do not share the generator to unsuspecting consumers. +class Loop: Generator where Elements: Swift.Collection { /// Creates a Generator that loops the provided collection of elements. /// diff --git a/Tests/GenythingTests/Generators/Generators_CollectTests.swift b/Tests/GenythingTests/Generators/Generators_CollectTests.swift new file mode 100644 index 00000000..0e8daeee --- /dev/null +++ b/Tests/GenythingTests/Generators/Generators_CollectTests.swift @@ -0,0 +1,13 @@ +import XCTest +@testable import Genything + +final class Generators_CollectTests: XCTestCase { + func test_collecting_to_alternate_generators() { + testAllSatisfy(Generators.collect([ + Generators.constant(0), + Generators.constant(1) + ])) { values in + values[0] == 0 && values[1] == 1 + } + } +} diff --git a/Tests/GenythingTests/Generators/Generators_MergeTests.swift b/Tests/GenythingTests/Generators/Generators_MergeTests.swift new file mode 100644 index 00000000..1125efa0 --- /dev/null +++ b/Tests/GenythingTests/Generators/Generators_MergeTests.swift @@ -0,0 +1,17 @@ +import XCTest +@testable import Genything + +final class Generators_MergeTests: XCTestCase { + func test_merging_generators() { + var curr = 0 + testAllSatisfy(Generators.merge([ + Generators.constant(0), + Generators.constant(1) + ])) { value in + defer { + curr = (curr + 1) % 2 + } + return value == curr + } + } +}