Перетворити список кортежів на карту (і мати справу з дублікатом ключа?)


90

Я думав про хороший спосіб перетворити список кортежу з дублікатом ключа [("a","b"),("c","d"),("a","f")]на карту ("a" -> ["b", "f"], "c" -> ["d"]). Зазвичай (у python) я створював порожню карту та цикл for по списку та перевіряв наявність дубліката ключа. Але я шукаю тут щось більш масштабне та розумне рішення.

До речі, фактичний тип ключа-значення, який я тут використовую, є (Int, Node)і я хочу перетворити на карту(Int -> NodeSeq)

Відповіді:


78

Групувати, а потім проектувати:

scala> val x = List("a" -> "b", "c" -> "d", "a" -> "f")
//x: List[(java.lang.String, java.lang.String)] = List((a,b), (c,d), (a,f))
scala> x.groupBy(_._1).map { case (k,v) => (k,v.map(_._2))}
//res1: scala.collection.immutable.Map[java.lang.String,List[java.lang.String]] = Map(c -> List(d), a -> List(b, f))

Більш шкідливий спосіб використання складання таким чином, як там ( map fкрок пропуску ).


124

Для працівників Google, які не очікують дублікатів або не відповідають політиці обробки дублікатів за замовчуванням :

List("a" -> 1, "b" -> 2).toMap
// Result: Map(a -> 1, c -> 2)

Станом на 2.12 політика за замовчуванням звучить так:

Дублікати ключів будуть замінені пізніше клавішами: якщо це невпорядкована колекція, який ключ на отриманій карті не визначений.


56

Ось ще одна альтернатива:

x.groupBy(_._1).mapValues(_.map(_._2))

Це дає нам Map[String, SeqView[String,Seq[_]]]... це навмисно?
Луїджі Плінге

1
@LuigiPlinge A SeqView[String,Seq[_]]також є Seq[String]. Досі оглядаючись назад, я не думаю, що це варто, тому я видалив view. mapValuesвсе одно зробить погляд на значення.
Даніель К. Собрал,

Це чудово зробило роботу для мого випадку (домашнє завдання курсу): ледачий вальський словникByOccurrences: Карта [Події, Список [Слово]] = {val пар = для (curWord <- словник) вихід {val curWordOccurrences = wordOccurrences (curWord) (curWordOccurrences, curWord)} пар.groupBy ( ._1) .mapValues ​​(.map (_._ 2))}
JasonG

mapValues ​​повертає вигляд карти, а не нову карту scala-lang.org/api/current/index.html#scala.collection.Map
Макс Хейбер

1
Можливо, хочеться, x.groupBy(_._1).mapValues(_.map(_._2)).map(identity)тому що mapValuesвираз буде перераховуватись кожного разу, коли він використовується. Див. Issues.scala-lang.org/browse/SI-7005
Джеффрі Агілера

20

Для працівників Google, яким важливі дублікати:

implicit class Pairs[A, B](p: List[(A, B)]) {
  def toMultiMap: Map[A, List[B]] = p.groupBy(_._1).mapValues(_.map(_._2))
}

> List("a" -> "b", "a" -> "c", "d" -> "e").toMultiMap
> Map("a" -> List("b", "c"), "d" -> List("e")) 

12

Починаючи з того Scala 2.13, що більшість колекцій забезпечуються методом groupMap, який є (як випливає з назви) еквівалентом (більш ефективним) groupByнаступного mapValues:

List("a" -> "b", "c" -> "d", "a" -> "f").groupMap(_._1)(_._2)
// Map[String,List[String]] = Map(a -> List(b, f), c -> List(d))

Це:

  • groups елементи на основі першої частини кортежів (групова частина групової карти)

  • maps згруповані значення, беручи їх другу частину кортежу (частина карти групи Map )

Це еквівалентно, list.groupBy(_._1).mapValues(_.map(_._2))але виконується за один прохід через Список.


4

Ось більш ідіоматичний спосіб Scala перетворити список кортежів на карту, яка обробляє дублікати ключів. Ви хочете використовувати складку.

val x = List("a" -> "b", "c" -> "d", "a" -> "f")

x.foldLeft(Map.empty[String, Seq[String]]) { case (acc, (k, v)) =>
  acc.updated(k, acc.getOrElse(k, Seq.empty[String]) ++ Seq(v))
}

res0: scala.collection.immutable.Map[String,Seq[String]] = Map(a -> List(b, f), c -> List(d))

1
Чому, на вашу думку, це більше у стилі Scala, ніж рішення groupBy-mapValue, надані тут?
Make42

@ om-nom-nom вислів "Більш шкідливий спосіб використання складання таким чином, як там (пропустіть мапу f кроком)."
cevaris

Я сподівався на логічний аргумент ;-). Ні ом-ном-ном, ні пов'язана стаття не надали доказів для мого запитання. (Або я це пропустив?)
Make42

1
@ Make42 Це більш зручний спосіб вирішити це, оскільки всі монади є моноїдами, а моноїди за законом складаються. У fp об'єкти та події моделюються як монади, і не всі монади реалізують groupBy.
soote

4

Нижче ви можете знайти кілька рішень. (GroupBy, FoldLeft, Aggregate, Spark)

val list: List[(String, String)] = List(("a","b"),("c","d"),("a","f"))

GroupBy за варіацією

list.groupBy(_._1).map(v => (v._1, v._2.map(_._2)))

Складіть ліву варіацію

list.foldLeft[Map[String, List[String]]](Map())((acc, value) => {
  acc.get(value._1).fold(acc ++ Map(value._1 -> List(value._2))){ v =>
    acc ++ Map(value._1 -> (value._2 :: v))
  }
})

Сукупна варіація - схожа на складку вліво

list.aggregate[Map[String, List[String]]](Map())(
  (acc, value) => acc.get(value._1).fold(acc ++ Map(value._1 -> 
    List(value._2))){ v =>
     acc ++ Map(value._1 -> (value._2 :: v))
  },
  (l, r) => l ++ r
)

Варіація іскри - для наборів великих даних (перетворення на RDD і на звичайну карту з RDD)

import org.apache.spark.rdd._
import org.apache.spark.{SparkContext, SparkConf}

val conf: SparkConf = new 
SparkConf().setAppName("Spark").setMaster("local")
val sc: SparkContext = new SparkContext (conf)

// This gives you a rdd of the same result
val rdd: RDD[(String, List[String])] = sc.parallelize(list).combineByKey(
   (value: String) => List(value),
   (acc: List[String], value) => value :: acc,
   (accLeft: List[String], accRight: List[String]) => accLeft ::: accRight
)

// To convert this RDD back to a Map[(String, List[String])] you can do the following
rdd.collect().toMap

2

Ви можете спробувати це

scala> val b = new Array[Int](3)
// b: Array[Int] = Array(0, 0, 0)
scala> val c = b.map(x => (x -> x * 2))
// c: Array[(Int, Int)] = Array((1,2), (2,4), (3,6))
scala> val d = Map(c : _*)
// d: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 2 -> 4, 3 -> 6)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.