Scala Spark——错误:类型不匹配;找到:(Int,String)必需:TraversableOnce[?]

Scala Spark——错误:类型不匹配;找到:(Int,String)必需:TraversableOnce[?],scala,apache-spark,Scala,Apache Spark,我对spark编程和scala是新手,我无法理解map和flatMap之间的区别。在使用flatMap时,为什么方法中使用的“选项”工作正常 def parseNames(line: String) : Option[(Int,String)] = { var fields = line.split('\"') if (fields.length >1) { return Some(fields(0).trim().toInt,fields(1) ) } else

我对spark编程和scala是新手,我无法理解map和flatMap之间的区别。在使用flatMap时,为什么方法中使用的“选项”工作正常

def parseNames(line: String) : Option[(Int,String)]  = {
  var fields = line.split('\"')
  if (fields.length >1) {
    return Some(fields(0).trim().toInt,fields(1) )
  }
  else {
    return None
  }
}
def main(args: Array[String]) {
  val sc = new SparkContext("local[*]","DemoHero")
  val txt= sc.textFile("../marvel-names1.txt")  
  val rdd = txt.flatMap(parseNames)
但如果没有“选项”,就会出现错误:

def parseNames(line: String) : (Int, String)  = {
  var fields = line.split('\"')    
  (fields(0).trim().toInt,fields(1) )
}

def main(args: Array[String]) {
  val sc = new SparkContext("local[*]","DemoHero")   
  val txt= sc.textFile("../marvel-names1.txt")  
  val rdd = txt.flatMap(parseNames)
根据我的理解,flatmap将Rdd放入String/Int Rdd的集合中。我在想,在这种情况下,两者都应该工作,没有任何错误。请让我知道我在哪里犯错

def parseNames (line: String): Option[(Int,String)]  = {
  var fields = line.split('\"')
  if (fields.length > 1) {
    Some (fields(0).trim ().toInt, fields(1))
  }
  else {
    None
  }
}
(删除了嘈杂的“返回”)


那么,
None
何时返回?如果fields.length不大于1。如果至少没有两个字段(字段(0)和字段(1)),
fields(0).trim().toInt
可能会成功,但
fields(1)
将失败。

flatMap
将要求将iterables作为被调用函数的返回类型。因为
flatMap
将迭代iterable的每个元素并返回每个展平的元素

在第一个
parseNames
函数中,返回
选项[(Int,String)]
,它是一个容器,由于使用隐式函数,它可以像iterable一样运行。所以
flatMap
起作用了

但是在第二个
parseNames
中,返回的
Tuple2[Int,String]
不是iterable。As
Tuple2
无法迭代,但可以使用
\u1
\u2
获取元素。因此,
flatMap
向您显示了编译错误

我希望解释清楚

如果将
Array
作为

def parseNames(line: String) : Array[(Int, String)]  = {
  var fields = line.split('\"')

  Array((fields(0).trim().toInt,fields(1)))
}
def parseNames(line: String) : List[(Int, String)]  = {
  var fields = line.split('\"')

  List((fields(0).trim().toInt,fields(1)))
}
列表
作为

def parseNames(line: String) : Array[(Int, String)]  = {
  var fields = line.split('\"')

  Array((fields(0).trim().toInt,fields(1)))
}
def parseNames(line: String) : List[(Int, String)]  = {
  var fields = line.split('\"')

  List((fields(0).trim().toInt,fields(1)))
}
Seq
as

def parseNames(line: String) : Seq[(Int, String)]  = {
  var fields = line.split('\"')

  Seq((fields(0).trim().toInt,fields(1)))
}

因为它们都是可编辑的,就像选项一样。

TL;DR:有一个从
选项
Iterable
的隐式转换,这就是为什么您的第一个
flatMap
不会失败


从理论上看,根本不清楚RDD为什么期望 返回类型中带有
TraversableOnce
的参数将接受返回
选项的函数,因为
选项
不扩展
可遍历一次

但是,如果打印由
flatMap
生成的去糖化代码,则会出现以下合成函数定义:

@SerialVersionUID(value = 0) final <synthetic> class anonfun$1 extends scala.runtime.AbstractFunction1 with Serializable {
  final def apply(line: String): Iterable = scala.this.Option.option2Iterable(org.example.ClassName.parseNames$1(line));
  final <bridge> <artifact> def apply(v1: Object): Object = anonfun$1.this.apply(v1.$asInstanceOf[String]());
  def <init>(): <$anon: Function1> = {
    anonfun$1.super.<init>();
    ()
  }
}
这将是一个简单的编译器错误


您的第二个代码片段不应该编译,幸运的是,它确实不应该编译:对是不可遍历的,所以
flatMap
不接受
parseNames(行:String):(Int,String)
作为参数。你想在这里用的是
映射
,因为您希望将每个字符串映射到一对
(Int,string)

flatMap
用于不同的用例:它用于将原始集合中的每个元素转换为 另一个集合,然后将所有生成的集合展平为单个集合,例如

sc.parallelize(List(1, 2, 3)).flatMap{ x => List(x, x*x, x*x*x) }
将首先为每个
x
生成一个
TraversableOnce

List(1,1,1)
List(2,4,8)
List(3,9,27)
然后将它们粘在一起,这样您就可以获得带有条目的RDD

1,1,1,2,4,8,3,9,27
它以同样的方式使用
选项
,因为“道德上”它类似于一个包含0到1个元素的列表,尽管它在继承层次结构中没有明确说明这一点



注意“不应该编译”的表述:每当我写(你的代码或其他代码)“不应该编译”时,我并不是说我通常希望你的代码中有编译错误。我的意思是,如果代码中有问题,编译器应该尽快生成一条清晰的错误消息。

选项[(Int,String)]
不是一个可编辑的。@Andreytukin感谢您的更正。我已经更新了我的答案:)我希望它现在很好,是的。。。除了
容器
甚至不是一个特征,而且
平面图
非常清楚地表明它期望返回
可遍历一次
,但是
选项
也不是子类
可遍历一次
。有一个令人讨厌的隐式转换故事正在发生,请看我回答中的血淋淋的细节。是的,我看到了@Andreytukin:)你的第二个代码片段不起作用的原因应该是显而易见的。为什么第一个代码段有效:这是个谜,因为
选项
没有扩展
可遍历一次