Преобразовать список кортежей в карту (и иметь дело с повторяющимся ключом?)

91

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

кстати, реальный тип пары "ключ-значение", которую я здесь использую, - это, (Int, Node)и я хочу превратить ее в карту(Int -> NodeSeq)

Tg.
источник

Ответы:

79

Сгруппируйте, а затем проект:

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шаг).

ом Ном ном
источник
125

Для гуглеров, которые не ждут дубликатов или согласны с политикой обработки дубликатов по умолчанию :

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

Начиная с 2.12 политика по умолчанию гласит:

Повторяющиеся ключи будут перезаписаны более поздними ключами: если это неупорядоченная коллекция, то какой ключ в результирующей карте не определен.

Кори Кляйн
источник
57

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

x.groupBy(_._1).mapValues(_.map(_._2))
Дэниел С. Собрал
источник
Это дает нам Map[String, SeqView[String,Seq[_]]]... это намеренно?
Луиджи Плиндж 05
1
@LuigiPlinge A SeqView[String,Seq[_]]- это тоже Seq[String]. Оглядываясь назад, я не думаю, что это стоит того, поэтому я удалил view. mapValuesв любом случае будет просматривать значения.
Дэниел С. Собрал
Это отлично сработало для моего случая (домашнее задание на coursera): lazy val dictionaryByOccurrences: Map [Occurrences, List [Word]] = {val pair = for (curWord <- dictionary) yield {val curWordOccurrences = wordOccurrences (curWord) (curWordOccurrences, curWord)} pair.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

Для гуглеров, которые заботятся о дубликатах:

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, который (как следует из названия) является эквивалентом (более эффективным) a, 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))
Cevaris
источник
1
Как вы думаете, почему это больше похоже на Scala, чем предлагаемые здесь решения groupBy-mapValue?
Make42
Оператор @ om-nom-nom «Более масштабный способ использования свертки, как здесь (пропустите шаг map f)».
cevaris
Я надеялся на логичный аргумент ;-). Ни om-nom-nom, ни связанная статья не подтвердили мой вопрос. (Или я это пропустил?)
Make42
1
@ Make42 Это более удобный способ справиться с этим, поскольку все монады являются моноидами, а моноиды по закону складываются. В fp объекты и события моделируются как монады, и не все монады будут реализовывать groupBy.
soote
4

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

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

Группировать по вариации

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
)

Spark Variation - для больших наборов данных (преобразование в 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)
Frankfzw
источник