如何在Scala中编写一组自定义整数?具体来说,我想要一个具有以下属性的类:
Set
特征。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语言中已有的功能。
我已经要求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版本中有很多不透明的代码。 SetLike
,newBuilder
和canBuildFrom
都是语言样板:它们与使用花括号编写集合或采用均值无关。我几乎可以接受它们作为你为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中的设计缺陷还是我没有看到的设计权衡。
答案 0 :(得分:2)
除了您可以使用隐式类和值类添加的mean
作为扩展方法之外,您列出的所有属性都应该由标准库中的immutable.BitSet
类支持。也许您可以在该实现中找到一些提示,尤其是出于效率目的。
你编写了很多代码来实现上面的委托,但你可以使用类继承来实现类似的东西 - 请注意,编写自定义集的委托版本在Java中也需要更多的样板。
也许宏将来允许你编写自动生成委托样板的代码 - 在那之前,有一个旧的AutoProxy编译器插件。