在许多中拆分流

时间:2013-06-14 18:49:13

标签: scala scala-collections

我想知道是否有一种优雅的方式来实现这样的目标:

val l = Stream.from(1)

val parts = l.some_function(3)  //any number

parts.foreach( println(_) )

> 1,4,7,10... 
> 2,5,8,11...
> 3,6,9,12...

实际上我需要在Streams上进行并行化这样的操作 - 在多个actor之间分割数据而不将整个内容加载到内存中。

6 个答案:

答案 0 :(得分:4)

来自Split a scala list into n interleaving lists的答案完全符合条件,稍作修改以适应Streams:

def round[A](seq: Iterable[A], n: Int) = {
  (0 until n).map(i => seq.drop(i).sliding(1, n).flatten)
}
round(Stream.from(1),3).foreach(i => println(i.take(3).toList))
List(1, 4, 7)
List(2, 5, 8)
List(3, 6, 9)

答案 1 :(得分:2)

我唯一能想到的是:

def distribute[T](n: Int)(x: Stream[T]) = (0 until n).map { p =>
  x.zipWithIndex.collect {
    case (e,i) if i % n == p => e
  }
}

这有点难看,因为每个子流必须完全遍历主流。但我认为你不能在保持(明显)不变性的同时缓解这一点。

您是否考虑过将各个任务分配给演员并拥有一个完成此任务的“任务分配器”?

答案 2 :(得分:2)

一种简单的方法是为所需的索引生成算术序列,然后将其映射到流。 apply方法将提取相应的值:

def f[A]( s:Stream[A], n:Int ) =
  0 until n map ( i => Iterator.iterate(0)(_+n) map ( s drop i ) )

f( Stream from 1, 3 ) map ( _ take 4 mkString "," )
// Vector(1,4,7,10, 2,5,8,11, 3,6,9,12)

更高性能的解决方案将使用迭代器,其下一个方法只是在算术序列的下一个索引处返回流中的值:

def comb[A]( s:Stream[A], first:Int, step:Int ):Iterator[A] = new Iterator {
  var i       = first - step
  def hasNext = true
  def next    = { i += step; s(i) }
}
def g[A]( s:Stream[A], n:Int ) =
  0 until n map ( i => comb(s,i,n) )

g( Stream from 1, 3 ) map ( _ take 4 mkString "," )
// Vector(1,4,7,10, 2,5,8,11, 3,6,9,12)

你提到这是针对演员的 - 如果这是Akka,也许你可以使用round-robin router

更新:上面(显然是错误的)假设只要程序正在运行就可以做更多的工作,所以hasNext总是返回true;请参阅Mikhail的答案,了解适用于有限流的版本。

更新:米哈伊尔已经确定this answer to a prior StackOverflow question实际上有一个适用于有限和无限流的答案(虽然它看起来不像迭代器那样表现得好)。

答案 3 :(得分:0)

scala> (1 to 30 grouped 3).toList.transpose foreach println
List(1, 4, 7, 10, 13, 16, 19, 22, 25, 28)
List(2, 5, 8, 11, 14, 17, 20, 23, 26, 29)
List(3, 6, 9, 12, 15, 18, 21, 24, 27, 30)

答案 4 :(得分:0)

我在Scala库中没有找到任何这样的函数,所以我改编了AmigoNico的迭代器变体。该代码处理有限和无限集合。

  def splitRoundRobin[A](s: Iterable[A], n: Int) = {
    def comb[A](s: Iterable[A], first: Int, step: Int): Iterator[A] = new Iterator[A] {
      val iter = s.iterator
      var nextElem: Option[A] = iterToNext(first)
      def iterToNext(elemsToSkip: Int) = {
        iterToNextRec(None, elemsToSkip)
      }
      def iterToNextRec(next: Option[A], repeat: Int): Option[A] = repeat match {
        case 0 => next
        case _ => if (iter.hasNext) iterToNextRec(Some(iter.next()), repeat - 1) else None
      }
      def hasNext = nextElem.isDefined || {
        nextElem = iterToNext(step)
        nextElem.isDefined
      }
      def next = {
        var result = if (nextElem.isDefined) nextElem.get else throw new IllegalStateException("No next")
        nextElem = None
        result
      }
    }
    0 until n map (i => comb(s, i, n))
  }  

  splitRoundRobin(1 to 12 toStream, 3) map (_.toList.mkString(","))
 // Vector(3,6,9,12, 1,4,7,10, 2,5,8,11)

  splitRoundRobin(Stream from 1, 3) map (_.take(4).mkString(","))
//> Vector(3,6,9,12, 1,4,7,10, 2,5,8,11)

答案 5 :(得分:0)

def roundRobin[T](n: Int, xs: Stream[T]) = {
  val groups = xs.grouped(n).map(_.toIndexedSeq).toStream
  (0 until n).map(i => groups.flatMap(_.lift(i)))
}

适用于无限的情况:

scala> roundRobin(3, Stream.from(0)).map(_.take(3).force.mkString).mkString(" ")
res6: String = 036 147 258

使用flatMap / lift代替普通map / apply表示即使输入是有限的且长度不是n的倍数,它也能正常工作: / p>

scala> roundRobin(3, Stream.from(0).take(10)).map(_.mkString).mkString(" ")
res5: String = 0369 147 258