групповой скала список строк - PullRequest
0 голосов
/ 25 мая 2018

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

В настоящее время мой ввод можно описать так:

val listInput1 = 
  List(
    "itemA,CATA,2,4 ",
    "itemA,CATA,3,1 ",
    "itemB,CATB,4,5",
    "itemB,CATB,4,6"
   )

val listInput2 = 
  List(
    "itemA,CATA,2,4 ",
    "itemB,CATB,4,5",
    "itemC,CATC,1,2"
  )

Требуемый вывод для списков на входе должен быть

val listoutput1 = 
  List(
    "itemA,CATA,5,5 ",
    "itemB,CATB,8,11"
  )

val listoutput2 =
  List(
    "itemA , CATA, 2,4 ",
    "itemB,CATB,4,5",
    "itemC,CATC,1,2"
  )

Я написал следующую функцию:

def sumByTitle(listInput: List[String]): List[String] =      
  listInput.map(_.split(",")).groupBy(_(0)).map { 
    case (title, features) => 
       "%s,%s,%d,%d".format(
         title,
         features.head.apply(1),
         features.map(_(2).toInt).sum,
         features.map(_(3).toInt).sum)}.toList

Это не дает мне ожидаемый результат, поскольку он меняет порядок строк.

Как я могу это исправить?

Ответы [ 3 ]

0 голосов
/ 25 мая 2018
def foldByTitle(listInput: List[String]): List[Item] =
  listInput.map(Item.parseItem).foldLeft(List.empty[Item])(sumByTitle)

val sumByTitle: (List[Item], Item) => List[Item] = (acc, curr) =>
  acc.find(_.name == curr.name).fold(curr :: acc) { i =>
    acc.filterNot(_.name == curr.name) :+ i.copy(num1 = i.num1 + curr.num1, num2 = i.num2 + curr.num2)
  }

case class Item(name: String, category: String, num1: Int, num2: Int)
object Item {
  def parseItem(serializedItem: String): Item = {
    val itemTokens = serializedItem.split(",").map(_.trim)
    Item(itemTokens.head, itemTokens(1), itemTokens(2).toInt, itemTokens(3).toInt)
  }
}

Таким образом сохраняется начальный порядок элементов.

0 голосов
/ 25 мая 2018

ListMap предназначен для сохранения порядка элементов, вставленных в Map.

import collection.immutable.ListMap

def sumByTitle(listInput: List[String]): List[String] = {
  val itemPttrn = raw"(.*)(\d+),(\d+)\s*".r
  listInput.foldLeft(ListMap.empty[String, (Int,Int)].withDefaultValue((0,0))) {
    case (lm, str) =>
      val itemPttrn(k, a, b) = str  //unsafe
      val (x, y) = lm(k)
      lm.updated(k, (a.toInt + x, b.toInt + y))
  }.toList.map { case (k, (a, b)) => s"$k$a,$b" }
}

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

sumByTitle(listInput1)
//res0: List[String] = List(itemA,CATA,5,5, itemB,CATB,8,11)

sumByTitle(listInput2)
//res1: List[String] = List(itemA,CATA,2,4, itemB,CATB,4,5, itemC,CATC,1,2)

Вы заметите, что завершающий пробел, если он есть, не сохраняется.

0 голосов
/ 25 мая 2018

Если вы просто заинтересованы в сортировке, вы можете просто вернуть список sorted:

val listInput1 = 
  List(
    "itemA , CATA, 2,4 ",
    "itemA , CATA, 3,1 ",
    "itemB,CATB,4,5",
    "itemB,CATB,4,6"
   )

val listInput2 = 
  List(
    "itemA , CATA, 2,4 ",
    "itemB,CATB,4,5",
    "itemC,CATC,1,2"
  )

def sumByTitle(listInput: List[String]): List[String] =      
  listInput.map(_.split(",")).groupBy(_(0)).map { 
    case (title, features) => 
       "%s,%s,%d,%d".format(
         title,
         features.head.apply(1),
         features.map(_(2).trim.toInt).sum,
         features.map(_(3).trim.toInt).sum)}.toList.sorted

println("LIST 1")
sumByTitle(listInput1).foreach(println)

println("LIST 2")
sumByTitle(listInput2).foreach(println)

Вы можете найти код в Scastie , с которым можно поиграться.


В качестве примечания вас может заинтересовать отделение сериализации и десериализации от вашей бизнес-логики.

Здесь вы можете найти еще один ноутбук Scastie с относительноНаивный подход для первого шага к разделению проблем.

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