튜플 목록을 맵으로 변환 (중복 키 처리?)


91

중복 키가있는 튜플 목록 [("a","b"),("c","d"),("a","f")]을 map 으로 변환하는 좋은 방법에 대해 생각하고있었습니다 ("a" -> ["b", "f"], "c" -> ["d"]). 일반적으로 (파이썬에서) 목록에 빈 맵과 for 루프를 만들고 중복 키를 확인합니다. 그러나 나는 여기에서 더 스칼라적이고 영리한 솔루션을 찾고 있습니다.

btw, 여기에서 사용하는 키-값의 실제 유형은 다음 (Int, Node)과 같습니다.(Int -> NodeSeq)

답변:


79

그룹화 후 프로젝트 :

scala> val x = List("a" -> "b", "c" -> "d", "a" -> "f")
//x: List[(java.lang.String, java.lang.String)] = List((a,b), (c,d), (a,f))
scala> x.groupBy(_._1).map { case (k,v) => (k,v.map(_._2))}
//res1: scala.collection.immutable.Map[java.lang.String,List[java.lang.String]] = Map(c -> List(d), a -> List(b, f))

폴드를 사용하는 더 깔끔한 방법, 거기 와 같은 방식 (스킵 map f단계).


125

중복을 기대하지 않거나 기본 중복 처리 정책에 문제가없는 Google 직원의 경우 :

List("a" -> 1, "b" -> 2).toMap
// Result: Map(a -> 1, c -> 2)

2.12부터 기본 정책은 다음과 같습니다.

중복 키는 이후 키로 덮어 쓰여집니다. 이것이 순서가 지정되지 않은 컬렉션 인 경우 결과 맵에있는 키는 정의되지 않습니다.


57

다른 대안이 있습니다.

x.groupBy(_._1).mapValues(_.map(_._2))

이것은 우리에게 Map[String, SeqView[String,Seq[_]]]...이 의도적입니까?
Luigi Plinge 2011

1
@LuigiPlinge A SeqView[String,Seq[_]]Seq[String]. 아직 돌이켜 보면 그럴 가치가 없다고 생각하므로 view. mapValues어쨌든 값을 볼 것입니다.
Daniel C. Sobral 2011

이것은 제 경우에 완벽하게 작업했습니다 (강좌 숙제) : lazy val dictionaryByOccurrences : Map [Occurrences, List [Word]] = {val pairs = for (curWord <-dictionary) yield {val curWordOccurrences = wordOccurrences (curWord) (curWordOccurrences, curWord)} pairs.groupBy (._1 ) .mapValues ​​( .map (_._ 2))}
JasonG

mapValues는 새 맵이 아닌 맵 뷰를 반환합니다. scala-lang.org/api/current/index.html#scala.collection.Map
Max Heiber

1
식이 사용될 때마다 다시 계산 x.groupBy(_._1).mapValues(_.map(_._2)).map(identity)되기 때문에 아마도 원할 mapValues것입니다. 참조 issues.scala-lang.org/browse/SI-7005
제프리 아길레라

20

중복에 관심이있는 Google 직원의 경우 :

implicit class Pairs[A, B](p: List[(A, B)]) {
  def toMultiMap: Map[A, List[B]] = p.groupBy(_._1).mapValues(_.map(_._2))
}

> List("a" -> "b", "a" -> "c", "d" -> "e").toMultiMap
> Map("a" -> List("b", "c"), "d" -> List("e")) 

12

시작 Scala 2.13하면 대부분의 컬렉션 은 (이름에서 알 수 있듯이) a와 동등한 (더 효율적) 뒤에 오는 groupMap 메서드 와 함께 제공 groupBy됩니다 mapValues.

List("a" -> "b", "c" -> "d", "a" -> "f").groupMap(_._1)(_._2)
// Map[String,List[String]] = Map(a -> List(b, f), c -> List(d))

이:

  • group튜플의 첫 번째 부분을 기반으로하는 s 요소 ( 그룹 Map 의 그룹 부분 )

  • map두 번째 튜플 부분을 취하여 그룹화 된 값 ( Map 그룹의 부분 매핑 )

이는 목록 을 한 번에 통과 하는 것과 동일 list.groupBy(_._1).mapValues(_.map(_._2))하지만 수행됩니다 .


4

다음은 튜플 목록을 중복 키를 처리하는 맵으로 변환하는 더 스칼라 관용적 방법입니다. 접기를 사용하고 싶습니다.

val x = List("a" -> "b", "c" -> "d", "a" -> "f")

x.foldLeft(Map.empty[String, Seq[String]]) { case (acc, (k, v)) =>
  acc.updated(k, acc.getOrElse(k, Seq.empty[String]) ++ Seq(v))
}

res0: scala.collection.immutable.Map[String,Seq[String]] = Map(a -> List(b, f), c -> List(d))

1
왜 이것이 여기에 제공된 groupBy-mapValue 솔루션보다 더 스칼라 스타일이라고 생각합니까?
Make42

@ om-nom-nom 문 "접기를 사용하는 더 깔끔한 방법, 거기와 같은 방식으로 (맵 f 단계 건너 뛰기)."
cevaris

나는 논리적 인 논쟁을 기대하고 있었다 ;-). om-nom-nom도 링크 된 기사도 내 질문에 대한 증거를 제공하지 않았습니다. (아니면 내가 놓쳤나요?)
Make42

1
@ Make42 모든 모나드는 모노 이드이고 법에 따라 모노 이드는 접을 수 있기 때문에 이것을 처리하는 더 fp 방법입니다. fp에서 객체와 이벤트는 모나드로 모델링되며 모든 모나드가 groupBy를 구현하지는 않습니다.
soote jul.

4

아래에서 몇 가지 솔루션을 찾을 수 있습니다. (GroupBy, FoldLeft, Aggregate, Spark)

val list: List[(String, String)] = List(("a","b"),("c","d"),("a","f"))

GroupBy 변형

list.groupBy(_._1).map(v => (v._1, v._2.map(_._2)))

왼쪽 접기 변형

list.foldLeft[Map[String, List[String]]](Map())((acc, value) => {
  acc.get(value._1).fold(acc ++ Map(value._1 -> List(value._2))){ v =>
    acc ++ Map(value._1 -> (value._2 :: v))
  }
})

집계 변형-왼쪽 접기와 유사

list.aggregate[Map[String, List[String]]](Map())(
  (acc, value) => acc.get(value._1).fold(acc ++ Map(value._1 -> 
    List(value._2))){ v =>
     acc ++ Map(value._1 -> (value._2 :: v))
  },
  (l, r) => l ++ r
)

Spark Variation-빅 데이터 세트 용 (RDD에서 RDD 및 일반 맵으로 변환)

import org.apache.spark.rdd._
import org.apache.spark.{SparkContext, SparkConf}

val conf: SparkConf = new 
SparkConf().setAppName("Spark").setMaster("local")
val sc: SparkContext = new SparkContext (conf)

// This gives you a rdd of the same result
val rdd: RDD[(String, List[String])] = sc.parallelize(list).combineByKey(
   (value: String) => List(value),
   (acc: List[String], value) => value :: acc,
   (accLeft: List[String], accRight: List[String]) => accLeft ::: accRight
)

// To convert this RDD back to a Map[(String, List[String])] you can do the following
rdd.collect().toMap

2

당신은 이것을 시도 할 수 있습니다

scala> val b = new Array[Int](3)
// b: Array[Int] = Array(0, 0, 0)
scala> val c = b.map(x => (x -> x * 2))
// c: Array[(Int, Int)] = Array((1,2), (2,4), (3,6))
scala> val d = Map(c : _*)
// d: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 2 -> 4, 3 -> 6)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.