objects - Combinando dos listas en Scala
switch case in scala (8)
De 2 listas del formulario List[(Int, String)
:
l1 = List((1,"a"),(3,"b"))
l2 = List((3,"a"),(4,"c"))
¿Cómo puedo combinar los Integer
donde las String
son iguales para obtener esta tercera lista?
l3 = List((4,"a"),(3,"b"),(4,"c"))
Ahora mismo estoy recorriendo ambas listas y agregando si las cadenas son iguales, pero creo que debería haber una solución simple con la coincidencia de patrones.
Con Scalaz , esto es muy fácil.
import scalaz._
import Scalaz._
val l3 = (l1.map(_.swap).toMap |+| l2.map(_.swap).toMap) toList
El |+|
El método está expuesto en todos los tipos T
para los cuales existe una implementación de Semigroup[T]
. Y da la casualidad de que el semigrupo para Map[String, Int]
es exactamente lo que quieres.
Otro opaco de dos líneas de eficacia cuestionable y eficacia indudable:
val lst = l1 ++ l2
lst.map(_._2).distinct.map(i => (lst.filter(_._2 == i).map(_._1).sum, i))
Qué tal si,
(l1 ++ l2).groupBy(_._2).mapValues(_.unzip._1.sum).toList.map(_.swap)
Descomprimir esto un poco en el REPL ayuda a mostrar lo que está pasando,
scala> l1 ++ l2
res0: List[(Int, java.lang.String)] = List((1,a), (3,b), (3,a), (4,c))
scala> res0.groupBy(_._2)
res1: ... = Map(c -> List((4,c)), a -> List((1,a), (3,a)), b -> List((3,b)))
scala> res1.mapValues(_.unzip)
res2: ... = Map(c -> (List(4),List(c)), a -> (List(1, 3),List(a, a)), b -> (List(3),List(b)))
scala> res1.mapValues(_.unzip._1)
res3: ... = Map(c -> List(4), a -> List(1, 3), b -> List(3))
scala> res1.mapValues(_.unzip._1.sum)
res4: ... = Map(c -> 4, a -> 4, b -> 3)
scala> res4.toList
res5: List[(java.lang.String, Int)] = List((c,4), (a,4), (b,3))
scala> res5.map(_.swap)
res6: List[(Int, java.lang.String)] = List((4,c), (4,a), (3,b))
Tenga en cuenta que con esta solución, las listas se recorren dos veces.
val l3 = (l1 zip l2).foldRight(List[(Int, String)]()) {
case ((firstPair @ (firstNumber, firstWord),
secondPair @ (secondNumber, secondWord)),
result) =>
if (firstWord == secondWord)
((firstNumber + secondNumber), firstWord) :: result
else
firstPair :: secondPair :: result
}
Una alternativa a la respuesta de Miles Sabin utilizando el nuevo método groupMapReduce Scala 2.13
que es (como su nombre lo indica) un equivalente (más eficiente) de un groupBy
seguido de mapValues
y un paso de reduce
:
(l1 ::: l2).groupMapReduce(_._2)(_._1)(_ + _).toList.map(_.swap)
// List[(Int, String)] = List((3,b), (4,a), (4,c))
Esta:
antepone
l1
al2
Elementos del
group
basados en su segunda parte de la tupla (parte del grupo del grupo MapReduce)los valores agrupados del
map
en su primera parte de la tupla (parte del mapa del grupo Reducir mapa )reduce
los valores de s (_ + _
) sumándolos (reduzca parte de groupMap Reduce )y finalmente
swap
las partes de las tuplas.
Esta es una versión equivalente realizada en una sola pasada (para el grupo / mapa / reducir parte) a través de la Lista de:
(l1 ::: l2).groupBy(_._2).mapValues(_.map(_._1).reduce(_ + _)).toList.map(_.swap)
for ( (k,v) <- (l1++l2).groupBy(_._2).toList ) yield ( v.map(_._1).sum, k )
val a = List(1,1,1,0,0,2)
val b = List(1,0,3,2)
scala> List.concat(a,b)
res31: List[Int] = List(1, 1, 1, 0, 0, 2, 1, 0, 3, 2)
(or)
scala> a.:::(b)
res32: List[Int] = List(1, 0, 3, 2, 1, 1, 1, 0, 0, 2)
(or)
scala> a ::: b
res28: List[Int] = List(1, 1, 1, 0, 0, 2, 1, 0, 3, 2)
val l = l1 ::: l2
val m = Map[String, Int]()
(m /: l) {
case (map, (i, s)) => { map.updated(s, i + (map.get(s) getOrElse 0))}
}.toList // Note: Tuples are reversed.
Pero supongo que hay una forma más elegante de hacer la parte updated
.