2013-04-17 33 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