2009-03-23 2 views
121

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

ответ

183

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

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

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

+8

Я по-прежнему предпочитаю, чтобы мои предложения в trac «Traversable [K] .mapTo (K => V)» и «Traversable [V] .mapBy (V => K)» были лучше! –

+0

Как альтернатива, с zip: 'c map (_.getP) zip c toMap' – onof

+6

Помните, что это квадратичная операция, но то же самое касается большинства других вариантов, приведенных здесь. Если посмотреть на исходный код scala.collection.mutable.MapBuilder и т. Д., Мне кажется, что для каждого кортежа создается новая неизменяемая карта, к которой добавляется кортеж. –

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 still convert возвращает список кортежей здесь. Итак, почему _ *? Я имею в виду, что это работает, но я хотел бы понять тип заявки здесь – MaatDeamon

+0

Является ли это более эффективным, чем другие методы? – Jus12

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

* Правильно ли это? Согласно scaladoc foldLeft: \t "foldLeft [B] (z: B) (op: (B, A) => B): B" B в этом случае должна быть Карта [String, Int], поэтому Я вообще не понимаю эту функцию в вашем примере! Он должен вернуть карту для начала, не так ли? –

+0

ОК - так у меня это получилось! «m (s) = s.length» (где m - это карта) возвращает новую карту с отображением «s -> s.length». Как я должен был это знать? Я не могу найти его нигде в программировании в разделах scala на картах! –

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

Кроме того, fwiw, вот как эти два будут выглядеть в Haskell: «Map.fromList $ map (bar &&& id) c',' Map.fromList $ map (bar >> = (,)) c'. – missingfaktor

6

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

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

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

1

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

2
c map (_.getP) zip c 

работает хорошо и очень Intuitiv

+6

Пожалуйста, добавьте более подробную информацию. –

+2

Извините Но это ответ на вопрос «Лучший способ превратить коллекцию в карту« Скала »?», Как Бен Лингс. –

+1

И Бен не дал никаких объяснений? – shinzou

-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

4

То, что вы пытаетесь достичь, немного не определено.
Что делать, если два или более предмета в 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) собирать {case (Some (p), ts) => p -> ts.head}' ''. Таким образом, вы можете делать такие вещи, как сплющенные карты, когда вы используете опцию [_]. – healsjnr

+0

@healsjnr Конечно, это можно было бы сказать для любой карты. Однако это не основная проблема. –

5

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

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

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

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

-1

использование карты() по сбору с последующим toMap

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

Как это отличается от ответа, который был представлен и принят 7 лет назад? – jwvh

Смежные вопросы