如何在Java中实现Scala的以下代码段

时间:2019-04-03 15:03:44

标签: java scala apache-spark

我正在实现代码,以向行中具有空值的数据框动态添加多列

我在使用Dataframe对象的map函数的scala中找到了以下代码段。

import org.apache.spark.sql.catalyst.encoders.RowEncoder
import org.apache.spark.sql.types.{DataTypes, NullType, StructType}
import org.apache.spark.sql.{DataFrame, Encoders, Row, SparkSession}
import org.apache.spark.sql.functions.lit;

def addColumnsViaMap(df: DataFrame, words: List[String]): DataFrame = {
   val encoder = RowEncoder.apply(getSchema(df, words))
   df.map(mappingRows(df.schema)(words))(encoder)
}

private val mappingRows: StructType => List[String] => Row => Row =
  (schema) => (words) => (row) => {
     val addedCols: List[Any] = words.map(_=> null)
    Row.merge(row, Row.fromSeq(addedCols))
  }

private def getSchema(df: DataFrame, words: List[String]): StructType = {
  var schema: StructType = df.schema
  words.foreach(word => schema = schema.add(word, "string", false))
  schema
}

我已经在Java中实现了以下两个功能

 private StructType getSchema(Dataset<Row> df, List<String> cols){
        StructType schema = df.schema();
        cols.forEach(col -> schema.add(col, "int", true));
        return schema;
    }

private addColumnsViaMap(Dataset<Row> df, List<String> cols){
    Encoder<Row> encoder1 = 
  RowEncoder.apply(dataConsolidationEngine.getSchema(df,cols));
   df.map(new MapFunction<Set<String>, Row>() {
                private static final long serialVersionUID = 1L;

                @Override
                public Row call(Set<String> cols) throws Exception {
                    // TODO Auto-generated method stub
                }
            }, encoder1);
}

由于参数不匹配,addColumnsViaMap方法具有编译错误,无法解析匿名地图函数方法。

我不理解mappingRows的Scala代码,尤其是以下StructType => List[String] => Row => Row = (schema) => (words) => (row)这意味着什么?

以及如何在Java中实现上述scala代码?

2 个答案:

答案 0 :(得分:1)

嗯,这个声明有点复杂(IMO也有点难以理解),所以让我们退后一步。

在scala中,StringList ...是每个人都知道的类型。您可以创建String类型的变量。

您还可以做的是为变量分配一个函数(这是scala的函数方向),因此函数也具有类型。举例来说,如果您有一个使用List并输出String的函数,则该函数的类型为List => String

那在代码中看起来像吗?

// A list of strings
val names = List("alice", "bob")
// A function that takes a list and returns a string
def listToString(list: List[String]): String = list.mkString(",")
// We can assign the function to a variable
val myListToString: List[String] => String = listToString

但是我们在声明函数时有一个较短的表示法,我们可以将它们声明为“内联”,而无需使用def语句。这样就可以等效地编写以上代码:

val names = List("alice", "bob")
val myListToString: List[String] => String = (list) => list.mkString(",")

所以,一般来说:

  • A => B类型,该函数采用A并返回B
  • (arg: A) => { new B() }是一个实际函数,它以A的一个实例作为输入(该实例绑定到变量名arg,并且其主体返回B的一个实例

现在让我们做些疯狂的事情,让我们...重新开始。假设F是一个接受List并返回String的函数。需要一个Int并返回一个F的函数是什么样的?

应该是:

  • Int => F
  • 也就是说:Int => (List => String)
  • 可以写成Int => List => String

您如何声明它?

// Borrowing from above
val names = List("alice", "bob")
val myListToString: List[String] => String = (list) => list.mkString(",")
// now we're doing it
val intToListToString = (integerValue) => myListToString
// now we're doing it in one go
val intToListToString2 = (integerValue) => (list) => list.mkString(",")

在这里,intToListToString是接受int并返回“接受List并返回String的函数”。

您可以一次又一次地筑巢。

直到得到:StructType => List[String] => Row => Row,这是一种类型,表示“将StructType作为输入并返回的函数(将List[String]作为输入并返回的函数(a函数,它以Row作为输入并返回一行)。

您可以将其实现为:

(schema) => // a function that takes schema, and returns
    (words) => // a function that takes a list of words and returns
        (row) => // a function that takes a row and returns
            Row.fromSeq(...) // another row

现在Java会是什么样子?

如果要严格按原样进行转换,则可以这样考虑:scala的A => B的自然等效项是java.util.Function<A, B>。最重要的是,如果要使用函数对map进行Spark Dataframe操作,则必须使用MapFunction<>

所以我们正在寻求实现Function<Schema, Function<List<String>, MapFunction<Row, Row>>>或类似的东西。

使用Java lambda表示法,您可以通过以下方式实现:

schema ->  words -> row -> Row.merge(row, Row.fromSeq(Array.newInstance(String.class, words.size)))

哪个函数需要一个模式

  • 返回一个包含单词列表的函数

    • 返回带有Row的函数

      • 返回一行,该行增加了包含null的列

也许我的Java语法是正确的,也许不是我不知道。

我所知道的是,这是实现您的要求的一种极其复杂的方法。

这是什么要求:您有一个数据框,有一个单词列表,您想要创建具有此名称并包含null的新列。

所以我在scala中要做的是:

import org.apache.spark.sql.DataFrame
def addColumnsViaMap(dataframe: DataFrame, words: List[String]) = words.foldLeft(dataframe)((df, word) => df.withColumn(word, lit(null: String)))

val dataframe = Seq(("a", "b"), ("c", "d")).toDF("columnA", "columnB")
val words = List("columnC", "columnD")
addColumnsViaMap(dataframe, words).show

+-------+-------+-------+-------+
|columnA|columnB|columnC|columnD|
+-------+-------+-------+-------+
|      a|      b|   null|   null|
|      c|      d|   null|   null|
+-------+-------+-------+-------+

您可能可以这样用Java编写

DataFrame addColumnsViaMap(DataFrame dataframe, List<String> words) {
    for (String word: words) {
        dataframe = dataframe.withColumn(word, lit((String) null))
    }
    return dataframe;
}

再一次,我没有基于Java的spark环境,但我的观点是:如果您掌握了原理,重新编写就很简单。

答案 1 :(得分:0)

private val mappingRows: StructType => List[String] => Row => Row =
  (schema) => (words) => (row) => {
     val addedCols: List[Any] = words.map(_=> null)
    Row.merge(row, Row.fromSeq(addedCols))
  }

简单地说,可以理解为:

mappingRows是一个“函数”,它带有3个参数(类型为StructTypeListRow,例如模式,单词和行),并返回{ {1}}。但是与其这样称呼:

Row

你会去

mappingRows(schema, words, row)`

这意味着只需拨打电话

mappingRows(schema)(words)(row)

将返回一个使用mappingRows(schema)(words) 并返回Row的函数:一个可以传递给典型Row函数的映射函数。

基本上,给定一个模式和一个col名称列表,闭包将一行作为输入。只需为每个给定的col名称在该行的null列上添加。

它可以帮助您回答问题吗?