【问题标题】:How do you extend a set of Integers in Scala?如何在 Scala 中扩展一组整数?
【发布时间】:2013-04-17 19:38:34
【问题描述】:

如何在 Scala 中编写自定义整数集?具体来说,我想要一个具有以下属性的类:

  1. 它是不可变的。
  2. 它扩展了 Set 特征。
  3. 所有收集操作都会根据需要返回此类型的另一个对象。
  4. 它在其构造函数中采用整数参数的变量列表。
  5. 它的字符串表示形式是用大括号括起来的以逗号分隔的元素列表。
  6. 它定义了一个方法mean,它返回元素的平均值。

例如:

CustomIntSet(1,2,3) & CustomIntSet(2,3,4) // returns CustomIntSet(2, 3)
CustomIntSet(1,2,3).toString // returns {1, 2, 3}
CustomIntSet(2,3).mean // returns 2.5

(1) 和 (2) 确保该对象以正确的 Scala 方式执行操作。 (3) 要求正确编写builder代码。 (4)确保构造函数可以自定义。 (5) 是如何覆盖现有toString 实现的示例。 (6) 是如何添加新功能的示例。

这应该使用最少的源代码和样板来完成,尽可能利用 Scala 语言中已经存在的功能。

我问过couplequestions 了解任务的各个方面,但我认为这涵盖了整个问题。到目前为止,我得到的最好的response 是使用SetProxy,这很有帮助,但上面的(3)失败了。我已经广泛研究了Scala 编程第二版中的“Scala 集合的体系结构”一章,并查阅了各种在线示例,但仍然一头雾水。

我这样做的目标是写一篇博文,比较 Scala 和 Java 解决这个问题的方式的设计权衡,但在我这样做之前,我必须实际编写 Scala 代码。我不认为这会那么困难,但确实如此,而且我承认失败了。


折腾了几天后,我想出了以下解决方案。

package example

import scala.collection.{SetLike, mutable}
import scala.collection.immutable.HashSet
import scala.collection.generic.CanBuildFrom

case class CustomSet(self: Set[Int] = new HashSet[Int].empty) extends Set[Int] with SetLike[Int, CustomSet] {
  lazy val mean: Float = sum / size

  override def toString() = mkString("{", ",", "}")

  protected[this] override def newBuilder = CustomSet.newBuilder

  override def empty = CustomSet.empty

  def contains(elem: Int) = self.contains(elem)

  def +(elem: Int) = CustomSet(self + elem)

  def -(elem: Int) = CustomSet(self - elem)

  def iterator = self.iterator
}

object CustomSet {
  def apply(values: Int*): CustomSet = new CustomSet ++ values

  def empty = new CustomSet

  def newBuilder: mutable.Builder[Int, CustomSet] = new mutable.SetBuilder[Int, CustomSet](empty)

  implicit def canBuildFrom: CanBuildFrom[CustomSet, Int, CustomSet] = new CanBuildFrom[CustomSet, Int, CustomSet] {
    def apply(from: CustomSet) = newBuilder

    def apply() = newBuilder
  }

  def main(args: Array[String]) {
    val s = CustomSet(2, 3, 5, 7) & CustomSet(5, 7, 11, 13)
    println(s + " has mean " + s.mean)
  }
}

这似乎符合上述所有标准,但它有很多样板。我发现下面的 Java 版本更容易理解。

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;

public class CustomSet extends HashSet<Integer> {
    public CustomSet(Integer... elements) {
        Collections.addAll(this, elements);
    }

    public float mean() {
        int s = 0;
        for (int i : this)
            s += i;
        return (float) s / size();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (Iterator<Integer> i = iterator(); i.hasNext(); ) {
            sb.append(i.next());
            if (i.hasNext())
                sb.append(", ");
        }
        return "{" + sb + "}";
    }

    public static void main(String[] args) {
        CustomSet s1 = new CustomSet(2, 3, 5, 7, 11);
        CustomSet s2 = new CustomSet(5, 7, 11, 13, 17);

        s1.retainAll(s2);

        System.out.println("The intersection " + s1 + " has mean " + s1.mean());
    }
}

这很糟糕,因为 Scala 的卖点之一是它比 Java 更简洁。

Scala 版本中有很多不透明的代码。 SetLikenewBuildercanBuildFrom 都是语言样板:它们与用花括号编写集合或取平均值无关。我几乎可以接受它们作为你为 Scala 的不可变集合类库支付的价格(目前接受不可变作为一种不合格的商品),但仍然留下 contains+-iterator只是样板直通代码。 它们至少和 getter 和 setter 函数一样丑陋。

似乎 Scala 应该提供一种不编写 Set 接口样板的方法,但我想不通。我尝试使用SetProxy 和扩展具体的HashSet 类而不是抽象的Set,但这两者都产生了令人困惑的编译器错误。

有没有办法在没有contains+-iterator 定义的情况下编写此代码,或者以上是我能做的最好的吗?


按照下面axel22 的建议,我编写了一个简单的实现,它利用了非常有用的如果不幸命名为 pimp my library 模式。

package example

class CustomSet(s: Set[Int]) {
  lazy val mean: Float = s.sum / s.size
}

object CustomSet {
  implicit def setToCustomSet(s: Set[Int]) = new CustomSet(s)
}

这样,您只需实例化 Sets 而不是 CustomSets 并根据需要进行隐式转换以取平均值。

scala> (Set(1,2,3) & Set(2,3,5)).mean
res4: Float = 2.0

这满足了我最初的愿望清单的大部分内容,但仍然未能通过第 (5) 项。


axel22 在下面的 cmets 中所说的话正是我问这个问题的核心。

至于继承,不可变(集合)类不容易 一般继承...

这符合我的经验,但从语言设计的角度来看,这里似乎有些问题。 Scala 是一种面向对象的语言。 (当我去年看到 Martin Odersky 发表演讲时,这是他强调的 Haskell 的卖点。)不变性是明确首选的操作模式。 Scala 的集合类被吹捧为其对象库的皇冠上的明珠。然而,当你想扩展一个不可变的集合类时,你会遇到所有这些非正式的传说,大意是“不要那样做”或“除非你真的知道你在做什么,否则不要尝试它。正在做。”通常类的目的是使它们易于扩展。 (毕竟,集合类没有标记为final。)我正在尝试确定这是 Scala 中的设计缺陷还是我没有看到的设计权衡。

【问题讨论】:

标签: scala inheritance set builder


【解决方案1】:

除了可以使用隐式类和值类作为扩展方法添加的mean 之外,标准库中的immutable.BitSet 类应该支持您列出的所有属性。也许您可以在该实现中找到一些提示,特别是出于效率目的。

您编写了很多代码来实现上面的委托,但是您可以使用 Java 中的类继承来实现类似的事情 - 请注意,编写自定义集的委托版本也需要更多的 Java 样板。

也许将来宏会允许您编写自动生成委托样板的代码——在那之前,有旧的AutoProxy 编译器插件。

【讨论】:

  • 感谢指向BitSet 的指针。这是编写集合类的有用模型,但它实现了+-,这是我试图避免在这里做的事情,因为Scala 已经为Set 实现了这些操作。
  • 另一种表达我关闭原始帖子的问题的方式是“我可以通过继承而不是委托来做到这一点吗?”我一直无法弄清楚如何。隐式类似乎很有希望。我以前没听说过,不过我打算升级到 Scala 2.10 看看。
  • 您实际上不必使用隐式类和值类——具有隐式转换和具有该方法的包装器的扩展方法模式就足够了(但您会产生对象分配的开销) .至于继承,不可变(集合)类通常不容易继承——问题是它们通常使用多个实现类,您需要继承所有这些类并覆盖所有创建新对象的方法。至于可变的,你可以继承HashSetSetLike接口,并覆盖newCombiner。应该工作。
  • “扩展方法模式”和“pimp my library”一样吗?
  • 是的,模式是一样的。
猜你喜欢
  • 1970-01-01
  • 2018-11-19
  • 2012-03-13
  • 1970-01-01
  • 2011-06-29
  • 1970-01-01
  • 2016-02-28
  • 1970-01-01
  • 2020-02-21
相关资源
最近更新 更多