如何解析一些(子)字段为空的分隔字段?

时间:2017-06-27 06:51:59

标签: scala apache-spark rdd

我在spark-shell中使用Spark 2.1.1和Scala 2.11.8。

我的输入数据集类似于:

2017-06-18 00:00:00 , 1497769200 , z287570731_serv80i:7:175 , 5:Re

2017-06-18 00:00:00 , 1497769200 , p286274731_serv80i:6:100 , 138 

2017-06-18 00:00:00 , 1497769200 , t219420679_serv37i:2:50 , 5

2017-06-18 00:00:00 , 1497769200 , v290380588_serv81i:12:800 , 144:Jo

2017-06-18 00:00:00 , 1497769200 , z292902510_serv83i:4:45 , 5:Re

2017-06-18 00:00:00 , 1497769200 , v205454093_serv75i:5:70 , 50:AK

它保存为CSV文件,使用sc.textFile("input path")

读取

经过几次转换后,这就是我所拥有的RDD的输出:

(String, String) = ("Re ",7)

我通过执行

得到这个
val tid = read_file.map { line =>
  val arr = line.split(",")
  (arr(3).split(":")(1), arr(2).split(":")(1))
}

我的输入RDD是:

( z287570731_serv80i:7:175 , 5:Re )

( p286274731_serv80i:6:100 , 138 )

( t219420679_serv37i:2:50 , 5 )

( v290380588_serv81i:12:800 , 144:Jo )

( z292902510_serv83i:4:45 , 5:Re )

可以观察到,在第一个第2栏中,我有

5:Re 

我正在获得输出

("Re ",7)

然而,当我到达第二行时,根据格式,第2列是138,应该是

138:null 

但在执行

时给出了ArrayIndexOutOfBoundsException
tid.collect()

如何更正此值,以便分别为第二行和第三行显示138和5?我试着这样做:

tid.filter(x => x._1 != null )

3 个答案:

答案 0 :(得分:3)

问题是你希望这个位置至少有两个部分,而你可能只有一个部分。

以下是导致问题的一行。

{var arr = line.split(","); (arr(3).split(":")(1),arr(2).split(":")(1))});

完成line.split(",")之后的arr(3).split(":")(1)以及arr(2).split(":")(1)

对于这种格式肯定有太多的假设,并被缺失的值所打败。

  

但在执行

时给出了ArrayIndexOutOfBoundsException

这是因为您访问了32元素,但只有2个(!)

scala> sc.textFile("input.csv").
  map { line => line.split(",").toSeq }.
  foreach(println)
WrappedArray(( z287570731_serv80i:7:175i ,  5:Re ))
WrappedArray(( p286274731_serv80i:6:100 ,  138 ))

问题几乎与Spark无关。它是一个常规的Scala问题,数据不在您预期的位置。

scala> val arr = "hello,world".split(",")
arr: Array[String] = Array(hello, world)

请注意,上面的内容只是纯粹的Scala。

解决方案1 ​​ - Spark Core的RDD

给出以下数据集......

2017-06-18 00:00:00 , 1497769200 , z287570731_serv80i:7:175 , 5:Re
2017-06-18 00:00:00 , 1497769200 , p286274731_serv80i:6:100 , 138 
2017-06-18 00:00:00 , 1497769200 , t219420679_serv37i:2:50 , 5
2017-06-18 00:00:00 , 1497769200 , v290380588_serv81i:12:800 , 144:Jo
2017-06-18 00:00:00 , 1497769200 , z292902510_serv83i:4:45 , 5:Re
2017-06-18 00:00:00 , 1497769200 , v205454093_serv75i:5:70 , 50:AK

......我执行以下操作:

val solution = sc.textFile("input.csv").
  map { line => line.split(",") }.
  map { case Array(_, _, third, fourth) => (third, fourth) }.
  map { case (third, fourth) =>
    val Array(_, a @ _*) = fourth.split(":")
    val Array(_, right, _) = third.split(":")
    (a.headOption.orNull, right)
  }
scala> solution.foreach(println)
(Re,7)
(null,6)
(Re,4)
(null,2)
(AK,5)
(Jo,12)

解决方案2 - Spark SQL的DataFrames

强烈建议使用Spark SQL进行此类数据转换。正如你所说,你是Spark的新手,所以为什么不从正确的Spark SQL SQL开始。

val solution = spark.
  read.
  csv("input.csv").
  select($"_c2" as "third", $"_c3" as "fourth").
  withColumn("a", split($"fourth", ":")).
  withColumn("left", $"a"(1)).
  withColumn("right", split($"third", ":")(1)).
  select("left", "right")
scala> solution.show(false)
+----+-----+
|left|right|
+----+-----+
|Re  |7    |
|null|6    |
|null|2    |
|Jo  |12   |
|Re  |4    |
|AK  |5    |
+----+-----+

答案 1 :(得分:2)

如果您的数据如下文件

( z287570731_serv80i:7:175 , 5:Re )
( p286274731_serv80i:6:100 , 138 )
( t219420679_serv37i:2:50 , 5 )
( v290380588_serv81i:12:800 , 144:Jo )
( z292902510_serv83i:4:45 , 5:Re )

然后你可以使用

val tid = sc.textFile("path to the input file")
  .map(line => line.split(","))
  .map(array => {
    if (array(1).contains(":")) (array(1).split(":")(1).replace(")", "").trim, array(0).split(":")(1))
    else (null, array(0).split(":")(1))
  })
tid.foreach(println)

应该输出为

(Re,7)
(null,6)
(null,2)
(Jo,12)
(Re,4)

但如果您有数据

2017-06-18 00:00:00 , 1497769200 , z287570731_serv80i:7:175 , 5:Re
2017-06-18 00:00:00 , 1497769200 , p286274731_serv80i:6:100 , 138
2017-06-18 00:00:00 , 1497769200 , t219420679_serv37i:2:50 , 5
2017-06-18 00:00:00 , 1497769200 , v290380588_serv81i:12:800 , 144:Jo
2017-06-18 00:00:00 , 1497769200 , z292902510_serv83i:4:45 , 5:Re
2017-06-18 00:00:00 , 1497769200 , v205454093_serv75i:5:70 , 50:AK
2017-06-18 00:00:00 , 1497769200 , z287096299_serv80i:19:15000 , 39:Re

然后你需要做

val tid = sc.textFile("path to the input file")
  .map(line => line.split(","))
  .map(array => {
    if (array(3).contains(":")) (array(3).split(":")(1).replace(")", "").trim, array(2).split(":")(1))
    else (null, array(2).split(":")(1))
  })
tid.foreach(println)

你应该输出

(Re,7)
(null,6)
(null,2)
(Jo,12)
(Re,4)
(AK,5)
(Re,19)

答案 2 :(得分:1)

ArrayIndexOutOfBounds正在发生,因为如果元组的第二个元素中不存在:,则元素将不存在。 您可以检查每个元组的第二个元素中是否存在:。然后使用map为您提供一个中间RDD,您可以在其上运行当前查询。

val rdd = sc.parallelize(Array(
    ( "z287570731_serv80i:7:175" , "5:Re" ),
    ( "p286274731_serv80i:6:100" , "138" ),
    ( "t219420679_serv37i:2:50" , "5" ),
    ( "v290380588_serv81i:12:800" , "144:Jo" ),
    ( "z292902510_serv83i:4:45" , "5:Re" )))


rdd.map { x =>
    val idx = x._2.lastIndexOf(":")
    if(idx == -1) (x._1, x._2+":null")
    else (x._1, x._2)
}

显然有更好的(更少的代码行)方法来完成你想要完成的任务但是作为初学者,最好在单个命令中布置每个步骤,以便易于阅读和理解,特别是scala用一行代码就可以阻止全球变暖。