스칼라 컬렉션을지도로 바꾸는 가장 좋은 방법은 무엇입니까?


165

나는 모음이있는 경우 c유형을 T하고, 등록 정보가 pT(유형 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의 내 제안을 선호 Traversable[K].mapTo( K => V)하고 Traversable[V].mapBy( V => K)더 좋았습니다!
oxbow_lakes

7
이것은 2 차 연산이지만 여기에 제공된 대부분의 다른 변형에서도 마찬가지입니다. scala.collection.mutable.MapBuilder 등의 소스 코드를 보면 각 튜플마다 튜플이 추가되는 새로운 불변 ​​맵이 생성되는 것 같습니다.
jcsahnwaldt Reinstate Monica

30
내 컴퓨터에서 500,000 개의 요소가있는 목록의 경우이 스칼라 코드는 간단한 Java 방식보다 약 20 배 느립니다 (적절한 크기로 HashMap을 만들고 목록을 반복하고 요소를지도에 넣습니다). 5,000 요소의 경우 스칼라는 약 8 배 느립니다. 스칼라로 작성된 루프 접근법은 toMap 변형보다 약 3 배 빠르지 만 여전히 Java보다 2 배에서 7 배 느립니다.
jcsahnwaldt Reinstate Monica

8
SO 커뮤니티에 테스트 소스를 제공 하시겠습니까? 고마워.
user573215

8
교체 cc.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
나는 스칼라에 대해 2 주 이상 읽었으며 예제를 통해 작업 해 왔으며이 ": _ *"표기법을 한 번도 보지 못했습니다! 도와 주셔서 감사합니다
oxbow_lakes

레코드의 경우, 왜 이것이 _의 시퀀스인지 정확하게 알아야하는 이유가 궁금 합니다. map 여전히 convert 여기에서 튜플 목록을 반환합니다. 왜 _ ? 나는 그것이 효과가 있다는 것을 의미하지만 여기에서 타입
표기

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
스칼라는 그때 이상 했어!
missingfaktor

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

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

당신이 달성하려는 것은 약간 정의되지 않은 것입니다.
두 개 이상의 항목 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 물론, 이것은 모든지도에 대해 말할 수 있습니다. 그러나 여기서 핵심적인 문제는 아닙니다.
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)

암시 적 변환으로 인해 호출자 코드는 스칼라의 implicitConversions를 가져와야합니다.


2
c map (_.getP) zip c

잘 작동하고 매우 직관적입니다


8
자세한 내용을 추가하십시오.
Syeda Zunaira

2
죄송 해요. 그러나 이것은 "컬렉션을 키별로 변환하는 스칼라 최고의 방법"이라는 질문에 대한 답변입니다. 벤 링스는
Jörg Bächtiger

1
그리고 벤은 어떤 설명도하지 않았습니까?
shinzou

1
그러면 두 개의 목록이 만들어지고 요소 c를 키 (정렬)로 사용하여 "맵"으로 결합됩니다 . 결과 컬렉션은 스칼라가 Map아니지만 다른 목록 / 반복 가능한 튜플을 생성 하기 때문에 "지도"를 참고하십시오 . 그러나 효과는 OP의 목적과 동일하지만 단순성을 할인하지는 않지만 foldLeft솔루션 만큼 효율적이지 않습니다. "키별로지도로 변환"질문에 대한 실제 답변
Dexter Legaspi

2

zip과 toMap을 사용하는 것은 어떻습니까?

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

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.
missingfaktor

-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


-3

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.