Java 删除Spark array列中的重复项

Java 删除Spark array列中的重复项,java,scala,apache-spark,apache-spark-sql,apache-spark-dataset,Java,Scala,Apache Spark,Apache Spark Sql,Apache Spark Dataset,我有一个给定的数据集: +-------------------+--------------------+ | date| products| +-------------------+--------------------+ |2017-08-31 22:00:00|[361, 361, 361, 3...| |2017-09-22 22:00:00|[361, 362, 362, 3...| |2017-09-21 22:00:00|[3

我有一个给定的数据集:

+-------------------+--------------------+
|               date|            products|
+-------------------+--------------------+
|2017-08-31 22:00:00|[361, 361, 361, 3...|
|2017-09-22 22:00:00|[361, 362, 362, 3...|
|2017-09-21 22:00:00|[361, 361, 361, 3...|
|2017-09-28 22:00:00|[360, 361, 361, 3...|
其中products列是一个字符串数组,其中可能包含重复项

我想删除此重复项(在一行内)

我所做的基本上就是编写一个这样的UDF函数

 val removeDuplicates: WrappedArray[String] => WrappedArray[String] = _.distinct
 val udfremoveDuplicates = udf(removeDuplicates)
此解决方案为我提供了正确的结果:

+-------------------+--------------------+--------------------+
|               date|            products|       rm_duplicates|
+-------------------+--------------------+--------------------+
|2017-08-31 22:00:00|[361, 361, 361, 3...|[361, 362, 363, 3...|
|2017-09-22 22:00:00|[361, 362, 362, 3...|[361, 362, 363, 3...|
我的问题是:

  • Spark是否提供了获得此结果的更好/更有效的方法

  • 我正在考虑使用一个映射,但是如何获得所需的列作为一个列表,以便能够像在我的removeDuplicateslambda中那样使用“distinct”方法

  • 编辑:我用java标记了这个主题,因为对于我来说,哪种语言(scala或java)可以获得answear:)
    Edit2:typos

    问题中提出的方法——使用UDF——是最好的方法,因为
    spark sql
    没有用于统一数组的内置原语

    如果您正在处理大量数据和/或数组值具有独特的属性,那么值得考虑UDF的实现

    WrappedArray.distinct
    在幕后构建一个
    mutable.HashSet
    ,然后遍历它以构建不同元素的数组。从性能角度来看,这可能存在两个问题:

  • Scala的可变集合并不是非常有效,这就是为什么在Spark的核心中,您会发现许多Java集合和
    while
    循环。如果需要极高的性能,可以使用更快的数据结构实现自己的通用distinct

  • distinct
    的通用实现不会利用数据的任何属性。例如,如果数组平均较小,那么直接构建到数组中并对重复项进行线性搜索的简单实现可能比构建复杂数据结构的代码执行得更好,尽管它在理论上是
    O(n^2)
    复杂的。例如,如果值只能是小范围内的数字或小集合中的字符串,则可以通过位集合实现uniquification


  • 同样,只有当你有大量的数据时,才应该考虑这些策略。您的简单实现几乎适用于所有情况。

    考虑到您当前的
    数据帧
    模式

    root
     |-- date: string (nullable = true)
     |-- products: array (nullable = true)
     |    |-- element: integer (containsNull = false)
    
    可以使用以下方法删除重复项

    df.map(row => DuplicateRemoved(row(0).toString, row(1).asInstanceOf[mutable.WrappedArray[Int]], row(1).asInstanceOf[mutable.WrappedArray[Int]].distinct)).toDF()
    
    当然,您需要一个
    案例类

    case class DuplicateRemoved(date: String, products: mutable.WrappedArray[Int], rm_duplicates: mutable.WrappedArray[Int])
    
    您应该得到以下输出

    +-------------------+------------------------------+-------------------------+
    |date               |products                      |rm_duplicates            |
    +-------------------+------------------------------+-------------------------+
    |2017-08-31 22:00:00|[361, 361, 361, 362, 363, 364]|[361, 362, 363, 364]     |
    |2017-09-22 22:00:00|[361, 362, 362, 362, 363, 364]|[361, 362, 363, 364]     |
    |2017-09-21 22:00:00|[361, 361, 361, 362, 363, 364]|[361, 362, 363, 364]     |
    |2017-09-28 22:00:00|[360, 361, 361, 362, 363, 364]|[360, 361, 362, 363, 364]|
    +-------------------+------------------------------+-------------------------+
    

    我希望答案是有帮助的

    答案现在已经过时了,因此这个新的答案

    使用Spark 2.4阵列函数,您可以实现如下功能,还可以显示其他一些方面:但您可以了解其要点:

    val res4 = res3.withColumn("_f", array_distinct(sort_array(flatten($"_e"))))
    

    顺便说一句,这里有一篇好文章:

    Spark没有为这种类型的操作提供内置函数,因此UDF是一种类似@user6910411的方式。如果您想要一个列表,只需在distinct后添加
    。toList
    ,并更新udf类型注释以返回列表。与数组相比,映射是一种昂贵得多的数据结构,除非确实需要,否则应避免使用,例如,经常检查元素的存在性以及当元素集合的平均大小相当大时(或者当您需要将映射合并在一起时,等等)。即使如此,如果您需要检查是否存在元素,通常最快的方法是将元素表示为分隔字符串,例如,
    ”:123:345:126:
    ,并对
    执行子字符串搜索。复杂的数据结构,甚至数组,需要比字符串多得多的处理。UDF方法更简单、更快、更好。这个答案涉及不安全的
    访问和不必要的案例类创建,然后将其返回到
    中。它处理整个案例行数据,如果这是更大的转换DAG的一部分,则会阻止Spark执行列优化或计划重写(另外,作为旁白,该问题说需要进行uniquification的数组有字符串;可能需要修复该字符串)是的@Sim,你完全正确。OP希望看到使用udf函数完成与我的其他答案中相同任务的其他可能性。这就是我发布此答案的原因。确切的问题是“Spark是否提供了更好/更有效的方法来获得此结果?”解决软件问题的方法有无数种,它们比给定的解决方案更糟糕、效率更低。:)我同意@Sim Ramesh,这不是一个好的解决方案,OP要求一个更有效的解决方案。对不起,我将否决你的答案。