我有一個元組串列,其中第一個元素是一個字串,第二個元素是一個字串串列。
例如...(忽略語音標記)
val p = List((a, List(x,y,z)), (b, List(x)), (c, List(y,z)))
我的目標是將這個串列分組到一個映射中,嵌套串列的元素作為鍵。
val q = Map(x -> List(a,b), y -> List(a,c), z-> List(a,c))
我最初的想法是按 p 的第二個元素分組,但這會將整個串列分配給鍵。
我是 Scala 的初學者,所以任何建議都值得贊賞。我應該期望能夠用高階函式來完成這個還是 for 回圈在這里有用?
提前致謝 :)
uj5u.com熱心網友回復:
這里有兩個變體:
val p = List(("a", List("x","y","z")), ("b", List("x")), ("c", List("y","z")))
// 1. "Transducers"
p.flatMap{ case (k, v) => v.map { _ -> k } } // List((x,a), (y,a), (z,a), (x,b), (y,c), (z,c))
.groupBy(_._1) // Map(z -> List((z,a), (z,c)), y -> List((y,a), (y,c)), x -> List((x,a), (x,b)))
.mapValues(_.map(_._2)) // Map(z -> List(a, c), y -> List(a, c), x -> List(a, b))
// 2. For-loop
var res = Map[String, List[String]]()
for ( (k, vs) <- p; v <- vs) {
res = v -> k :: res.getOrElse(v, List())
}
res // Map(x -> List(b, a), y -> List(c, a), z -> List(c, a))
// Note, values of `res` are inverted,
// because the efficient "cons" operator (::) was used to add values to the lists
// you can revert the lists afterwards as this:
res.mapValues(_.reverse) // Map(x -> List(a, b), y -> List(a, c), z -> List(a, c))
第二個變體的性能更高,因為沒有創建中間集合,但它也可以被認為是“不太慣用的”,因為使用了可變變數res。但是,在私有方法中使用可變方法完全沒問題。
更新。根據@LuisMiguelMejíaSuárez 的建議:
(1)中,由于scala 2.13, groupBy后跟mapValues可以換成groupMap,所以整個鏈變成:
p.flatMap{ case (k, v) => v.map { _ -> k } }
.groupMap(_._1)(_._2)
可以使用foldLeft以下方法實作另一個沒有中間集合的功能變體:
p.foldLeft(Map[String, List[String]]()) {
case (acc, (k, vs)) =>
vs.foldLeft(acc) { (acc1, v) =>
acc1 (v -> (k :: acc1.getOrElse(v, List())))
}
}
或者使用updatedWith(scala 2.13)稍微更有效:
p.foldLeft(Map[String, List[String]]()) {
case (acc, (k, vs)) =>
vs.foldLeft(acc) { (acc1, v) =>
acc1.updatedWith(v) {
case Some(list) => Some(k :: list)
case None => Some(List(k))
}
}
}
......或者同樣的事情稍微短一點:
p.foldLeft(Map[String, List[String]]()) {
case (acc, (k, vs)) =>
vs.foldLeft(acc) { (acc1, v) =>
acc1.updatedWith(v)(_.map(k :: _).orElse(Some(List(k))))
}
}
總的來說,我建議要么使用foldLeft變體(最高效和功能最強大),要么使用第一個groupMap變體(更短,可以說更具可讀性,但性能更差),具體取決于您的目標。
uj5u.com熱心網友回復:
您的輸入串列p距離成為Map. 從那里您只需要一個通用的 Map 逆變器。
import scala.collection.generic.IsIterableOnce
import scala.collection.Factory
// from Map[K,C[V]] to Map[V,C[K]] (Scala 2.13.x)
implicit class MapInverter[K,V,C[_]](m: Map[K,C[V]]) {
def invert(implicit iio: IsIterableOnce[C[V]] {type A = V}
, fac: Factory[K,C[K]]): Map[V,C[K]] =
m.foldLeft(Map.empty[V, List[K]]) {
case (acc, (k, vs)) =>
iio(vs).iterator.foldLeft(acc) {
case (a, v) =>
a (v -> (k::a.getOrElse(v,Nil)))
}
}.map{case (k,v) => k -> v.to(fac)}
}
用法:
val p = List(("a", List("x","y","z")), ("b", List("x")), ("c", List("y","z")))
val q = p.toMap.invert
//Map(x -> List(b, a), y -> List(c, a), z -> List(c, a))
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/316320.html
