【问题标题】:How to parse delimited fields with some (sub)fields empty?如何解析一些(子)字段为空的分隔字段?
【发布时间】:2017-11-30 03:09:24
【问题描述】:

我在 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 显示 null?我试着这样做:

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

【问题讨论】:

    标签: scala apache-spark rdd


    【解决方案1】:

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

    以下是导致问题的行。

    {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 的数据帧

    强烈建议使用 Spark SQL 进行此类数据转换。正如您所说,您是 Spark 的新手,所以为什么不从正确的地方开始,这正是 Spark 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    |
    +----+-----+
    

    【讨论】:

    • 感谢您的建议,非常感谢。但我会在这里解释整个问题。我必须映射两列中的值说 (Re, 5) ; (空,6); (空, 5) ; (Jo, 12) 和 (Re, 4)。像 138 和 5 这样没有像第一行 (5:Re) 这样的数据的地方必须标记为空,然后必须计算 Re、Jo、Null 等的出现次数。我不明白 'if' 在这里有什么帮助。
    • 所有代码都必须在一行中执行,对吗?此外,我认为我学习 Spark 和 Scala 的方法到目前为止是错误的。你能建议我如何开始吗?
    • 可以但不是必须的。我对其进行了格式化,因此您可以将其复制并粘贴到 spark-shell 并自己查看它是如何工作的。
    • 我做到了。我收到一条错误消息,提示“值拆分不是 Array[String] 的成员”
    • @JacekLaskowski val Array(_, a @ _*) = rt.split(":") 的意思是“将数组rt.split(":") 的所有元素移动到a 除了第一个元素”
    【解决方案2】:

    ArrayIndexOutOfBounds 正在发生,因为如果元组的第二个元素中不存在 :,则该元素将不存在。 您可以检查: 是否存在于每个元组的第二个元素中。然后使用 map 为您提供一个中间 RDD,您可以在该 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 时只需一行代码即可阻止全球变暖。

    【讨论】:

    • 为什么我在执行 rdd.map 时得到“值 _2 和 _1 不是字符串的成员”?
    • 因为在这个例子中,我使用了Tuple2 来存储字段。因为,你在一行上使用split,你可能会得到一个Array,所以你可以尝试使用x(0)x(1)。这取决于您的 RDD 的外观。 x._1 用于元组。 x(0) 用于数组。
    【解决方案3】:

    如果您的数据在文件中如下所示

    ( 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)
    

    【讨论】:

    • (null,00) (null,00) (null,00) (null,00) 是我得到的输出
    • 你的输入和定义的一样吗?
    • 分享您的输入文件样本数据。也许我可以帮忙。 :)
    • 是不是说你的输入文件是单行的?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-26
    • 2016-06-30
    • 2014-11-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多