Spark:将aggregateByKey放入一对列表中

时间:2015-08-10 13:04:57

标签: scala apache-spark aggregation

我有一组键控记录,其中包含book id和reader id字段。

case class Book(book: Int, reader: Int)

如何使用aggregateByKey将具有相同密钥的所有记录合并为以下格式的一条记录:

(key:Int, (books: List:[Int], readers: List:[Int])) 

书籍是所有书籍的列表,读者是具有给定键的记录中所有读者的列表?

我的代码(如下)导致编译错误:

import org.apache.log4j.{Level, Logger}
import org.apache.spark.{SparkContext, SparkConf}

object Aggr {

  case class Book(book: Int, reader: Int)

  val bookArray = Array(
      (2,Book(book = 1, reader = 700)),
      (3,Book(book = 2, reader = 710)),
      (4,Book(book = 3, reader = 710)),
      (2,Book(book = 8, reader = 710)),
      (3,Book(book = 1, reader = 720)),
      (4,Book(book = 2, reader = 720)),
      (4,Book(book = 8, reader = 720)),
      (3,Book(book = 3, reader = 730)),
      (4,Book(book = 8, reader = 740))
  )

  def main(args: Array[String]) {
    Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
    Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
    // set up environment
    val conf = new SparkConf()
      .setMaster("local[5]")
      .setAppName("Aggr")
      .set("spark.executor.memory", "2g")
    val sc = new SparkContext(conf)

    val books = sc.parallelize(bookArray)
    val aggr = books.aggregateByKey((List()[Int], List()[Int]))
    ({case
      ((bookList:List[Int],readerList:List[Int]), Book(book, reader)) =>
      (bookList ++ List(book), readerList ++ List(reader))
      },
    {case ((bookLst1:List[Int], readerLst1:List[Int]),
    (bookLst2:List[Int], readerLst2:List[Int])
      ) => (bookLst1 ++ bookLst2, readerLst1 ++ readerLst2) })


  }
}

错误:

Error:(36, 44) object Nil does not take type parameters.
val aggr = books.aggregateByKey((List()[Int], List()[Int]))

Error:(37, 6) missing parameter type for expanded function The argument types of an anonymous function must be fully known. (SLS 8.5) Expected type was: ?
({case
 ^
                                       ^

更新

使用(List(0), List(0)初始化accumalator时,所有内容都会编译,但会在结果中插入额外的零。非常有趣:

val aggr :  RDD[(Int, (List[Int], List[Int]))] = books.aggregateByKey((List(0), List(0))) (
{case
  ((bookList:List[Int],readerList:List[Int]), Book(book, reader)) =>
  (bookList ++ List(book), readerList ++ List(reader))
  },
{case ((bookLst1:List[Int], readerLst1:List[Int]),
(bookLst2:List[Int], readerLst2:List[Int])
  ) => (bookLst1 ++ bookLst2, readerLst1 ++ readerLst2) }
)

这导致以下输出:

[Stage 0:>                                                          (0 + 0) / 5](2,(List(0, 1, 0, 8),List(0, 700, 0, 710)))
(3,(List(0, 2, 0, 1, 0, 3),List(0, 710, 0, 720, 0, 730)))
(4,(List(0, 3, 0, 2, 8, 0, 8),List(0, 710, 0, 720, 720, 0, 740)))

假设我可以将空列表作为初始化列表而不是带有零的列表,我当然不会有额外的零,列表会很好地连接。

有人可以解释一下为什么空列表初始值设定项(List(), List()导致错误并且(List(0), List(0)编译。它是Scala错误还是一个功能?

2 个答案:

答案 0 :(得分:2)

实际上你做的一切都很好,只是你的缩进/语法风格有点草率,你只需要从中移动一个括号:

val aggr = books.aggregateByKey((List()[Int], List()[Int]))
({case

进入这个:

val aggr = books.aggregateByKey((List[Int](), List[Int]())) (
    {case

这些链接可能会说明为什么这对您没有用处:

What are the precise rules for when you can omit parenthesis, dots, braces, = (functions), etc.?(第一个答案)

http://docs.scala-lang.org/style/method-invocation.html#suffix-notation

答案 1 :(得分:0)

回答您的更新 - 您错放了列表的类型声明。如果您将它们声明为List[Int]()而不是List()[Int],那么一切都会奏效。编译器错误消息正确地告诉您问题,但它并不容易理解。通过将[Int]放在最后,您将类型参数传递给List()函数的结果List()的结果是Nil - 表示空列表的单个对象 - 它不接受类型参数。

至于为什么List(0)也有效 - 如果可以的话,scala会执行类型推断。您已经声明了列表中的一个元素 - 即0,一个整数,因此它推断出这是一个List[Int]。但请注意,这不会声明一个空列表,而是一个单个零的列表。您可能希望改为使用List[Int]()

仅使用List()无法正常工作,因为scala无法推断出空列表的类型。