如何在Swift中创建一个生成器?

6

我能在Swift中创建一个生成器吗?

利用迭代器,我需要存储中间结果,例如:

struct Countdown: IteratorProtocol, Sequence {

    private var value = 0

    init(start: Int) {
        self.value = start
    }

    mutating func next() -> Int? {
        let nextNumber = value - 1
        if nextNumber < 0 {
            return nil
        }

        value -= 1

        return nextNumber
    }
}

for i in Countdown(start: 3) {
    print(i)
} // print 1 2 3

在这个例子中,我需要存储value
在我的情况下,我想使用生成器而不是迭代器,因为我不想在每个next中存储我的序列的中间结果。

你能再加点描述吗?(我没有给你投反对票。)虽然不理解Python,但知道它只是另一种语言,我搜索了一下Python的“生成器”是什么。现在我明白它只是一个简单的迭代循环...那么,(1)“yield”到底是什么?(2)你试图做什么 - 用通用的语言术语来说? - user7014451
我使用Python编写代码,因为我不知道如何表达“yield”和“generator”。好吧...使用“yield”,您将返回调用生成器的表达式,并在循环结束时返回最后一个调用的yield。如果您不想在每个迭代器中存储中间结果,那么这非常有用-正是我的情况。无论如何,我编辑了我的问题。 - macabeus
1
Swift没有“yield”语句或协程,请参考https://stackoverflow.com/questions/43505101/swift-equivalent-of-unity3d-coroutines。 - Martin R
这里有一个使用线程的仿真示例:https://github.com/JadenGeller/Yield(我没有尝试过)。 - Martin R
1
我目前正在制定一个提案,旨在为Swift引入生成器的语法支持,并寻求对此的反馈。我在这里更详细地描述了它:https://gist.github.com/maxdesiatov/8ae5c0eb747cbda47e641a8e423a1e83 - Max Desiatov
显示剩余3条评论
4个回答

9
理解生成器的工作原理(以及它们在Swift中为什么不那么重要)对于从Python转过来的人来说,一开始可能会很困难。
在Swift v2.1之前,有一个叫做GeneratorType的协议。这在Swift v3.0+中被重命名为IteratorProtocol。您可以符合此协议,以创建自己的对象,执行类似于Python中可以执行的即时计算。
更多信息可以在Apple文档中找到:IteratorProtocol IteratorProtocol页面上的一个简单示例:
struct CountdownIterator: IteratorProtocol {
    let countdown: Countdown
    var times = 0

    init(_ countdown: Countdown) {
        self.countdown = countdown
    }

    mutating func next() -> Int? {
        let nextNumber = countdown.start - times
        guard nextNumber > 0
            else { return nil }

        times += 1
        return nextNumber
    }
}

let threeTwoOne = Countdown(start: 3)
for count in threeTwoOne {
    print("\(count)...")
}
// Prints "3..."
// Prints "2..."
// Prints "1..."

然而,您需要考虑为什么要使用生成器:

Swift 自动执行称为“写时复制”的操作。这意味着,在 Swift 中,许多使用 Python 生成器来避免对象集合(数组、列表、字典等)的大量拷贝成本的情况是不必要的。通过使用使用写时复制的类型之一,您可以免费获得此功能。

Swift 中哪些值类型支持写时复制?

即使它不是集合的一部分,也可以使用包装器强制几乎任何对象进行写时复制:

如何创建具有写时复制语义的容器?

Swift 中的优化通常意味着您不需要编写生成器。如果确实需要(通常是因为数据重、科学计算),则可以按上述方式实现。


OP询问如何避免存储中间值;无论是写时复制还是您的迭代器协议示例,都无法避免存储中间值。(说到这一点,OP的原始Python代码也存储了中间值,但请参见我对OP问题的评论。) - max

1
沃尔特提供了很多好的信息,通常你不应该在Swift中这样做,但即使你想要一个迭代器,正确的方法是使用组合,而不是构建自己的迭代器。Swift有很多现有的序列可以组合起来创建你想要的内容,而不需要维护自己的状态。因此,在你的例子中,你将会使用范围的迭代器:
struct Countdown: Sequence {

    private var value = 0

    init(start: Int) {
        self.value = start
    }

    func makeIterator() -> AnyIterator<Int> {
        return AnyIterator((0..<value).reversed().makeIterator())
    }
}

for i in Countdown(start: 3) {
    print(i)
} // print 1 2 3

这种类型的函数需要保持状态,这是它们本质的要求(即使在协程世界中也是如此)。不直接维护状态也可以,只需委托给更原始的类型。Swift有几十个内置迭代器可用于构建大多数可能需要的东西,并且任何迭代器都可以提升为AnyIterator以隐藏实现细节。如果您有一个足够自定义需要真正需要next(),那么是的,存储状态就是您的问题。必须有某种方式来处理它。但我发现这种情况非常罕见,并且经常暗示着过度设计。


1
基于您提供的代码和我所了解的生成器知识,您可以这样做:
struct Countdown {
    private var _start = 0
    private var _value = 0

    init(value: Int) {
        _value = value
    }

    mutating func getNext() -> Int? {
        let current = _start
        _start += 1
        if current <= _value {
            return current
        } else {
            return nil
        }
    }
}

然后无论您在何处使用它,都可以执行类似以下操作

var counter = Countdown(value: 5)
while let value = counter.getNext() {
    print(value)
}

正是我所需要的。 - Jako

1
我有一个类似于上面的解决方案,但是感觉更加“yield-y”。
struct Countdown
{
    static func generator(withStart: Int) -> () -> Int?
    {
        var start = withStart + 1
        return {
            start = start - 1
            return start > 0 ? start : nil
        }
    }
}

let countdown = Countdown.generator(withStart: 5)

while let i = countdown()
{
    print ("\(i)")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接