Найкращий спосіб 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) }

Але зараз мені потрібна змінна карта. Чи є кращий спосіб зробити це, щоб воно було в одному рядку, і я закінчував незмінну карту? (Очевидно, я міг би перетворити вищезазначене на просту утиліту бібліотеки, як у Java, але я підозрюю, що в Scala немає потреби)

Відповіді:


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
Надайте, будь ласка, джерела тестування спільноті ЗП? Дякую.
користувач573215

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

Тільки для запису, мені цікаво, чому нам потрібно точно уточнити, що це послідовність з _ . карта все ще перетворить повернути сюди список кортежів. То чому ж _ ? Я маю на увазі, що це працює, але я хотів би зрозуміти тип
припису

1
Це більш ефективно, ніж інші методи?
липня 1515

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 із сумою та функцією "_ + _"; це набагато заплутаніше! Здається, ця функція передбачає, що у мене вже є кортеж (м, с), якого я не отримую
oxbow_lakes

2
Людина, Скала тоді була дивною!
зниклий фактор

8
@Daniel Я пробую ваш код, але з'являється така помилка: "оновлення значення не є членом 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.


8

Інше рішення (може працювати не для всіх типів)

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

це дозволяє уникнути створення посередницького списку, більше інформації тут: Scala 2.8 breakOut


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 }. Таким чином, ви можете робити такі речі, як згладжувати карти, коли клавіша є опцією [_].
healsjnr

@healsjnr Звичайно, це можна сказати для будь-якої карти. Тут це не головне питання.
Ейал Рот

1
Ви можете використовувати .mapValues(_.head)замість карти.
lex82

2

Це, мабуть, не найефективніший спосіб перетворити список на карту, але це робить код виклику більш читабельним. Я використовував неявні перетворення, щоб додати метод 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)

Зауважте, що через неявну конверсію код абонента повинен імпортувати неявні конверсії Scala.


2
c map (_.getP) zip c

Добре працює і дуже інтуїтивно


8
Будь ласка, додайте більше деталей.
Syeda Zunaira

2
Мені шкода. Але це відповідь на питання "Найкращий спосіб Scala перетворити колекцію в карту за ключем?" як це Бен Лінгс.
Йорг Бахтігер

1
А Бен не дав жодних пояснень?
shinzou

1
це створює два списки і об'єднується в "карту", використовуючи елементи в cякості ключа (роду). Зверніть увагу на "карту", оскільки отримана колекція не є масштабом, Mapале створює інший список / ітерабельний кортежів ... але ефект такий же для цілей ОП, я не знижую простоту, але це не так ефективно, як foldLeftрішення, і це не реальна відповідь на питання "перетворення колекції в карту за ключем"
Dexter Legaspi


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))

Крім того, фіві, ось як би виглядали ці двоє в 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
}

Карта має бути зміненою, а карту потрібно повернути, оскільки додавання до змінної карти не повертає карту.


1
Насправді вона може бути реалізована незмінно так: val personsMap = persons.foldLeft(Map[Int, PersonDTO]()) { (m, p) => m + (p.id -> p) }Карта може бути непорушною, як показано вище, тому що додавання до непорушної карти повертає нову незмінну карту з додатковим записом. Це значення служить акумулятором за допомогою операції складання.
RamV13

-2

використовувати карту () для колекції, а потім - toMap

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

3
Чим це відрізняється від відповіді, яка була подана та прийнята 7 років тому?
jwvh

-4

Якщо переходить з Json String (читання файлу json) в масштабовану карту

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.