コレクションをキーごとに変換するScalaの最良の方法?


165

私がコレクションしている場合cのタイプのをTとプロパティがあるpT(タイプのP、たとえば)、行うための最善の方法何であるマップ・バイ・抽出-keyは

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

1つの方法は次のとおりです。

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

しかし、今は変更可能なマップが必要です。これを1行にして、不変の Map を作成するためにこれを行うより良い方法はありますか?(明らかに、Javaの場合と同様に、上記を単純なライブラリユーティリティに変えることができますが、Scalaでは必要がないと思います)

回答:


232

使用できます

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

ただし、これには2つのトラバーサルが必要であることに注意してください。


8
私はまだのtracの中で、私の提案を好むTraversable[K].mapTo( K => V)Traversable[V].mapBy( V => K)良好でした!
oxbow_lakes 2010

7
これは2次演算ですが、ここで説明する他のほとんどのバリアントでも同じです。scala.collection.mutable.MapBuilderなどのソースコードを見ると、タプルごとに、タプルが追加された新しい不変マップが作成されているように見えます。
jcsahnwaldt Reinstate Monica、2012

30
私のマシンで500,000要素のリストを使用すると、このScalaコードは単純なJavaアプローチ(適切なサイズのHashMapを作成し、リストをループし、要素をマップに入れる)よりも約20倍遅くなります。5,000要素の場合、Scalaは約8倍遅くなります。Scalaで記述されたループアプローチは、toMapバリアントよりも約3倍高速ですが、Javaよりも2〜7倍低速です。
jcsahnwaldt Reinstate Monica 2012

8
SOコミュニティにテストソースを提供してもらえますか?どうも。
user573215 2013

8
交換するcc.iterator、中間コレクションの回避を作成します。
ghik 2014

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 2009年

念のため、これが_のシーケンスであることを正確に示す必要があるのはなぜでしょうか。mapはまだ変換し、タプルのリストをここに返します。なぜ_なのか 私はそれが機能することを意味しますが、ここで型の表記を理解したいと思います
-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
あの頃、Scalaは変でした。
missingfaktor 2012

8
@Danielコードを試してみましたが、「値の更新はscala.collection.immutable.Map [String、Int]のメンバーではありません」というエラーが表示されます。このコードの操作方法をコードに説明してください。
mr.boyfox 14

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、やのような2つのトラバーサル実装を使用するよりも適切な場合がありtoMapます。


8

別の解決策(すべてのタイプでは機能しない場合があります)

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

これにより、中間リストの作成が回避されます。詳細はこちら: Scala 2.8 breakOut


7

あなたが達成しようとしているのは少し未定義です。
2つ以上のアイテムが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
これに関する便利な調整の1つは、のcollect代わりにを使用することですmap。例:c.group(t => t.p) collect { case (Some(p), ts) => p -> ts.head }。このようにして、キーがOption [_]のときにマップをフラット化するなどのことができます。
healsjnr 2016

@healsjnrもちろん、これはどのマップでも言えることです。ただし、ここでは中心的な問題ではありません。
Eyal Roth

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)

暗黙的な変換のため、呼び出し元のコードはscalaのimplicitConversionsをインポートする必要があることに注意してください。


2
c map (_.getP) zip c

うまく機能し、非常に直感的です


8
詳細を追加してください。
Syeda Zunaira 2014

2
申し訳ありません。しかし、これは「コレクションをキーごとのマップに変換するScalaの最良の方法?」という質問に対する回答です。ベン・リングがそうであるように。
イェルクBächtiger

1
そしてベンは何の説明もしなかったのですか?
しんぞう2017年

1
これにより、2つのリストが作成され、の要素cをキー(一種)として使用して「マップ」に結合されます。結果のコレクションはスカラではなく、Mapタプルの別のリスト/反復可能なリストを作成するため、「マップ」に注意してください...しかし、OPの目的については効果は同じです。単純さを損なうことはありませんが、foldLeftソリューションほど効率的ではありません。 「コレクションをキーでマップに変換する」という質問への本当の答え
デクスターレガスピ

2

zipとtoMapを使用するのはどうですか?

myList.zip(myList.map(_.length)).toMap

1

価値のあることを行うために、これを行う2つの無意味な方法を次に示します。

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

また、FWIW、これはこれら二つはHaskellでどのように見えるかです:Map.fromList $ map (bar &&& id) cMap.fromList $ map (bar >>= (,)) c
missingfaktor 2012

-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) }できます。不変のMapに追加すると、追加のエントリを持つ新しい不変のMapが返されるため、Mapは不変である可能性があります。この値は、フォールド操作を通じてアキュムレーターとして機能します。
RamV13 2016


-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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.