Scala DataSet с наследованием класса случая

Я хотел бы иметь возможность хранить различные связанные типы в Spark DataFrame, но работать со строго типизированными классами случаев через DataSet. Например. скажем, у меня есть признак Base и два класса случаев A и B, которые расширяют этот признак:

trait Base {
  def name: String
}

case class A(name: String, number: Int) extends Base

case class B(name: String, text: String) extends Base

Я хотел бы создать val lb = List[Base](A("Alice", 20), B("Bob", "Foo")), а затем создать DataFrame через lb.toDS(). Неудивительно, что это не работает, поскольку для этого признака нет кодировщика для его различных расширенных классов.

Я мог бы вручную создать класс Case, представляющий структуру, которая может хранить информацию как для A, так и для B:

case class Struct(typ: String, name: String, number: Option[Int] = None, text: Option[String] = None)

И я мог бы добавить несколько функций для создания Struct из экземпляра черты Base и наоборот:

trait Base {
  def name: String

  def asStruct: Struct = {
    this match {
      case A(name, number) => Struct("A", name, number = Some(number))
      case B(name, text) => Struct("B", name, text = Some(text))
    }
  }
}

case class Struct(typ: String, name: String, number: Option[Int] = None, text: Option[String] = None) {
  def asBase: Base = {
    this match {
      case Struct("A", name, Some(number), None) => A(name, number)
      case Struct("B", name, None, Some(text)) => B(name, text)
      case _ => throw new Exception(f"Invalid Base structure {s}")
    }
  }
}

Затем я могу создать свой DataFrame следующим образом:

    val a = A("Alice", 32)
    val b = B("Bob", "foo")

    val ls = List[Struct](a.asStruct, b.asStruct)

    val sparkSession = spark
    import sparkSession.implicits._

    val df = ls.toDS()

    df.show()

+---+-----+------+----+
|typ| name|number|text|
+---+-----+------+----+
|  A|Alice|    32|NULL|
|  B|  Bob|  NULL| foo|
+---+-----+------+----+

Я могу работать с этим подходом, но мне интересно, можно ли написать кодировщик, который автоматически обрабатывает класс Base как класс Struct, используя метод asStruct, написанный выше?

Спасибо, я не знаю, почему я не смог найти эти статьи при поиске.

David Regan 16.04.2024 14:30
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

безрамочные инъекции :

// the injector
implicit val baseHolder: Injection[Base, Struct] = new Injection[Base, Struct] with Serializable {
  def invert(a: Struct): Base =
    a match {
      case Struct("A", name, Some(number), None) => A(name, number)
      case Struct("B", name, None, Some(text)) => B(name, text)
      case _ => throw new Exception(f"Invalid Base structure {s}")
    }

  def apply(b: Base): Struct =
    b match {
      case A(name, number) => Struct("A", name, number = Some(number))
      case B(name, text) => Struct("B", name, text = Some(text))
    }
}
import frameless._
implicit val enc = TypedExpressionEncoder[Base]

import sparkSession.implicits._
val lb: Dataset[Base] = Seq[Base](A("Alice", 20), B("Bob", "Foo")).toDS
lb.show

дает:

+---+-----+------+----+
|typ| name|number|text|
+---+-----+------+----+
|  A|Alice|    20|null|
|  B|  Bob|  null| Foo|
+---+-----+------+----+

но действительно ли они на пятёрки и четверки?

lb.collect().foreach{ b =>
  println(b.getClass.getName)
}

покажет А и Б.

Порядок параметров при внедрении важен: левый — это тот, для которого мы создаем внедрение, а второй параметр — это тип, который мы храним в фрейме данных.

Это очень здорово. Именно то, что я искал.

David Regan 16.04.2024 14:31

Крис, еще один вопрос. Я протестировал набор данных, сформированный из класса Case, содержащего базовые поля. Если я вызываю UDF, который принимает параметр Base, декодирование не работает... вместо этого UDF передается тип Struct. Является ли это ограничением или есть какое-то другое неявное свойство, которое я могу объявить, чтобы преобразовать структуру в A или B?

David Regan 18.04.2024 10:19

Это был мой класс Case class C(x: Base, y: Base) и мой тест: val a = A("Alice", 32) val b = B("Bob", "foo") val lb = Seq(C(a, b)) val ds: Dataset[C] = lb.toDS defprocessBase(b: Base): String = { b match { case A(name, Number) => f"$name имеет номер $ число" case B(name, text) => f"$name содержит текст $text" case _ => "Не знаю" } } valprocessBaseUdf = udf(processBase(_)) var df = ds.withColumn(" x",processBaseUdf(ds("x"))) df = df.withColumn("y",processBaseUdf(ds("y")))

David Regan 18.04.2024 10:22

вам, вероятно, следует задать это как другой вопрос и показать код. Я предполагаю, что вывод типа для C неверен, и вам следует использовать: неявный val cenc = TypedExpressionEncoder[C]

Chris 18.04.2024 10:47

также вам нужно будет указать, тестируете ли вы локально или в затененной банке (перемещение scala с оттенком maven не работает)

Chris 18.04.2024 12:01

Хорошо, я настрою ценс и проверю, работает ли это. Если нет, я задам еще один вопрос и покажу весь код. Спасибо за помощь.

David Regan 18.04.2024 13:15

Я задал вопрос о декодировании и UDF здесь: stackoverflow.com/questions/78349325/…

David Regan 18.04.2024 19:28

Другие вопросы по теме