2013-04-17 27 views
1

如何在Scala中编写自定义整数集?具体而言,我想要一个具有以下属性的类:如何在Scala中扩展一组Integer?

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

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

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

我这样做的目标是写一篇博客文章,比较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版本中有很多不透明的代码。 SetLike,newBuildercanBuildFrom都是语言样板文件:它们与用大括号编写套件或采取一种方法无关。我几乎可以接受它们作为您为Scala的不可变收藏类库支付的价格(目前接受不变性作为不合格产品),但是仍留下contains,+,-iterator,这些只是样板直通代码。 它们至少和getter和setter函数一样丑陋。

看来斯卡拉应该提供一种不写Set接口样板的方法,但我无法弄清楚。我尝试使用SetProxy和扩展具体HashSet类而不是摘要Set,但这两个都给编译器错误编写错误。

有没有写这个代码不contains+-,并且iterator定义的方式,或者是上面的我能做到的最好?


axel22建议下面我写了一个简单的实现,它利用了非常有用的,如果不幸名为皮条客我的图书馆模式。

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 S和需要采取的意思是隐式转换。

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

这满足了我大部分原来的愿望清单,但仍然失败了项目(5)。


东西axel22在评论中说,下面得到的,为什么我问这个问题的心脏。

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

这广场凭我的经验,但是从语言设计的角度有什么不对劲这里。 Scala是一种面向对象的语言。 (当我看到马丁奥德斯基去年发表演讲时,这是他突出强调的哈斯克尔的卖点。)不变性是明确的首选操作模式。斯卡拉的收藏类被吹捧为其对象库的王冠之宝。然而,当你想扩展一个不可变的集合类时,你会遇到所有这种非正式的传说,以“不要那样做”或“除非你知道你在做什么真的”。通常类的要点是使它们易于扩展。 (毕竟,集合类没有标记为final。)我正在试图确定这是Scala中的设计缺陷还是我没有看到的设计权衡。

+0

[Extend Scala Set with concrete type](http:// stackoverflow。com/questions/4416885/extend-scala-set-with-concrete-type) – Suma

回答

2

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

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

也许宏将在未来允许您编写自动生成委托样板文件的代码 - 在此之前,旧版本为AutoProxy编译器插件。

+0

感谢指向'BitSet'的指针。这是编写集合类的有用模型,但它实现了'+'和'-',这是我试图避免在这里做的事情,因为Scala已经为'Set'实现了这些操作。 –

+0

措辞问题的另一种方法我关闭了我原来的帖子,是“我可以用继承而不是委托来做这件事吗?”我一直无法弄清楚如何。隐式类看起来很有希望。我以前没有听说过他们,但我将升级到Scala 2.10并观看。 –

+0

您并不需要使用隐式类和值类 - 具有隐式转换的扩展方法模式以及具有该方法的包装器已足够(但会产生对象分配开销)。至于继承,不可变(集合)类通常不容易继承 - 问题在于它们通常使用多于一个实现类,并且您需要继承所有这些类并覆盖所有创建新对象的方法。至于mutable,你可以继承'HashSet'和'SetLike'接口,并覆盖'newCombiner'。应该管用。 – axel22