Scala: Как передать поле класса в метод - PullRequest
1 голос
/ 23 января 2020

Я новичок в Scala и пытаюсь провести некоторый анализ данных.

У меня есть файлы CSV с несколькими заголовками - скажем, номер товара, тип товара, месяц, проданные товары.

Я создал класс Item с полями заголовков. Я разделил CSV на список, где каждая итерация списка представляет собой строку файла CSV, представляемого классом Item.

Я пытаюсь создать метод, который будет создавать карты на основе параметра I отправить. Например, если я хочу сгруппировать проданные товары по месяцам или по типу. Однако я изо всех сил пытаюсь отправить Item.field в метод.

Например, я пытаюсь что-то наподобие:

makemaps(Item.month);
makemaps(Item.itemtype);

def makemaps(Item.field):
   if (item.field==Item.month){}
   else (if item.field==Item.itemType){}

Однако мои логики c для этого кажутся неправильными , Есть идеи?

1 Ответ

2 голосов
/ 23 января 2020
def makeMap[T](items: Iterable[Item])(extractKey: Item => T): Map[T, Iterable[Item]] =
  items.groupBy(extractKey)

Итак, учитывая этот пример Item class:

case class Item(month: String, itemType: String, quantity: Int, description: String)

Вы можете иметь (я считаю, что типовые описания обязательны):

val byMonth = makeMap[String](items)(_.month)
val byType = makeMap[String](items)(_.itemType)
val byQuantity = makeMap[Int](items)(_.quantity)
val byDescription = makeMap[String](items)(_.description)

Обратите внимание, что _.month, например, создает функцию, принимающую Item, что приводит к String, содержащемуся в поле month (немного упрощает).

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

object Item {
  val month: Item => String = _.month
  val itemType: Item => String = _.itemType
  val quantity: Item => Int = _.quantity
  val description: Item => String = _.description

  // Allows us to determine if using a predefined extractor or using an ad hoc one
  val extractors: Set[Item => Any] = Set(month, itemType, quantity, description)
}

Затем вы можете передавать их так:

val byMonth = makeMap[String](items)(Item.month)

Единственное реальное семантическое изменение заключается в том, что вы явно избегаете возможного дополнительного конструирования лямбд. во время выполнения за счет того, что лямбды остаются в памяти все время. Дополнительным преимуществом является то, что вы можете кэшировать карты с помощью экстрактора, если вы уверены, что источник Item никогда не меняется: для лямбд - равенство - это ссылочное равенство. Это может быть особенно полезно, если у вас есть некоторый класс, представляющий коллекцию Item s, а не просто стандартный набор, например:

object Items {
  def makeMap[T](items: Iterable[Item])(extractKey: Item => T): Map[T, 
Iterable[Item]] =
    items.groupBy(extractKey)
}

class Items(val underlying: immutable.Seq[Item]) {
  def makeMap[T](extractKey: Item => T): Map[T, Iterable[Item]] =
    if (Item.extractors.contains(extractKey)) {
      if (extractKey == Item.month) groupedByMonth.asInstanceOf[Map[T, Iterable[Item]]]
      else if (extractKey == Item.itemType) groupedByItemType.asInstanceOf[Map[T, Iterable[Item]]]
      else if (extractKey == Item.quantity) groupedByQuantity.asInstanceOf[Map[T, Iterable[Item]]]
      else if (extractKey == Item.description) groupedByDescription.asInstanceOf[Map[T, Iterable[Item]]]
      else throw new AssertionError("Shouldn't happen!")
    } else {
      Items.makeMap(underlying)(extractKey)
    }

  lazy val groupedByMonth = Items.makeMap[String](underlying)(Item.month)
  lazy val groupedByItemType = Items.makeMap[String](underlying)(Item.itemType)
  lazy val groupedByQuantity = Items.makeMap[Int](underlying)(Item.quantity)
  lazy val groupedByDescription = Items.makeMap[String](underlying)(Item.description)
}

(это почти наверняка личный рекорд для asInstanceOf s в небольшом блоке кода ... Я не уверен, должен ли я гордиться или стыдиться этого фрагмента)

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