1

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) ビルダー コードが正しく記述されている必要があります。(4) コンストラクターをカスタマイズできるようにします。toString(5) は、既存の実装をオーバーライドする方法の例です。(6) は、新しい機能を追加する方法の例です。

これは、Scala 言語に既に存在する機能を可能な限り利用して、最小限のソース コードとボイラープレートで行う必要があります。

タスクの側面についていくつか 質問しましたが、これで問題全体がカバーされていると思います。私がこれまでに得た最良の回答SetProxyは、 を使用することです。これは役に立ちますが、上記の (3) で失敗します。私は『Programming in Scala 』の第 2 版の「The Architecture of 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 のセールス ポイントの 1 つは、Java よりも簡潔でクリーンであることです。

Scala バージョンには不透明なコードがたくさんあります。SetLikenewBuilder、およびcanBuildFromはすべて言語定型文です。これらは、中括弧を使用してセットを記述したり、平均を取ることとは関係ありません。Scala の不変コレクション クラス ライブラリの代償として、私はほとんど受け入れることができます (今のところ、不変性を修飾されていないものとして受け入れていcontainsます) 。それらは、少なくとも getter および setter 関数と同じくらい醜いです。+-iterator

Scala はインターフェイスのボイラープレートを書かない方法を提供する必要があるようですSetが、私にはわかりません。抽象クラスの代わりに具象クラスを使用SetProxyして拡張しようとしましたが、どちらも複雑なコンパイラエラーを引き起こしました。HashSetSet

contains+-、および定義なしでこのコードを記述する方法はありiteratorますか、または上記が私ができる最善の方法ですか?


以下のaxel22のアドバイスに従って、残念ながら pimp my ライブラリ パターンという名前の非常に便利なものを利用する簡単な実装を書きました。

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これにより、 s の代わりにs をインスタンス化CustomSetし、必要に応じて暗黙的な変換を行って平均を取得します。

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

これは私の最初のウィッシュ リストのほとんどを満たしますが、項目 (5) はまだ失敗しています。


以下のコメントでaxel22言ったことは、私がこの質問をしている理由の核心になります。

継承に関しては、イミュータブル (コレクション) クラスは一般に継承が容易ではありません…</p>

これは私の経験と一致しますが、言語設計の観点からは、ここで何かがおかしいようです。Scala はオブジェクト指向言語です。(昨年、Martin Odersky が講演するのを見たとき、それが彼が強調した Haskell のセールス ポイントでした。) 不変性は、明示的に好まれる動作モードです。Scala のコレクション クラスは、Scala のオブジェクト ライブラリの至宝としてもてはやされています。それでも、不変のコレクション クラスを拡張したい場合は、「そうしないでください」または「自分が何をしているのか本当にわからない限り、試してはいけません」という非公式の知識に出くわします。通常、クラスのポイントは、簡単に拡張できるようにすることです。(結局のところ、コレクション クラスは とマークされていませんfinal。) I'

4

1 に答える 1

2

暗黙mean的なクラスと値のクラスを使用して拡張メソッドとして追加できるものは別として、リストするすべてのプロパティimmutable.BitSetは、標準ライブラリのクラスでサポートされている必要があります。おそらく、特に効率化のために、その実装でいくつかのヒントを見つけることができます。

上記の委譲を実現するために多くのコードを書きましたが、Java の場合と同様にクラス継承を使用して同様のことを実現できます。カスタム セットの委譲バージョンを作成するには、Java でもより多くのボイラープレートが必要になることに注意してください。

おそらくマクロを使用すると、委任定型文を自動的に生成するコードを記述できるようになるでしょう。それまでは、古いAutoProxyコンパイラ プラグインが存在します。

于 2013-04-21T17:54:05.773 に答える