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

119

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

Теги:
scala-collections
map

10 ответов

169
Лучший ответ

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

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

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

  • 8
    Я все еще предпочитаю, чтобы мои предложения в tracable Traversable[K].mapTo( K => V) и Traversable[V].mapBy( V => K) были лучше!
  • 0
    Как альтернатива, с zip: c map (_.getP) zip c toMap
Показать ещё 8 комментариев
14

Вы можете построить карту с переменным количеством кортежей. Поэтому используйте метод 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 недель и работал с примерами, и ни разу не видел эту запись ": _ *"! Большое спасибо за вашу помощь
  • 0
    Просто для записи, мне интересно, почему мы должны уточнить, что это последовательность с _ . map все еще конвертируйте, верните список кортежей здесь. Так почему _ ? Я имею в виду, что это работает, но я хотел бы понять тип приписывания здесь
Показать ещё 1 комментарий
10

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

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }
  • 0
    Я попробую это (я уверен, что это работает :-). Что происходит с функцией "(m, s) => m (s) = s.length"? Я видел типичный пример foldLeft с суммой и функцией "_ + _"; это намного более запутанно! Кажется, что функция предполагает, что у меня уже есть кортеж (m, s), который я на самом деле не получаю
  • 0
    Это правильно? В соответствии с скалярной версией foldLeft: "foldLeft [B] (z: B) (op: (B, A) => B): B" B в этом случае должно быть Map [String, Int], поэтому я не Я действительно не понимаю функцию в вашем примере! Это должно вернуть карту для начала, не так ли?
Показать ещё 8 комментариев
6

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

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

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

4

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

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

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

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

4

То, чего вы пытаетесь достичь, немного undefined.
Что делать, если два или более элемента в 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 => tp) collect { case (Some(p), ts) => p -> ts.head } . Таким образом, вы можете делать такие вещи, как сглаживать карты, когда вы нажимаете клавишу Option [_].
  • 0
    @healsjnr Конечно, это можно сказать для любой карты. Это не главная проблема здесь, все же.
Показать ещё 1 комментарий
2
c map (_.getP) zip c

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

  • 8
    Пожалуйста, добавьте больше деталей.
  • 2
    Мне жаль. Но это ответ на вопрос "Scala лучший способ превратить коллекцию в карту по ключу?" как Бен Лингс
Показать ещё 2 комментария
1

Это, вероятно, не самый эффективный способ превратить список в карту, но он делает код вызова более читабельным. Я использовал неявные преобразования, чтобы добавить метод 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 implicitConversions.

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))
  • 0
    Кроме того, вот как они выглядят в Haskell: Map.fromList $ map (bar &&& id) c , Map.fromList $ map (bar >>= (,)) c .
0

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

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) } Карта может быть неизменной , как показано выше, потому что добавление к неизменяемой Карте возвращает новую неизменяемую Карту с дополнительной записью. Это значение служит аккумулятором при операции складывания.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню