Еще одна проблема Scala CanBuildFrom: оператор обогащения коллекции, который оборачивает другой объект другого типа

Пользователь Régis Jean-Gilles изящно ответил на мой предыдущий вопрос, где я боролся с CanBuildFrom и функциями обогащения (иначе говоря, "pimp my library" или "enrich my library"):

Создание неявной функции, которая оборачивает map() в Scala с правильным типом: не для слабонервных

Но на этот раз у меня возникла еще более сложная проблема.

У меня есть функция для реализации вариантов intersectWith, для пересекающихся коллекций по их ключам. Мне удалось заставить их работать как правильные функции сбора:

implicit class IntersectUnionWithPimp[K, A, Repr](a: GenTraversableLike[(K, A), Repr]) {

  /**
   * Intersect two collections by their keys. This is identical to
   * `intersectWith` except that the combiner function is passed the
   * key as well as the two items to combine.
   *
   * @param b Other collection to intersect with.
   * @param combine Function to combine values from the two collections.
   */
  def intersectWithKey[B, R, That](b: GenTraversable[(K, B)])(
      combine: (K, A, B) => R)(
      implicit bf: CanBuildFrom[Repr, (K, R), That]): That = {
    ...
  }

  /**
   * Intersect two collections by their keys. Keep the ordering of
   * objects in the first collection. Use a combiner function to
   * combine items in common. If either item is a multi-map, then
   * for a key seen `n` times in the first collection and `m`
   * times in the second collection, it will occur `n * m` times
   * in the resulting collection, including all the possible
   * combinations of pairs of identical keys in the two collections.
   *
   * @param b Other collection to intersect with.
   * @param combine Function to combine values from the two collections.
   */
  def intersectWith[B, R, That](b: GenTraversable[(K, B)])(
      combine: (A, B) => R)(
      implicit bf: CanBuildFrom[Repr, (K, R), That]): That =
    a.intersectWithKey(b){ case (_, x, y) => combine(x, y) }(bf)
}

Теперь у меня в настоящее время также нетCanBuildFrom версии intersectBy и друзья, и это я не могу работать как CanBuildFrom версии.

implicit class IntersectUnionByPimp[A](a: Traversable[A]) {
  /**
   * Intersect two collections by their keys, with separate key-selection
   * functions for the two collections. This is identical to
   * `intersectBy` except that each collection has its own key-selection
   * function. This allows the types of the two collections to be
   * distinct, with no common parent.
   *
   * @param b Other collection to intersect with.
   * @param key1fn Function to select the comparison key for the first
   *   collection.
   * @param key1fn Function to select the comparison key for the first
   *   collection.
   * @param combine Function to combine values from the two collections.
   */
  def intersectBy2[K, B, R](b: Traversable[B])(key1fn: A => K
      )(key2fn: B => K)(combine: (A, B) => R): Traversable[R] = {
    val keyed_a = a.map { x => (key1fn(x), x) }
    val keyed_b = b.map { x => (key2fn(x), x) }
    keyed_a.intersectWith(keyed_b)(combine).map(_._2)
  }

  /**
   * Intersect two collections by their keys. Keep the ordering of
   * objects in the first collection. Use a combiner function to
   * combine items in common. If either item is a multi-map, then
   * for a key seen `n` times in the first collection and `m`
   * times in the second collection, it will occur `n * m` times
   * in the resulting collection, including all the possible
   * combinations of pairs of identical keys in the two collections.
   *
   * @param b Other collection to intersect with.
   * @param keyfn Function to select the comparison key.
   * @param combine Function to combine values from the two collections.
   */
  def intersectBy[K, B >: A](b: Traversable[B])(keyfn: B => K)(
      combine: (A, B) => B): Traversable[B] = {
    val keyed_a = a.map { x => (keyfn(x), x) }
    val keyed_b = b.map { x => (keyfn(x), x) }
    keyed_a.intersectWith(keyed_b)(combine).map(_._2)
  }
}

Лучшая версия, которую я могу придумать, такова:

implicit class IntersectUnionByPimp[A, Repr](a: GenTraversableLike[A, Repr]) {
  def intersectBy2[K, B, R, That](b: Traversable[B])(key1fn: A => K)(
      key2fn: B => K)(combine: (A, B) => R)(
      implicit bf: CanBuildFrom[Repr, R, That]): That = {
    // FIXME! How to make this work while calling `map`?
    // val keyed_a = a.map { x => (key1fn(x), x) }
    val keyed_a = mutable.Buffer[(K, A)]()
    a.foreach { x => keyed_a += ((key1fn(x), x)) }
    val keyed_b = b.map { x => (key2fn(x), x) }
    keyed_a.intersectWith(keyed_b)(combine).map(_._2)
  }

  def intersectBy[K, B >: A, That](b: Traversable[B])(keyfn: B => K)(
      combine: (A, B) => B)(
      implicit bf: CanBuildFrom[Repr, B, That]): That = {
    // FIXME! How to make this work while calling `map`?
    // val keyed_a = a.map { x => (keyfn(x), x) }
    val keyed_a = mutable.Buffer[(K, A)]()
    a.foreach { x => keyed_a += ((keyfn(x), x)) }
    val keyed_b = b.map { x => (keyfn(x), x) }
    keyed_a.intersectWith(keyed_b)(combine).map(_._2)
}

Во-первых, я не понимаю, почему мне нужно переписать вызов map что производит keyed_a с изменяемым буфером; Кажется, должен быть лучший путь. Но я все еще получаю такую ​​же ошибку в нижней строке:

[error] /Users/benwing/devel/textgrounder/src/main/scala/opennlp/textgrounder/util/collection.scala:1018: type mismatch;
[error]  found   : scala.collection.mutable.Buffer[R]
[error]  required: That
[error]  Note: implicit method bufferToIndexedSeq is not applicable here because it comes after the application point and it lacks an explicit result type
[error]       keyed_a.intersectWith(keyed_b)(combine).map(_._2)
[error]                                                  ^

Итак, мои вопросы:

  1. Как позвонить map на GenTraversableLike?
  2. Как позвонить intersectWith работать правильно? Я знаю, что должен как-то передать CanBuildFrom на основании того, что я получил, и я знаю о mapResult на строителей, но я не уверен, что делать здесь, или если это вообще возможно.

Пример intersectBy, который пересекает списки чисел с плавающей точкой, обрабатывая два числа одинаково, если их неотъемлемая часть одинакова, и вычисляя абсолютную разницу:

scala> List(4.5,2.3,4.2).intersectBy(List(4.6,4.8))(_.toInt){ case (a,b) => (a - b).abs }
res2: Traversable[Double] = List(0.09999999999999964, 0.2999999999999998, 0.39999999999999947, 0.5999999999999996)

(за исключением того, что возвращаемый тип должен быть List[Double])

Спасибо за любую помощь.

1 ответ

Решение

Хорошо, оказывается, мне нужно создать конструктор, чтобы вернуть предметы, а не пытаться вернуть их напрямую. Следующие работы:

implicit class IntersectUnionByPimp[A, Repr](a: GenTraversableLike[A, Repr]) {
  /**
   * Intersect two collections by their keys, with separate key-selection
   * functions for the two collections. This is identical to
   * `intersectBy` except that each collection has its own key-selection
   * function. This allows the types of the two collections to be
   * distinct, with no common parent.
   *
   * @param b Other collection to intersect with.
   * @param key1fn Function to select the comparison key for the first
   *   collection.
   * @param key2fn Function to select the comparison key for the first
   *   collection.
   * @param combine Function to combine values from the two collections.
   */
  def intersectBy2[K, B, R, That](b: GenTraversable[B])(key1fn: A => K)(
      key2fn: B => K)(combine: (A, B) => R)(
      implicit bf: CanBuildFrom[Repr, R, That]): That = {
    // It appears we can't call map() on `a`.
    val keyed_a = mutable.Buffer[(K, A)]()
    a.foreach { x => keyed_a += ((key1fn(x), x)) }
    val keyed_b = b.map { x => (key2fn(x), x) }
    // Nor can we return the value of map() here. Need to use a builder
    // instead.
    val bu = bf()
    for ((_, r) <- keyed_a.intersectWith(keyed_b)(combine))
      bu += r
    bu.result
  }

  /**
   * Intersect two collections by their keys. Keep the ordering of
   * objects in the first collection. Use a combiner function to
   * combine items in common. If either item is a multi-map, then
   * for a key seen `n` times in the first collection and `m`
   * times in the second collection, it will occur `n * m` times
   * in the resulting collection, including all the possible
   * combinations of pairs of identical keys in the two collections.
   *
   * @param b Other collection to intersect with.
   * @param keyfn Function to select the comparison key.
   * @param combine Function to combine values from the two collections.
   */
  def intersectBy[K, B >: A, That](b: GenTraversable[B])(keyfn: B => K)(
      combine: (A, B) => B)(
      implicit bf: CanBuildFrom[Repr, B, That]): That = {
    val keyed_a = mutable.Buffer[(K, A)]()
    a.foreach { x => keyed_a += ((keyfn(x), x)) }
    val keyed_b = b.map { x => (keyfn(x), x) }
    val bu = bf()
    for ((_, r) <- keyed_a.intersectWith(keyed_b)(combine))
      bu += r
    bu.result
  }
}

Я не совсем уверен, почему просто звоню map на GenTraversableLike не похоже на работу, но так и будет.

Другие вопросы по тегам