Scala лучший способ превратить коллекцию в карту под ключ?

165

Если у меня есть коллекция 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 нет необходимости)

oxbow_lakes
источник

Ответы:

232

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

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

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

Бен Лингс
источник
8
Я все еще предпочитаю мои предложения в trac of a Traversable[K].mapTo( K => V)и Traversable[V].mapBy( V => K)были лучше!
oxbow_lakes
7
Имейте в виду, что это квадратичная операция, но то же самое относится и к большинству других вариантов, приведенных здесь. Глядя на исходный код scala.collection.mutable.MapBuilder и т. Д., Мне кажется, что для каждого кортежа создается новая неизменяемая карта, к которой добавляется кортеж.
jcsahnwaldt Восстановить Монику
30
На моей машине для списка с 500 000 элементов этот код Scala примерно в 20 раз медленнее, чем простой Java-подход (создайте HashMap с соответствующим размером, переберите список, поместите элементы в карту). Для 5000 элементов Scala работает примерно в 8 раз медленнее. Циклический подход, написанный на Scala, примерно в 3 раза быстрее, чем вариант toMap, но все же в 2-7 раз медленнее, чем Java.
jcsahnwaldt Восстановить Монику
8
Не могли бы вы предоставить тестовые источники SO-сообществу? Спасибо.
user573215
8
Замените cна, c.iteratorчтобы избежать создания промежуточной коллекции.
ghik
21

Вы можете создать карту с переменным количеством кортежей. Поэтому используйте метод 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)
Джеймс Ири
источник
5
Я читал о Scala более 2 недель и работал с примерами, и ни разу не видел эту запись ": _ *"! Большое спасибо за вашу помощь
oxbow_lakes
Просто для записи, мне интересно, почему мы должны уточнить, что это последовательность с _ . карта все еще конвертирует, возвращает список кортежей здесь. Так почему _ ? Я имею в виду, что это работает, но я хотел бы понять приписывание типа здесь
MaatDeamon
1
Это более эффективно, чем другие методы?
Jus12
16

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

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }
Даниэль Спивак
источник
Я попробую это (я уверен, что это работает :-). Что происходит с функцией "(m, s) => m (s) = s.length"? Я видел типичный пример foldLeft с суммой и функцией "_ + _"; это намного более запутанно! Кажется, что функция предполагает, что у меня уже есть кортеж (m, s), который я на самом деле не получаю
oxbow_lakes
2
Чувак, тогда Скала была странной!
фактор
8
@Daniel Я пробую ваш код, но появляется следующая ошибка: «Значение update не является членом scala.collection.immutable.Map [String, Int]». Пожалуйста, объясните свой код, как работает этот код?
mr.boyfox
1
кажется, не работает. для меня либо «Приложение не принимает параметры»
jayunit100
7
Неизменная версия: list.foldLeft(Map[String,Int]()) { (m,s) => m + (s -> s.length) }. Обратите внимание , что если вы хотите использовать запятую , чтобы построить кортеж, вам требуется дополнительная пара скобок: ((s, s.length)).
Кельвин
11

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

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

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

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

RamV13
источник
8

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

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

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

Somatik
источник
7

То, что вы пытаетесь достичь, немного неопределенно.
Что делать, если два или более элементов в 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 }
Эяль Рот
источник
1
Одним из удобных способов настройки является использование collectвместо map. Например: c.group(t => t.p) collect { case (Some(p), ts) => p -> ts.head }. Таким образом, вы можете делать такие вещи, как сглаживать карты, когда вы нажимаете клавишу Option [_].
healsjnr
@healsjnr Конечно, это можно сказать для любой карты. Это не главная проблема здесь, все же.
Эяль Рот
1
Вы могли бы использовать .mapValues(_.head)вместо карты.
lex82
2

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

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.

Эрез
источник
2
c map (_.getP) zip c

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

Йорг Бехтигер
источник
8
Пожалуйста, добавьте больше деталей.
Сайеда Зунайра
2
Мне жаль. Но это ответ на вопрос "Scala лучший способ превратить коллекцию в карту по ключу?" как Бен Лингс
Йорг Бехтигер
1
А Бен не дал никаких объяснений?
Синдзоу
1
это создает два списка и объединяется в «карту», ​​используя элементы в cкачестве ключа (вроде). Обратите внимание на «карту», ​​потому что результирующая коллекция не является scala, Mapно создает другой список / итерируемый набор кортежей ... но эффект такой же для целей OP, я не стал бы сбрасывать со счетов простоту, но это не так эффективно, как foldLeftрешение, и не реальный ответ на вопрос «превращение коллекции в карту по ключу»
Декстер Легаспи
2

Как насчет использования zip и toMap?

myList.zip(myList.map(_.length)).toMap
AwesomeBobX64
источник
1

Для чего это стоит, вот два бессмысленных способа сделать это:

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))
missingfaktor
источник
Кроме того , FWIW, это то , как эти два будет выглядеть в Haskell: Map.fromList $ map (bar &&& id) c, Map.fromList $ map (bar >>= (,)) c.
фактор
-1

Это работает для меня:

val personsMap = persons.foldLeft(scala.collection.mutable.Map[Int, PersonDTO]()) {
    (m, p) => m(p.id) = p; m
}

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

rustyfinger
источник
1
Фактически, это может быть реализовано неизменным следующим образом: val personsMap = persons.foldLeft(Map[Int, PersonDTO]()) { (m, p) => m + (p.id -> p) }Карта может быть неизменной, как показано выше, потому что добавление к неизменяемой Карте возвращает новую неизменяемую Карту с дополнительной записью. Это значение служит аккумулятором при операции складывания.
RamV13
-2

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

val map = list.map(e => (e, e.length)).toMap
Кришна Кумар Чурасия
источник
3
Чем это отличается от ответа, который был представлен и принят 7 лет назад?
jwvh
-4

При преобразовании из Json String (чтение файла JSON) в Scala Map

import spray.json._
import DefaultJsonProtocol._

val jsonStr = Source.fromFile(jsonFilePath).mkString
val jsonDoc=jsonStr.parseJson
val map_doc=jsonDoc.convertTo[Map[String, JsValue]]

// Get a Map key value
val key_value=map_doc.get("key").get.convertTo[String]

// If nested json, re-map it.
val key_map=map_doc.get("nested_key").get.convertTo[Map[String, JsValue]]
println("Nested Value " + key_map.get("key").get)
Аджит Сурендран
источник
Этот 11-летний вопрос ничего не спрашивает о JSON. Ваш ответ неуместен.
jwvh