Spark - 错误:类型不匹配;发现:(Int,String)必需:TraversableOnce [?]

时间:2018-03-04 13:02:33

标签: scala apache-spark

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

@Requires("n != null")
public Natural(Natural n) {
  this.data = n.data;
}

但如果没有“选项”,则会出现错误:

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)

根据我的理解,flatmap使Rdd进入String / Int Rdd的集合。我想在这种情况下两者都应该没有任何错误。请让我知道我在哪里犯了错误。

3 个答案:

答案 0 :(得分:3)

TL; DR:存在从OptionIterable的隐式转换,这就是您的第一个 flatMap不会失败。

inheritance hierarchy of Option开始,为什么RDD的flatMap期望为什么并不清楚 返回类型中带有TraversableOnce的参数将接受返回Option的函数,因为 Option不会延伸TraversableOnce

但是,如果您打印由flatMap生成的desugared代码,则会显示以下合成函数定义:

@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>();
    ()
  }
}

详细信息并不重要,有些事情需要line: String并返回Iterable。 有趣的是Option.option2Iterable部分。

这是一个隐式转换defined directly on Option, 它会悄悄地将选项转换为IterableIterableTraversableOnce的特例。

这是编译器可以将option2Iterable潜入合成Function定义的方式 在您的方法和flatMap的调用之间进行调解。现在你有一个论点 键入String => Iterable[(Int, String)],以便flatMap编译好。

请注意,如果没有合成Function - 包装方法的实例,它将无法工作。如果你这样声明parseNames

def parseNames: String => Option[(Int,String)] = { line => 

这将是一个简单的编译器错误。

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

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

sc.parallelize(List(1, 2, 3)).flatMap{ x => List(x, x*x, x*x*x) }

首先会为每个TraversableOnce生成x

List(1,1,1)
List(2,4,8)
List(3,9,27)

然后将它们全部粘合在一起,这样就可以获得带条目的RDD

1,1,1,2,4,8,3,9,27

它以同样的方式与Option一起使用,因为“道德”它类似于具有0对1元素的列表,即使它没有在其继承层次结构中明确说明。

关于配方​​“不应该编译”的注意事项:每当我写下(你的代码或其他一些代码)“不应该编译”时,我并不是说我一般希望你你的代码中有编译错误。我的意思是如果代码中存在一些问题,编译器应该尽快产生明确的错误消息。

答案 1 :(得分:0)

def parseNames (line: String): Option[(Int,String)]  = {
  var fields = line.split('\"')
  if (fields.length > 1) {
    Some (fields(0).trim ().toInt, fields(1))
  }
  else {
    None
  }
}

(删除了嘈杂的&#34;返回&#34;)

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

答案 2 :(得分:0)

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

在您的第一个parseNames函数中,返回Option[(Int, String)],这是一个容器,由于使用隐式,它可以像 iterable 一样运行功能。所以flatMap工作了。

但是在您的第二个parseNames中,会返回Tuple2[Int, String],其中不是可迭代的。由于Tuple2无法迭代,但可以使用_1_2来获取元素。所以flatMap显示了编译错误。

我希望解释清楚。

如果您将parseNames作为

返回,则第二个Array会有效
def parseNames(line: String) : Array[(Int, String)]  = {
  var fields = line.split('\"')

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

List

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

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

Seq

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

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

因为所有这些都是 iterables ,因为Option是。