【问题标题】:Another Scala CanBuildFrom issue: a collection enrichment operator that wraps another of a different type另一个 Scala CanBuildFrom 问题:包装另一个不同类型的集合丰富运算符
【发布时间】:2014-01-11 03:30:16
【问题描述】:

用户 Régis Jean-Gilles 优雅地回答了我之前在使用 CanBuildFrom 和丰富函数(又名“pimp my library”或“enrich my library”)时遇到的问题:

Creating an implicit function that wraps map() in Scala with the right type: Not for the faint at heart

但这一次我遇到了一个更复杂的问题。

我有一个函数可以在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. 如何在 GenTraversableLike 上调用 map
  2. 如何正确调用intersectWith?我知道我必须以某种方式传递一个基于我收到的CanBuildFrom,并且我知道关于 Builders 的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) 你真的应该把它分成两个问题,因为第二个问题 (intersectBy2) 依赖于第一个问题 (intersectWith) 的正确行为。 (2) 如果您为intersectWith 提供输入和所需的输出集合,这将使事情更容易理解,例如如果输入是List(1 -> "foo", 2 -> "bar", 2 -> "baz"),第二个集合和输出是什么。
  • @0__:我不太清楚你为什么要拆分它。 intersectWith 已经可以了。我在问如何让intersectByintersectBy2 工作,intersectBy2 的答案意味着intersectBy 的答案,因为前者更复杂。
  • 缺少intersectWithKey的实现,所以这一步应该是第一步,可能涉及更改精确签名。
  • @0__: intersectWithKey 已经可以使用,带有这个精确的签名。如果需要,请插入 ???作为实施。问题是如何编译它;如果你插入???,它编译正确,然后在运行时抛出“未实现”错误,那么你已经解决了问题。如果你愿意,我会插入实现,但它只会占用更多空间。

标签: scala scala-collections implicit set-intersection enrich-my-library


【解决方案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
  }
}

我不完全确定为什么在 GenTraversableLike 上调用 map 似乎不起作用,但就这样吧。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-04
    • 1970-01-01
    • 2017-06-13
    • 2010-10-03
    • 2011-06-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多