如何在scala中实现惰性序列(可迭代)?

发布于 2024-10-08 12:36:43 字数 311 浏览 6 评论 0 原文

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

scala 中是否有与 c# 代码片段类似的内容:

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

谢谢,Dudu

I want to implement a lazy iterator that yields the next element in each call, in a 3-level nested loop.

Is there something similar in scala to this snippet of c#:

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

Thanks, Dudu

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

梦晓ヶ微光ヅ倾城 2024-10-15 12:36:43

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 添加。

Scala sequence types all have a .view method which produces a lazy equivalent of the collection. You can play around with the following in the REPL (after issuing :silent to stop it from forcing the collection to print command results):

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)

The first will print out the numbers 1 to 10, the second will not until you actually try to access those elements of the result.

There is nothing in Scala directly equivalent to C#'s yield statement, which pauses the execution of a loop. You can achieve similar effects with the delimited continuations which were added for scala 2.8.

彡翼 2024-10-15 12:36:43

如果将迭代器与 ++ 连接在一起,您将得到一个在两者上运行的迭代器。 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 到 2) 替换为您自己更有趣的集合或范围。)

If you join iterators together with ++, you get a single iterator that runs over both. And the reduceLeft method helpfully joins together an entire collection. Thus,

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(_ ++ _)

will produce the iterator you want. If you want it to be even more lazy than that, you can add .iterator after the first two (1 to 2) also. (Replace each (1 to 2) with your own more interesting collection or range, of course.)

天邊彩虹 2024-10-15 12:36:43

您可以在 序列理解 ://www.scala-lang.org/api/current/index.html#scala.collection.Iterator" rel="nofollow">Iterators 来获得你想要的:

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

如果你想创建一个惰性的 Iterable (而不是惰性迭代器)使用 Views 代替:

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

取决于惰性程度如果您愿意,您可能不需要对迭代器/视图的所有调用。

You can use a Sequence Comprehension over Iterators to get what you want:

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

If you want to create a lazy Iterable (instead of a lazy Iterator) use Views instead:

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

Depending on how lazy you want to be, you may not need all of the calls to iterator / view.

戏剧牡丹亭 2024-10-15 12:36:43

如果您的 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

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

If your 3 iterators are generally small (i.e., you can fully iterate them without concern for memory or CPU) and the expensive part is computing the result given i, j, and k, you can use Scala's Stream class.

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

If your iterators are too large for this approach, you could extend this idea and create a Stream of tuples that calculates the next value lazily by keeping state for each iterator. For example (although hopefully someone has a nicer way of defining the product method):

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

This approach fully supports infinitely sized inputs.

手心的海 2024-10-15 12:36:43

我认为下面的代码是您真正要寻找的...我认为编译器最终会将其翻译成与 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> 

您可以从输出显示,直到迭代 x 的下一个值时,才会调用“doIt”中的 print,这种风格的 for 生成器比一堆嵌套映射更容易读/写。

I think the below code is what you're actually looking for... I think the compiler ends up translating it into the equivalent of the map code Rex gave, but is closer to the syntax of your original example:

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> 

You can see from the output that the print in "doIt" isn't called until the next value of x is iterated over, and this style of for generator is a bit simpler to read/write than a bunch of nested maps.

七度光 2024-10-15 12:36:43

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

Turn the problem upside down. Pass "do" in as a closure. That's the entire point of using a functional language

迷爱 2024-10-15 12:36:43

Iterator.zip 会做到这一点:

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

Iterator.zip will do it:

iterator1.zip(iterator2).zip(iterator3).map(tuple => doSomething(tuple))
陈独秀 2024-10-15 12:36:43

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

Just read the 20 or so first related links that are show on the side (and, indeed, where shown to you when you first wrote the title of your question).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文