Группировать значения по ключу с любым моноидом - PullRequest
5 голосов
/ 15 марта 2012

Я хотел бы написать метод 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 Ответ

6 голосов
/ 15 марта 2012

Во-первых, хотя это не относится к вашему вопросу, вы ограничиваете общность кода, явно упоминая конструктор типа 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))
...