Scala лучший способ превратить Коллекцию в Карту под ключ? - PullRequest
146 голосов
/ 23 марта 2009

Если у меня есть коллекция c типа T и есть свойство p для T (скажем, типа P), как лучше всего создать карту -по-извлечения ключа

val c: Collection[T]
val m: Map[P, T]

Один из способов заключается в следующем:

m = new HashMap[P, T]
c foreach { t => m add (t.getP, t) }

Но теперь мне нужна изменяемая карта. Есть ли лучший способ сделать это так, чтобы он был в 1 строке, и я получаю неизменную карту? (Очевидно, я мог бы превратить вышесказанное в простую библиотечную утилиту, как в Java, но я подозреваю, что в Scala нет необходимости)

Ответы [ 11 ]

213 голосов
/ 14 июля 2010

Вы можете использовать

c map (t => t.getP -> t) toMap

но помните, что для этого нужно 2 обхода.

18 голосов
/ 24 марта 2009

Вы можете создать карту с переменным количеством кортежей. Поэтому используйте метод map в коллекции, чтобы преобразовать его в коллекцию кортежей, а затем используйте трюк: _ *, чтобы преобразовать результат в переменный аргумент.

scala> val list = List("this", "maps", "string", "to", "length") map {s => (s, s.length)}
list: List[(java.lang.String, Int)] = List((this,4), (maps,4), (string,6), (to,2), (length,6))

scala> val list = List("this", "is", "a", "bunch", "of", "strings")
list: List[java.lang.String] = List(this, is, a, bunch, of, strings)

scala> val string2Length = Map(list map {s => (s, s.length)} : _*)
string2Length: scala.collection.immutable.Map[java.lang.String,Int] = Map(strings -> 7, of -> 2, bunch -> 5, a -> 1, is -> 2, this -> 4)
14 голосов
/ 24 марта 2009

В дополнение к решению @James Iry, это также возможно сделать с помощью сгиба. Я подозреваю, что это решение немного быстрее, чем метод кортежа (создается меньше мусорных объектов):

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }
9 голосов
/ 13 декабря 2016

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

val map = c.foldLeft(Map[P, T]()) { (m, t) => m + (t.getP -> t) }

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

Компромисс здесь - простота кода и его эффективность. Таким образом, для больших коллекций этот подход может быть более подходящим, чем использование двух реализаций обхода, таких как применение map и toMap.

8 голосов
/ 28 октября 2013

Другое решение (может не работать для всех типов)

import scala.collection.breakOut
val m:Map[P, T] = c.map(t => (t.getP, t))(breakOut)

это позволяет избежать создания списка посредников, подробнее здесь: Scala 2.8 breakOut

6 голосов
/ 03 декабря 2015

То, что вы пытаетесь достичь, немного неопределенно.
Что если два или более элемента в c имеют одинаковые p? Какой элемент будет сопоставлен с этим p на карте?

Более точный способ взглянуть на это - получить карту между p и всеми c предметами, которые имеют это:

val m: Map[P, Collection[T]]

Этого легко достичь с помощью groupBy :

val m: Map[P, Collection[T]] = c.groupBy(t => t.p)

Если вам все еще нужна исходная карта, вы можете, например, сопоставить p с первой t, в которой она есть:

val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }
2 голосов
/ 04 декабря 2014
c map (_.getP) zip c

Работает хорошо и очень интуитивно

1 голос
/ 27 июля 2014

Возможно, это не самый эффективный способ преобразования списка в карту, но он делает код вызова более читабельным. Я использовал неявные преобразования, чтобы добавить метод mapBy в список:

implicit def list2ListWithMapBy[T](list: List[T]): ListWithMapBy[T] = {
  new ListWithMapBy(list)
}

class ListWithMapBy[V](list: List[V]){
  def mapBy[K](keyFunc: V => K) = {
    list.map(a => keyFunc(a) -> a).toMap
  }
}

Пример телефонного кода:

val list = List("A", "AA", "AAA")
list.mapBy(_.length)                  //Map(1 -> A, 2 -> AA, 3 -> AAA)

Обратите внимание, что из-за неявного преобразования код вызывающей стороны должен импортировать implicitConversions scala.

1 голос
/ 04 февраля 2012

Что бы это ни стоило, вот два бессмысленных способа сделать это:

scala> case class Foo(bar: Int)
defined class Foo

scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._

scala> val c = Vector(Foo(9), Foo(11))
c: scala.collection.immutable.Vector[Foo] = Vector(Foo(9), Foo(11))

scala> c.map(((_: Foo).bar) &&& identity).toMap
res30: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))

scala> c.map(((_: Foo).bar) >>= (Pair.apply[Int, Foo] _).curried).toMap
res31: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))
0 голосов
/ 24 декабря 2017

используйте map () для коллекции, затем toMap

val map = list.map(e => (e, e.length)).toMap
...