如何基于空值过滤FlatMap

时间:2015-11-05 09:27:09

标签: scala graph apache-spark vertices

我使用以下代码生成图表。

注意: - raw(1)= name,raw(2)= type

 val Nodes: RDD[(VertexId, (String, String))] = sc.textFile(nodesFile).flatMap {
  line =>
    if (!line.isEmpty && line(0) != '#') {
      val row = line.split(";,;,;")
      if (row.length == 3) {
        if (row(0).length > 0 && row(1).length > 0 && row(2).length > 0 && row(0).forall(_.isDigit) && row(2).toString.toUpperCase != "AB" && row(2).toString.toUpperCase != "XYZ") {
          List((row(0).toLong, (row(1).toString.toUpperCase, row(2).toString.toUpperCase)))
        } else { None }
      } else { None }
    } else {
      None
    }
}

所以它会像这样生成地图。

(11,(SAMSUNG_PHONE,Item))
(0,null)
(1,(Flying,PC))
(6,null)

意味着顶点0和6的值是' AB'或者' XYZ'。这就是为什么它插入null但我想要过滤并想要删除这个空值节点..我试过但没有得到它。

请给我提示或参考。

2 个答案:

答案 0 :(得分:2)

解决方案

假设输入文件包含内容

0;foo;AB
1;cool,stuff
2;other;things
6;foo;XYZ
3;a;b

你的代码几乎正常运作。

调整拆分模式(见下文)和抛光返回值(List()而不是None)后,代码可以正常工作:

configuredUnitTest("Test SO") { sc =>
  import org.apache.spark.rdd.RDD

  val sqlContext = new SQLContext(sc)

  val nodesFile = "/home/martin/input.txt"

  val nodes: RDD[(Long, (String, String))] = sc.textFile(nodesFile).flatMap {
    line =>
      if (!line.isEmpty && line(0) != '#') {
        val row = line.split("[,;]")
        if (row.length == 3) {
          if (row(0).length > 0 && row(1).length > 0 && row(2).length > 0 && row(0).forall(_.isDigit) && row(2).toString.toUpperCase != "AB" && row(2).toString.toUpperCase != "XYZ") {
            List((row(0).toLong, (row(1).toString.toUpperCase, row(2).toString.toUpperCase)))
          } else {
            List()
          }
        } else {
          List()
        }
      } else {
        List()
      }
  }

  println( nodes.count() )

  val result = nodes.collect()
  println( result.size )
  println( result.mkString("\n") )
}

结果是

3
3
(1,(COOL,STUFF))
(2,(OTHER,THINGS))
(3,(A,B))

代码缺陷

返回函数类型String => List [Long,(String,String)]

拒绝非匹配行的代码是冗长且不可读的。为什么不返回List()而不是None。证明的例子:

scala> val a = List(1,2,3)
a: List[Int] = List(1, 2, 3)

scala> a.flatMap( x => { if (x==1) List() else List(x)} )
res0: List[Int] = List(2, 3)

说完 后,您无需使用flatmap并过滤null

分割模式

您的正则表达式拆分模式错误

你的模式“;,;,;”说:当遇到序列“;,;,;”时拆分,因此“a;,;,; b”被分成a和b。这很可能你想要什么。相反,你想分开“;”或“,”,所以rexex说“;”或“,”是“[;,]”。

scala> val x ="a;b,c"
x: String = a;b,c

scala> x.split(";").mkString("|")
res2: String = a|b,c

scala> x.split(";,").mkString("|")
res3: String = a;b,c

scala> x.split("[;,]").mkString("|")
res4: String = a|b|c

更好的方法

使用filter和一些辅助函数,您的代码可以重写为

configuredUnitTest("Test SO") { sc =>
  import org.apache.spark.rdd.RDD

  val sqlContext = new SQLContext(sc)

  val nodesFile = "/home/martin/input.txt"

  def lengthOfAtLeastOne(x: String) : Boolean = x.length > 0
  def toUpperCase(x: String) = x.toString.toUpperCase

  val nodes = sc.textFile(nodesFile)
    .map( _.split("[;,]") )
    .filter( _.size == 3 )
    .filter( xs => ( lengthOfAtLeastOne(xs(0)) && lengthOfAtLeastOne(xs(1)) && lengthOfAtLeastOne(xs(2)) ) )
    .filter( xs => (toUpperCase(xs(2)) != "AB") && (toUpperCase(xs(2)) != "XYZ"))
    .map( xs => (xs(0).toLong, ( toUpperCase(xs(1)), toUpperCase(xs(2))) ))

  println( nodes.count() )

  val result = nodes.collect()
  println( result.size )
  println( result.mkString("\n") )

  println("END OF")
}

阅读好多了,是吗?

答案 1 :(得分:0)

我已经测试了您的代码,它似乎工作得很好。 我使用了Martin Senne的输入,也使用了他的正则表达式。 因为我不确切知道textFile是什么,所以我只是从文件中读取了一些行

val lines = Source.fromFile("so/resources/f").getLines.toList

val x = lines.flatMap {
  line =>
    if (!line.isEmpty && line(0) != '#') {
      val row = line.split("[;,]")
      if (row.length == 3) {
        if (row(0).length > 0 && row(1).length > 0 && row(2).length > 0 && row(0).forall(_.isDigit) && row(2).toString.toUpperCase != "AB" && row(2).toString.toUpperCase != "XYZ") {
          List((row(0).toLong, (row(1).toString.toUpperCase, row(2).toString.toUpperCase)))
        } else {
          None
        }
      } else {
        None
      }
    } else {
      None
    }
}

println(x.size)
x foreach println

所以对于这个输入:

0;foo;AB
1;cool,stuff
2;other;things
6;foo;XYZ
3;a;b

我得到了这个输出:

3
(1,(COOL,STUFF))
(2,(OTHER,THINGS))
(3,(A,B))

当然,您的代码可以添加许多修改:

.filterNot(_.startsWith("#")) // lines cannot start with #
      .map(_.split("[;,]")) // split the lines
      .filter(_.size == 3)  // each line must have 3 items in it
      .filter(line => line.filter(_.length > 0).size == 3) // and none of those items can be empty ***
      .filter(line => line(2).toUpperCase != "AB" ) // also those that have AB
      .filter(line => line(2).toUpperCase != "XYZ" ) // also those that have XYZ
      .map(line => (line(0).toLong, (line(1).toUpperCase, line(2).toUpperCase))) // the final format

***这个:

.filter(line => line.filter(_.length > 0).size == 3)

也可以这样写:

.map(_.filter(_.length > 0)) // each item must not be empty
.filter(_.size == 3)  // so the lines with less than 3 items will be removed; ***

另外,作为观察,您不必将toString放在String旁边。

相关问题