你如何在Scala中扩展一组整数?

时间:2013-04-17 19:38:35

标签: scala inheritance set builder

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

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

    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)要求正确编写构建器代码。 (4)确保构造函数可以定制。 (5)是如何覆盖现有toString实现的示例。 (6)是如何添加新功能的示例。

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

    我已经要求couple questions了解任务的各个方面,但我认为这个涵盖了整个问题。到目前为止,我得到的最好的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定义的情况下编写此代码,或者我能做的最好?< / p>


    按照下面axel22的建议,我写了一个简单的实现,利用了非常有用的,如果不幸的是pimp我的库模式。

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

    使用此功能,您只需实例化Set而不是CustomSet,然后根据需要进行隐式转换以获取均值。

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

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


    在下面的评论中说axel22的内容是我问这个问题的核心原因。

      

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

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

1 个答案:

答案 0 :(得分:2)

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

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

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