使用任何Monoid的键对值进行分组

时间:2012-03-15 03:04:30

标签: scala monads scalaz monoids

我想编写一个方法mergeKeys,用键将Iterable[(K, V)]中的值分组。例如,我可以写:

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

但是,我希望能够使用任何Monoid而不是为List编写方法。例如,值可能是整数,我想要对它们求和,而不是将它们附加到列表中。或者它们可能是元组(String, Int),我想在集合中累积字符串但添加整数。我怎么写这样的方法?或者我可以在scalaz中使用其他东西来完成这项工作吗?

更新:我没有想象的那么遥远。我有点接近,但如果值是元组,我仍然不知道如何使它工作。我是否需要编写另一个隐式转换?即,每个类型参数的隐式转换是什么?

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
    }

1 个答案:

答案 0 :(得分:6)

首先,虽然它与您的问题无关,但您限制了代码 明确提到类型构造函数F[_]的一般性。它工作正常 没有这样做:

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

[...]

现在,对于您的实际问题,无需更改mergeKeys来处理 有趣的组合;只需写一个Monoid来处理任何类型的问题 结合你想做的事。假设你想做你的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
}

给出

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