Group values ​​by key with any monoid

I would like to write a method mergeKeysthat groups values ​​into Iterable[(K, V)]keys. For example, I could write:

  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)))
     }
  }

However, I would like to use any Monoidinstead of writing a method for List. For example, the values ​​may be integers, and I want to summarize them, and not add them to the list. Or they can be tuples (String, Int)where I want to accumulate rows in a set, but add integers. How can I write such a method? Or is there something else I can use in scalaz to do this?

Update: I was not as far as I thought. I came up a bit, but I still don't know how to make it work if the values ​​are tuples. Do I need to write another implicit conversion? Ie, one implicit conversion for each number of type parameters?

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
    }
+5
source share
1 answer

First, although this does not apply to your question, you restrict your generality code by explicitly specifying a type constructor F[_]. It works fine without this:

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))
        }
    }
}

[...]

Now, for your actual question, there is no need to change the mergeKeysfun kinds of combinations to handle; just write Monoidto handle any types you want to do. Suppose you wanted to make an example Strings + Ints:

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
}

gives

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

All Articles