2012-03-15 14 views
5

Me gustaría escribir un método mergeKeys que agrupa los valores en un Iterable[(K, V)] con las teclas. Por ejemplo, podría escribir:Agrupar valores por una clave con cualquier Monoid

def mergeKeysList[K, V](iter: Iterable[(K, V)]) = { 
    iter.foldLeft(Map[K, List[V]]().withDefaultValue(List.empty[V])) { 
     case (map, (k, v)) => 
      map + (k -> (v :: map(k))) 
    } 
    } 

Sin embargo, me gustaría ser capaz de utilizar cualquier Monoid en lugar de escribir un método para List. Por ejemplo, los valores pueden ser enteros y quiero sumarlos en lugar de agregarlos en una lista. O pueden ser tuplas (String, Int) donde quiero acumular las cadenas en un conjunto, pero agrego los enteros. ¿Cómo puedo escribir tal método? ¿O hay algo más que pueda usar en Scalaz para hacer esto?

Actualización: No estaba tan lejos como pensaba. Me acerqué un poco más, pero sigo sin saber cómo hacerlo funcionar si los valores son tuplas. ¿Debo escribir otra conversión implícita? Es decir, ¿una conversión implícita para cada número de parámetros de tipo?

sealed trait SuperTraversable[T, U, F[_]] 
extends scalaz.PimpedType[TraversableOnce[(T, F[U])]] { 
    def mergeKeys(implicit mon: Monoid[F[U]]): Map[T, F[U]] = { 
    value.foldLeft(Map[T, F[U]]().withDefaultValue(mon.zero)) { 
     case (map, (k, v)) => 
     map + (k -> (map(k) |+| v)) 
    } 
    } 
} 

implicit def superTraversable[T, U, F[_]](
    as: TraversableOnce[(T, F[U])] 
): SuperTraversable[T, U, F] = 
    new SuperTraversable[T, U, F] { 
     val value = as 
    } 

Respuesta

6

En primer lugar, si bien no es relevante a su pregunta, usted está limitando generalidad de su código al mencionar explícitamente el constructor de tipo F[_]. Funciona bien sin hacerlo:

sealed trait SuperTraversable[K, V] 
extends scalaz.PimpedType[TraversableOnce[(K, V)]] { 
    def mergeKeys(implicit mon: Monoid[V]): Map[K, V] = { 
     value.foldLeft(Map[K, V]().withDefaultValue(mon.zero)) { 
      case (map, (k, v)) => 
       map + (k -> (map(k) |+| v)) 
     } 
    } 
} 

[...] 

Ahora, por tu pregunta real, no hay necesidad de cambiar mergeKeys para manejar tipos de combinaciones divertidas; simplemente escriba Monoid para manejar cualquier tipo de combinación de que desee hacer. Digamos que quería hacer su Cuerdas + Entrs ejemplo:

implicit def monoidStringInt = new Monoid[(String, Int)] { 
    val zero = ("", 0) 
    def append(a: (String, Int), b: => (String, Int)) = (a, b) match { 
     case ((a1, a2), (b1, b2)) => (a1 + b1, a2 + b2) 
    } 
} 

println { 
    List(
     "a" -> ("Hello, ", 20), 
     "b" -> ("Goodbye, ", 30), 
     "a" -> ("World", 12) 
    ).mergeKeys 
} 

da

Map(a -> (Hello, World,32), b -> (Goodbye, ,30)) 
+0

Perfecto, gracias! – schmmd