Получить имя переменной из класса дела - PullRequest
0 голосов
/ 15 апреля 2020

:)

Я хотел бы сослаться на имя переменной (только имя, без значения) в классе дела.

Далее приведен очень упрощенный пример:

case class Person(name: String, age: Int)
val schema = Encoders.products[Person].schema
val jack = Person("name", 20)

override def method[Person](df: DataFrame) : DataFrame = {        
  df.withColumn("json", from_json(col("column_value"), schema))
    .select("json.*")
    .withColumn(jack.name, trim(col(jack.name)))
    .withColumn(jack.age, col(jack.age) + 2)
}

Конечно, jack.name вернет значение, которое является строкой и хорошо работает для моей компании. Но, как вы уже можете себе представить, jack.age даст мне значение, а не «возраст».

До сих пор я получаю это, что я считаю действительно уродливым и неэффективным решением :

val onlyNames: Seq[String] = schema.map(_.name) 
...
.withColumn(...)
.withColumn(onlyNames(2), col(onlyNames(2)) + 2)

Версии: Spark 2.3.0 // Scala 2.11.8

Ответы [ 2 ]

2 голосов
/ 15 апреля 2020

В Scala 2.13 вы можете использовать:

val person = Person("John", 23)
(person.productEleementNames zip person.productIterator).foldLeft(dataFrame) {
  case (dataFrame, (name, value)) =>
    dataFrame.withColumn(name, value) // example
}

, но, поскольку вы находитесь на 2.11 или, в лучшем случае, на 2.12 из-за Spark, вы должны использовать другой способ.

Одним из способов будет использование отражения во время выполнения:

(person.getClass.getDeclaredFields.map(_.getName) zip person.productIterator).foldLeft(dataFrame) {
  case (dataFrame, (name, value)) =>
    dataFrame.withColumn(name, value) // example
}

это будет иметь штраф за время выполнения, но не потребует каких-либо зависимостей.

Другим вариантом будет использование бесформенного или магнолийного для вычисления результата во время компиляции (при условии, что вы знаете тип того, из чего вы хотите извлечь имена полей).

Бесформенное решение уже предоставлено в другом вопросе .

Решение Magnolia было бы что-то вроде (отказ от ответственности: не проверяется, если он компилируется):

import magnolia._

trait FieldNames[T] {
  def apply(): List[String]
}

object FieldNames {
  def getNames[T](implicit fieldNames: FieldNames[T]): FieldNames[T] = fieldNames

  type Typeclass[T] = FieldNames[T]

  def combine[T](ctx: ReadOnlyCaseClass[Typeclass, T]): FieldNames[T] = () =>
    ctx.parameters.map(_.label).toList
  }

  implicit def gen[T]: FieldNames[T] = macro Magnolia.gen[T]
}

(FieldNames.getNames[Person] zip person.productIterator).foldLeft(dataFrame) {
  case (dataFrame, (name, value)) =>
    dataFrame.withColumn(name, value) // example
}

Отражение времени компиляции требует немного больше усилий и предполагает, что вы знаете тип значения, с которым вы работаете во время компиляции, но должны Быстрее во время выполнения и меньше подверженных ошибкам.

Короче говоря, какой из них лучше, зависит от вашего варианта использования.

1 голос
/ 15 апреля 2020

Я согласен с предыдущими ответами, есть и другие альтернативы для извлечения полей.

Поскольку вы используете Spark, вы всегда можете смоделировать имена столбцов следующим образом:

case class Person(name: String, age: Int)
val jack = Person("name", 20)

val columnNames = Seq(jack).toDF().columns

println(columnNames.toList)

И для pure Scala 2.11

val fieldNames = classOf[Person].getDeclaredFields.map { f =>
  f.setAccessible(true)
  val res = f.getName
  f.setAccessible(false)
  res
}

println(fieldNames.toList)

Scast ie пример -> здесь

И бесформенный пример:

import shapeless._
import shapeless.ops.record._

case class Person(name: String, age: Int)
val labelledPerson = LabelledGeneric[Person]
val columnNames = Keys[labelledPerson.Repr].apply.toList.map(_.name)
println(columnNames)

Бесформенный и Scala версии:

scalaVersion := "2.11.8"
libraryDependencies ++= Seq(
  "com.github.alexarchambault" %% "argonaut-shapeless" % "6.1"
)

Scast ie пример -> здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...