【问题标题】:How to create a generator in Swift?如何在 Swift 中创建生成器?
【发布时间】:2017-10-30 09:06:01
【问题描述】:

我可以在 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

在我的情况下,我想使用 generator 而不是迭代器,因为我不想将序列的中间结果存储在每个 next 中。

【问题讨论】:

  • 您能补充一点描述吗? (我没有对你投反对票。)不了解 Python - 但知道它只是另一种语言 - 我用谷歌搜索了 Python “生成器”是什么。所以现在我明白它是一个简单的迭代循环......好吧,(1) “yield”到底是什么?(2)你想做什么——一般来说,不可知论的语言术语——你想做什么?
  • @dfd 我在 Python 中使用代码,因为我不知道如何表达什么是“产量”和什么是“生成器”。好吧...使用“yield”,您返回到调用生成器的表达式,并在循环结束时返回到最后调用的 yield。如果您不想将中间结果存储在每个迭代器中,这将非常有用——这正是我的情况。无论如何,我编辑了我的问题。
  • Swift 没有“yield”语句或协程,比较stackoverflow.com/questions/43505101/…
  • 这是一个使用线程的仿真:github.com/JadenGeller/Yield(我没有尝试过)。
  • 我目前正在制定一项提案,在 Swift 中引入对生成器的语法支持,并寻求对此的反馈。我在这里更详细地描述了它gist.github.com/maxdesiatov/8ae5c0eb747cbda47e641a8e423a1e83

标签: swift generator


【解决方案1】:

Walter 提供了很多很好的信息,通常你不应该在 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(),那么是的,存储状态是您的问题。必须做点什么。但我发现这一切都极为罕见,并且经常在出现时建议过度设计。

【讨论】:

    【解决方案2】:

    了解生成器的工作原理(以及为什么它们在 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 会自动执行“写入时复制”的操作。这意味着许多使用 Python 生成器来避免对象集合(数组、列表、字典等)的大量复制成本的情况在 Swift 中是不必要的。您可以使用其中一种使用写时复制的类型免费获得此功能。

    Which value types in Swift supports copy-on-write?

    也可以使用包装器强制几乎所有对象在写入时复制,即使它不是集合的一部分:

    How can I make a container with copy-on-write semantics?

    swift 中的优化通常意味着您不必编写生成器。如果您确实需要(通常是因为数据量大、科学计算),则可以如上所述。

    【讨论】:

    • OP 在询问如何避免存储中间值;无论是写时复制,还是您的迭代器协议示例,都不会避免存储中间值。 (就此而言,OP 的原始 python 代码也存储了中间值,但请参阅我对 OP 问题的评论。)
    【解决方案3】:

    我有一个类似于上面的解决方案,但对它有一点“屈服”的感觉。

    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)")
    }
    

    【讨论】:

      【解决方案4】:

      根据你提供的代码和我对生成器的一点了解,你可以做类似的事情

      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)
      }
      

      【讨论】:

      • 正是我想要的
      猜你喜欢
      • 2018-12-18
      • 2010-10-16
      • 1970-01-01
      • 2017-05-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-31
      • 1970-01-01
      相关资源
      最近更新 更多