【问题标题】:Re-infer sequence type in Scala在 Scala 中重新推断序列类型
【发布时间】:2020-08-22 10:47:34
【问题描述】:

假设我们有一个Any的序列

val seq = Seq(1,2,null)
seq: Seq[Any] = List(1, 2, null)

现在如果过滤非空元素获得一个新序列

val cleanSeq = seq.filterNot(_ == null)
cleanSeq: Seq[Any] = List(1, 2)

现在我想获得与创建类似cleanSeq 这样的新序列时获得的相同类型

val seq2 = Seq(1,2)
seq2: Seq[Int] = List(1, 2)

我可以从cleanSeq开始以某种方式获得Seq[Int]吗?

更新

前面只是一个虚拟示例,我可以有除Int 之外的其他类型,也可以有复杂类型示例:Array[Map[String, Float]]

我能做的唯一假设是我有一个可能包含空值的序列。但是序列中的其他元素除了Any 之外都有一个共同的超类型。去掉空值后,我想找到常见的超类型。

更新

用例

我想从具有名称和值的列创建火花数据框。这些值存储在Seq 中。从Seq 的类型我想导出数据框的架构。

列的定义

  import reflect.runtime.universe._
  import org.apache.spark.sql.catalyst.ScalaReflection
  import org.apache.spark.sql.types._

  case class InternalColumn[A: TypeTag](colName: String, col: Seq[A]) {

    private def getType: DataType = ScalaReflection.schemaFor[A].dataType

    def genStructField: StructField = StructField(colName, getType)
  }

创建数据框:

 def createDF[T](data: Seq[T], sch: StructType): DataFrame = {
    val dataRow: Seq[Row] = data.map {
      case row: Row => row
      case prod: Product => Row(prod.productIterator.toSeq: _*)
      case d => Row(d)
    }
    spark.createDataFrame(sc.makeRDD(dataRow), sch)
  }

用法

  def createFromColumns(data: Seq[InternalColumn[_]]): DataFrame = {
    def loop(schema: StructType, cols: Seq[InternalColumn[_]]): StructType = cols.toList match {
      case Nil => schema
      case h :: t => loop(schema.add(h.genStructField), t)
    }

    val sch: StructType = loop(new StructType(), data)
    createDF(data.map(_.col).transpose.map(Row.fromSeq), sch)
  }

val df = createFromColumns(List(InternalColumn("c1", List(1,2,3)), InternalColumn("c2", List("a", "b", "c"))))

scala> df.show()
+---+---+
| c1| c2|
+---+---+
|  1|  a|
|  2|  b|
|  3|  c|
+---+---+


scala> df.printSchema
root
 |-- c1: integer (nullable = true)
 |-- c2: string (nullable = true)


到目前为止效果很好。但是有人可能想要创建一个数据框,其中包含一个包含空值的列。如果具有 null 的列可以为空,例如它具有 StringType,那么它仍然有效。

当您在不可为空的列中有空值时会出现问题,例如:

scala> val df = createFromColumns(List(InternalColumn("c1", List(1,2,null)), InternalColumn("c2", List("a", "b", "c"))))
java.lang.UnsupportedOperationException: Schema for type Any is not supported

这就是我想在删除所有空值时推断序列类型的原因。

【问题讨论】:

  • 如果您在编译时无法知道类型应该是什么,那么编译器也无法知道。 Seq 在运行时 中的元素的通用超类型只能在在运行时 知道。所以编译器无能为力。
  • 你可能在编译时对类型有很好的了解,考虑使用Either
  • 所以如果我做对了,简短的回答是不,这是不可能的。谢谢!
  • 视情况而定。你从哪里得到这个List(1,2,null)?如果某处有一个黑盒向您抛出一个或多个Seq[Any],则无法在编译时恢复该类型。

标签: scala types


【解决方案1】:

您首先应该尽量不要丢失正确的类型(Seq[Any] 是一种不好的代码味道,就像使用 null 一样),但要找回真实的类型,您可以进行运行时类型检查:

 seq.collect{ case x: Int => x } 

这将再次是Seq[Int],丢弃了所有不是Int的东西。

如果您实际上没有其他东西,而只是 Intnull,请考虑改用 Option[Int]

val seq: Seq[Option[Int]] = Seq(Some(1), Some(2), None)
// then you can do
seq.flatten // gives you Seq[Int] 

去掉空值后,我想找到常见的超类型。

您确实必须知道(在您编写程序时)常见的超类型应该是什么。然后您可以编写代码来检查该类型。

如果你不知道你想要一个Seq[Int],你会声明结果的类型是什么?请记住,这些泛型类型(Seq[Int] 中的 Int)在运行时会被删除,并且只存在于编译时进行静态类型检查。

【讨论】:

  • 对不起,我可能不够清楚。这只是一个虚拟的例子,我事先不知道我是否会面对一个 Int。它可能是字符串、布尔值等。我会相应地更新我的答案。
  • 是的,这种动态类型的编码在 Scala 中非常少见。我们通常首先尝试避免这种情况发生。也许您可以扩展从哪里获取这些数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-16
  • 1970-01-01
  • 2012-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多