我希望消除Foo
的歧义。其中一些需要拆分为单独的实例。每个都需要一个独特的,连续的Id。
val maxId: Long = foos.map(_.id).max()
foos.flatMap { foo =>
if (foo.bar) List(foo, foo.copy(id = ???, ...))
else List(foo)
}
在Scala中,我使用了foldLeft。使用Spark,我能想到的最好的是flatMap到(Foo, Option[Long])
,过滤_._2.isEmpty
,zipWithIndex和join。有更聪明的方法吗?
e.g。给定
case class Foo(id: Long) {
val bar: Boolean = id % 2 == 1
}
此输入
RDD( Foo(1), Foo(2), Foo(3) )
应该成为
RDD( Foo(1), Foo(2), Foo(3), Foo(4), Foo(5) )
因为Foo(1)
和Foo(3)
已展开并接受了下一个可用的ID(4& 5)。
答案 0 :(得分:1)
在任何分布式系统中,可以彼此独立生成的标识优于顺序生成器。
好方法是.copy( id = randomLong )
,最好的方式是.copy( id = UUID.randomUUID() )
但问题是关于连续 ID的具体问题。我对该案的提议是
import Numeric.Implicits._
import scala.reflect.ClassTag
abstract class UpdateIDS[T: ClassTag, Id: Numeric : ClassTag] extends Serializable {
def getId(elem: T): Id
def setId(elem: T, id: Id): T
def shouldChange(elem: T): Boolean
val Id = implicitly[Numeric[Id]]
def apply(xs: RDD[T]): RDD[T] = {
val next = xs.map(getId).max + Id.one
val counts: Seq[(Int, Int)] = xs.mapPartitionsWithIndex { (idx, elems) =>
Iterator.single(idx, elems.count(shouldChange))
}.collect.view
val starts = counts.map(_._2).map(Id.fromInt).scanLeft(next)(_ + _)
val startMapLocal = counts.zip(starts).map { case ((idx, _), start) => (idx, start) }.toMap
val startMap = xs.context.broadcast(startMapLocal)
xs.mapPartitionsWithIndex { case (idx, elems) =>
elems.scanLeft((List.empty[T], startMap.value(idx))) { (pair, elem) =>
pair match {
case (_, counter) if shouldChange(elem) => (List(elem, setId(elem, counter)), counter + Id.one)
case (_, counter) => (List(elem), counter)
}
}.flatMap { _._1 }
}
}
}
你可以轻松定义
object fooUpdateId extends UpdateIDS[Foo, Int] {
def getId(foo: Foo) = foo.id
def setId(foo: Foo, id: Int) = foo.copy(id = id)
def shouldChange(foo: Foo) = foo.id % 2 == 1
}
然后运行
val foosUpdated = fooUpdateId(foos)
重要提示,此处生成的集合的顺序会被更改,以获得更高效的解决方案。如果您需要在不那么大的RDD中订购,那么您可以使用sortBy
。
另请注意,使用scalaz中的UpdadeIDs
和mapAccumL
可能会稍微简化Lens
的实现,但我选择避免使用外部库。