【问题标题】:How to implement lazy sequence (iterable) in scala?如何在scala中实现惰性序列(可迭代)?
【发布时间】:2011-05-29 12:19:59
【问题描述】:

我想实现一个惰性迭代器,它在每次调用中产生下一个元素,在一个 3 级嵌套循环中。

scala 中是否有类似 c# 的 sn-p 的东西:

foreach (int i in ...)
    {
        foreach (int j in ...)
        {
            foreach (int k in ...)
            {
                 yield return do(i,j,k);
            }
        }
    }

谢谢嘟嘟

【问题讨论】:

标签: scala yield


【解决方案1】:

Scala 序列类型都有一个 .view 方法,该方法产生一个惰性集合的等价物。您可以在 REPL 中使用以下内容(在发出 :silent 以阻止它强制集合打印命令结果之后):

def log[A](a: A) = { println(a); a } 

for (i <- 1 to 10) yield log(i)

for (i <- (1 to 10) view) yield log(i)

第一个将打印出数字 1 到 10,第二个将在您实际尝试访问结果的这些元素之前打印出来。

Scala 中没有任何东西直接等同于 C# 的 yield 语句,它会暂停循环的执行。您可以使用为 scala 2.8 添加的 delimited continuations 实现类似的效果。

【讨论】:

    【解决方案2】:

    如果您将迭代器与++ 连接在一起,您将得到一个在两者上运行的迭代器。 reduceLeft 方法有助于将整个集合连接在一起。因此,

    def doIt(i: Int, j: Int, k: Int) = i+j+k
    (1 to 2).map(i => {
      (1 to 2).map(j => {
        (1 to 2).iterator.map(k => doIt(i,j,k))
      }).reduceLeft(_ ++ _)
    }).reduceLeft(_ ++ _)
    

    会产生你想要的迭代器。如果你想让它更懒惰,你也可以在前两个(1 to 2) 之后添加.iterator。 (当然,用您自己更有趣的收藏或范围替换每个(1 to 2)。)

    【讨论】:

    • 这听起来是我所拥有的最佳选择,但仍不完全是 C# 的产量,但它非常好。谢谢!
    【解决方案3】:

    您可以使用Sequence Comprehension 而不是Iterators 来获得您想要的:

    for {
        i <- (1 to 10).iterator
        j <- (1 to 10).iterator
        k <- (1 to 10).iterator
    } yield doFunc(i, j, k)
    

    如果你想创建一个惰性迭代器(而不是一个惰性迭代器),请改用Views

    for {
        i <- (1 to 10).view
        j <- (1 to 10).view
        k <- (1 to 10).view
    } yield doFunc(i, j, k)
    

    根据你想变得多么懒惰,你可能不需要对迭代器/视图的所有调用。

    【讨论】:

      【解决方案4】:

      如果您的 3 个迭代器通常都很小(即,您可以完全迭代它们而不用担心内存或 CPU)并且昂贵的部分是计算给定 i、j 和 k 的结果,您可以使用 Scala 的 Stream 类。

      val tuples = for (i <- 1 to 3; j <- 1 to 3; k <- 1 to 3) yield (i, j, k)
      val stream = Stream(tuples: _*) map { case (i, j, k) => i + j + k }
      stream take 10 foreach println
      

      如果你的迭代器对于这种方法来说太大了,你可以扩展这个想法并创建一个元组流,通过保持每个迭代器的状态来懒惰地计算下一个值。例如(尽管希望有人有更好的定义产品方法的方法):

      def product[A, B, C](a: Iterable[A], b: Iterable[B], c: Iterable[C]): Iterator[(A, B, C)] = {
        if (a.isEmpty || b.isEmpty || c.isEmpty) Iterator.empty
        else new Iterator[(A, B, C)] {
          private val aItr = a.iterator
          private var bItr = b.iterator
          private var cItr = c.iterator
      
          private var aValue: Option[A] = if (aItr.hasNext) Some(aItr.next) else None
          private var bValue: Option[B] = if (bItr.hasNext) Some(bItr.next) else None
      
          override def hasNext = cItr.hasNext || bItr.hasNext || aItr.hasNext
          override def next = {
            if (cItr.hasNext)
              (aValue get, bValue get, cItr.next)
            else {
              cItr = c.iterator
              if (bItr.hasNext) {
                bValue = Some(bItr.next)
                (aValue get, bValue get, cItr.next)
              } else {
                aValue = Some(aItr.next)
                bItr = b.iterator
                (aValue get, bValue get, cItr.next)
              }
            }
          }
        }
      }
      
      val stream = product(1 to 3, 1 to 3, 1 to 3).toStream map { case (i, j, k) => i + j + k }
      stream take 10 foreach println
      

      这种方法完全支持无限大小的输入。

      【讨论】:

        【解决方案5】:

        我认为下面的代码是您真正要寻找的...我认为编译器最终会将其翻译成与 Rex 提供的地图代码等效的代码,但更接近原始示例的语法:

        scala> def doIt(i:Int, j:Int) = { println(i + ","+j); (i,j); }
        doIt: (i: Int, j: Int)(Int, Int)
        
        scala> def x = for( i <- (1 to 5).iterator; 
                            j <- (1 to 5).iterator ) yield doIt(i,j)
        x: Iterator[(Int, Int)]
        
        scala> x.foreach(print)
        1,1
        (1,1)1,2
        (1,2)1,3
        (1,3)1,4
        (1,4)1,5
        (1,5)2,1
        (2,1)2,2
        (2,2)2,3
        (2,3)2,4
        (2,4)2,5
        (2,5)3,1
        (3,1)3,2
        (3,2)3,3
        (3,3)3,4
        (3,4)3,5
        (3,5)4,1
        (4,1)4,2
        (4,2)4,3
        (4,3)4,4
        (4,4)4,5
        (4,5)5,1
        (5,1)5,2
        (5,2)5,3
        (5,3)5,4
        (5,4)5,5
        (5,5)
        scala> 
        

        您可以从输出中看到,“doIt”中的 print 直到 x 的下一个值被迭代后才被调用,并且这种 for 生成器样式比一堆嵌套映射更易于读/写.

        【讨论】:

          【解决方案6】:

          把问题颠倒过来。将“do”作为闭包传入。这就是使用函数式语言的全部意义

          【讨论】:

            【解决方案7】:

            Iterator.zip 会做到的:

            iterator1.zip(iterator2).zip(iterator3).map(tuple => doSomething(tuple))
            

            【讨论】:

            • 不,这不会产生嵌套循环所产生的笛卡尔积。
            【解决方案8】:

            只需阅读侧面显示的 20 个左右的第一个相关链接(实际上,当您第一次写问题标题时显示给您的位置)。

            【讨论】:

            • 我已经彻底阅读了这些链接——它们都没有显示出我正在寻找的东西,而且它似乎根本不存在...... C# 的 yield 更强大(至少对于我需要的)。另外,我认为在提出这些先前的问题之后,有可能将此功能添加到 scala 中。
            • @duduamar 除了通过 continuation 实现的那个之外,没有类似于 C# 的yield,这几乎是我的观点。这个问题已经被问过。
            猜你喜欢
            • 2012-09-20
            • 1970-01-01
            • 2011-03-15
            • 1970-01-01
            • 2019-07-08
            • 2013-01-02
            • 1970-01-01
            • 2015-11-12
            • 1970-01-01
            相关资源
            最近更新 更多