Не знаете, как оптимально выполнить преобразование списка в Scala

Кто-нибудь знает хороший способ превратить следующий список входных данных в список нужных результатов ниже?

Функция, которую я пытаюсь создать

def transformList(input:List[(String,String)]):List[(String,String)] = ???

вход

val inputList = List(
    ("class","testClass1"),
    ("class","testClass2"),
    ("id","testId1"),
    ("class","testClassRepeat"),
    ("class","testClassRepeat"),
    ("id","testId2"),
    ("href","testHref1")
)

желаемый выход

List(
    ("class","testClass1 testClass2 testClassRepeat testClassRepeat"),
    ("id","testId1 testId2"),
    ("href","testHref1")
)

У меня есть решение, но я не думаю, что делаю это хорошим/эффективным способом. В настоящее время я использую следующее решение:

  • Создать пустую измененную карту
  • Прокрутите список ввода с помощью .foreach
  • Нажатие клавиши/значений на основе inputList в изменчивой карте. Затем добавление к значениям существующих ключей, если это применимо (например, в моем примере ввода списка есть 4 "класса".)

Спасибо, Фил

Ответ 1

Вы можете использовать groupBy и выполняться в одной строке.

   scala> inputList.groupBy(_._1).
          map{ case (key, value) => (key, value.map(_._2).mkString(" "))}.toList

    res0: List[(String, String)] = List(
                                       (href,testHref1), 
                                       (class,testClass1 testClass2 testClassRepeat testClassRepeat), 
                                       (id,testId1 testId2)
)

Ответ 2

def f(xs: List[(String, String)]): Map[String, List[String]] = 
    xs.foldRight(Map.empty[String, List[String]]){ 
          (elem: (String, String), acc: Map[String, List[String]]) =>
             val (key, value) = elem
             acc.get(key) match {
                case None     => acc + (key -> List(value))
                case Some(ys) => acc.updated(key, value :: ys)
             }
    }

scala> f(inputList)
res2: Map[String,List[String]] = Map(
       href -> List(testHref1), 
       id -> List(testId1, testId2), 
       class -> List(testClass1, testClass2, testClassRepeat, testClassRepeat)
     )

Ответ 3

Может быть, groupBy() - это то, что вы ищете?

scala> inputList.groupBy(_._1)
res0: Map[String,List[(String, String)]] = Map(
         href -> List((href,testHref1)),
         class -> List((class,testClass1), (class,testClass2), (class,testClassRepeat), (class,testClassRepeat)),
         id -> List((id,testId1), (id,testId2))
      )

Также довольно просто очистить список кортежей, пока мы на нем, например.

scala> inputList.groupBy(_._1).map(kv => (kv._1, kv._2.map(_._2)))
res1: Map[String,List[String]] = Map(
         href -> List(testHref1),
         class -> List(testClass1, testClass2, testClassRepeat, testClassRepeat),
         id -> List(testId1, testId2)
      )

Ответ 4

Вы можете использовать foldLeft для сортированной коллекции:

def transformList(input:List[(String,String)]):List[(String,String)] =
  input
    .sortBy(_._1)
    .foldLeft(List[(String, String)]()) {
      case ((xn,xv)::xs, (name, value)) if xn==name => (xn, xv + " " + value)::xs
      case (xs, item) => item::xs
    }

Ответ 5

Еще один способ с пониманием, но также используя groupBy (см. @hezamu):

val m = 
  for { (k, xs) <- inputList.groupBy(_._1)
         s = xs.map(_._2).mkString(" ")
      }
  yield k -> s

а затем

m.toMap

Ответ 6

Вы можете использовать groupBy с for comprehension, чтобы сделать код более встроенным с реляционными концепциями SQL, в котором нужный результат аналогичен ключу Group By on, а затем преобразует сгруппированный результат, в этом случае конкатенацию строка:

def transformList(input:List[(String,String)]):List[(String,String)] = {
  (for {
    // Return the generator for key-values of grouped result
    (k, v) <- input.groupBy(y => y._1)
    // For every list in the grouped result return the concatenated string
    z = v.map(_._2).mkString(" ")
  } yield k -> z)
  .toList

}