Scalaで利用可能な最も強力なパターンの1つは、enrich-my-library *パターンです。これは、appearへの暗黙的な変換を使用してメソッドを追加します。動的なメソッド解決を必要としない既存のクラス。たとえば、すべての文字列に、空白文字の数をカウントするメソッドspaces
が必要な場合は、次のことができます。
class SpaceCounter(s: String) {
def spaces = s.count(_.isWhitespace)
}
implicit def string_counts_spaces(s: String) = new SpaceCounter(s)
scala> "How many spaces do I have?".spaces
res1: Int = 5
残念ながら、このパターンは、ジェネリックコレクションを処理するときに問題が発生します。たとえば、 アイテムをコレクションで順番にグループ化する について多くの質問がありました。ワンショットで機能するものは組み込まれていないため、これは、ジェネリックコレクションC
とジェネリック要素タイプA
を使用したenrich-my-libraryパターンの理想的な候補のようです。
class SequentiallyGroupingCollection[A, C[A] <: Seq[A]](ca: C[A]) {
def groupIdentical: C[C[A]] = {
if (ca.isEmpty) C.empty[C[A]]
else {
val first = ca.head
val (same,rest) = ca.span(_ == first)
same +: (new SequentiallyGroupingCollection(rest)).groupIdentical
}
}
}
もちろん、それが機能しないことを除いて。 REPLは次のことを示しています:
<console>:12: error: not found: value C
if (ca.isEmpty) C.empty[C[A]]
^
<console>:16: error: type mismatch;
found : Seq[Seq[A]]
required: C[C[A]]
same +: (new SequentiallyGroupingCollection(rest)).groupIdentical
^
2つの問題があります。空のC[C[A]]
リストから(または薄い空気から)C[A]
を取得するにはどうすればよいですか?そして、C[C[A]]
の代わりにsame +:
行からSeq[Seq[A]]
を取得するにはどうすればよいですか?
* 以前はpimp-my-libraryとして知られていました。
この問題を理解するための鍵は、コレクションライブラリにコレクションを構築して操作する2つの異なる方法があることを理解することです。 1つは、すべてのNiceメソッドとのパブリックコレクションインターフェイスです。 creatingコレクションライブラリで広く使用されているが、それ以外ではほとんど使用されていないもう1つは、ビルダーです。
エンリッチメントの問題は、同じタイプのコレクションを返そうとしたときにコレクションライブラリ自体が直面する問題とまったく同じです。つまり、コレクションを作成したいのですが、一般的に作業する場合、「コレクションと同じタイプ」を参照する方法がありません。したがって、ビルダーが必要です。
ここで問題となるのは、ビルダーをどこから入手するかということです。明らかな場所はコレクション自体からです。 これは機能しません。ジェネリックコレクションに移行する際に、コレクションのタイプを忘れることをすでに決定しました。したがって、コレクションが必要なタイプのコレクションをさらに生成するビルダーを返すことができたとしても、タイプが何であるかはわかりません。
代わりに、浮かんでいるCanBuildFrom
暗黙からビルダーを取得します。これらは、特に入力タイプと出力タイプを照合し、適切にタイプされたビルダーを提供する目的で存在します。
したがって、2つの概念的な飛躍があります。
CanBuildFrom
sから取得します。例を見てみましょう。
class GroupingCollection[A, C[A] <: Iterable[A]](ca: C[A]) {
import collection.generic.CanBuildFrom
def groupedWhile(p: (A,A) => Boolean)(
implicit cbfcc: CanBuildFrom[C[A],C[A],C[C[A]]], cbfc: CanBuildFrom[C[A],A,C[A]]
): C[C[A]] = {
val it = ca.iterator
val cca = cbfcc()
if (!it.hasNext) cca.result
else {
val as = cbfc()
var olda = it.next
as += olda
while (it.hasNext) {
val a = it.next
if (p(olda,a)) as += a
else { cca += as.result; as.clear; as += a }
olda = a
}
cca += as.result
}
cca.result
}
}
implicit def iterable_has_grouping[A, C[A] <: Iterable[A]](ca: C[A]) = {
new GroupingCollection[A,C](ca)
}
これを分解しましょう。まず、コレクションのコレクションを作成するには、グループごとにC[A]
、すべてのグループをまとめるC[C[A]]
の2種類のコレクションを作成する必要があることがわかっています。したがって、2つのビルダーが必要です。1つはA
sを取得してC[A]
sをビルドし、もう1つはC[A]
sを取得してC[C[A]]
sをビルドします。 CanBuildFrom
の型シグネチャを見ると、
CanBuildFrom[-From, -Elem, +To]
つまり、CanBuildFromは、開始しているコレクションのタイプを知りたいということです。この場合は、C[A]
であり、生成されたコレクションの要素とそのコレクションのタイプです。したがって、これらを暗黙的なパラメータcbfcc
およびcbfc
として入力します。
これを実現したので、それがほとんどの作業です。 CanBuildFrom
sを使用してビルダーを提供できます(必要なのはそれらを適用することだけです)。そして、あるビルダーは+=
でコレクションを構築し、それを最終的にresult
であるはずのコレクションに変換し、それ自体を空にしてclear
で再開する準備をすることができます。 。ビルダーは空から開始します。これにより、最初のコンパイルエラーが解決されます。再帰の代わりにビルダーを使用しているため、2番目のエラーもなくなります。
最後の小さな詳細(実際に作業を行うアルゴリズム以外)は、暗黙の変換にあります。 new GroupingCollection[A,C]
ではなく[A,C[A]]
を使用することに注意してください。これは、クラス宣言が1つのパラメーターを持つC
用であり、渡されたA
でそれ自体を埋めるためです。したがって、タイプC
を渡して、それからC[A]
を作成します。細かい部分ですが、別の方法を試すとコンパイル時エラーが発生します。
ここでは、メソッドを「等しい要素」コレクションよりも少し一般的にしました。むしろ、このメソッドは、順次要素のテストが失敗するたびに元のコレクションを切り離します。
実際のメソッドを見てみましょう。
scala> List(1,2,2,2,3,4,4,4,5,5,1,1,1,2).groupedWhile(_ == _)
res0: List[List[Int]] = List(List(1), List(2, 2, 2), List(3), List(4, 4, 4),
List(5, 5), List(1, 1, 1), List(2))
scala> Vector(1,2,3,4,1,2,3,1,2,1).groupedWhile(_ < _)
res1: scala.collection.immutable.Vector[scala.collection.immutable.Vector[Int]] =
Vector(Vector(1, 2, 3, 4), Vector(1, 2, 3), Vector(1, 2), Vector(1))
できます!
唯一の問題は、これらのメソッドを配列に使用できないことです。これは、2つの暗黙的な変換を連続して行う必要があるためです。これを回避するには、配列の個別の暗黙的な変換を作成する、WrappedArray
にキャストするなど、いくつかの方法があります。
編集:配列や文字列などを処理するための私の好ましいアプローチは、コードをさらにmore汎用にし、適切な暗黙の変換を使用して、配列も機能するようにそれらをより具体的にすることです。この特定のケースでは:
class GroupingCollection[A, C, D[C]](ca: C)(
implicit c2i: C => Iterable[A],
cbf: CanBuildFrom[C,C,D[C]],
cbfi: CanBuildFrom[C,A,C]
) {
def groupedWhile(p: (A,A) => Boolean): D[C] = {
val it = c2i(ca).iterator
val cca = cbf()
if (!it.hasNext) cca.result
else {
val as = cbfi()
var olda = it.next
as += olda
while (it.hasNext) {
val a = it.next
if (p(olda,a)) as += a
else { cca += as.result; as.clear; as += a }
olda = a
}
cca += as.result
}
cca.result
}
}
ここに、C
からIterable[A]
を与える暗黙的なものを追加しました。ほとんどのコレクションでは、これは単なるIDになります(たとえば、List[A]
はすでにIterable[A]
です) 、ただし、配列の場合は、実際の暗黙的な変換になります。その結果、C[A] <: Iterable[A]
--の要件を削除しました。基本的には<%
の要件を明示的にしたため、コンパイラに入力させる代わりに、自由に明示的に使用できます。わたしたちのため。また、コレクションのコレクションがC[C[A]]
であるという制限を緩和しました。代わりに、D[C]
であり、後で必要なものとして入力します。これは後で入力するので、メソッドレベルではなくクラスレベルにプッシュしました。それ以外は基本的に同じです。
ここで問題は、これをどのように使用するかです。通常のコレクションの場合、次のことができます。
implicit def collections_have_grouping[A, C[A]](ca: C[A])(
implicit c2i: C[A] => Iterable[A],
cbf: CanBuildFrom[C[A],C[A],C[C[A]]],
cbfi: CanBuildFrom[C[A],A,C[A]]
) = {
new GroupingCollection[A,C[A],C](ca)(c2i, cbf, cbfi)
}
ここで、C
の場合はC[A]
を、C[C[A]]
の場合はD[C]
をプラグインします。 new GroupingCollection
の呼び出しで明示的なジェネリック型が必要であることに注意してください。これにより、どの型が何に対応するかをまっすぐに保つことができます。 implicit c2i: C[A] => Iterable[A]
のおかげで、これは自動的に配列を処理します。
しかし、待ってください。文字列を使用したい場合はどうでしょうか。 「文字列の文字列」を作成できないため、問題が発生しています。これは、追加の抽象化が役立つところです。文字列を保持するのに適したD
を呼び出すことができます。 Vector
を選び、次のようにします。
val vector_string_builder = (
new CanBuildFrom[String, String, Vector[String]] {
def apply() = Vector.newBuilder[String]
def apply(from: String) = this.apply()
}
)
implicit def strings_have_grouping(s: String)(
implicit c2i: String => Iterable[Char],
cbfi: CanBuildFrom[String,Char,String]
) = {
new GroupingCollection[Char,String,Vector](s)(
c2i, vector_string_builder, cbfi
)
}
文字列のベクトルの構築を処理するには、新しいCanBuildFrom
が必要です(ただし、Vector.newBuilder[String]
を呼び出すだけなので、これは非常に簡単です)。次に、すべてのタイプを入力する必要があります。 GroupingCollection
が適切に入力されていること。すでに[String,Char,String]
CanBuildFromの周りに浮かんでいるので、文字のコレクションから文字列を作成できることに注意してください。
それを試してみましょう:
scala> List(true,false,true,true,true).groupedWhile(_ == _)
res1: List[List[Boolean]] = List(List(true), List(false), List(true, true, true))
scala> Array(1,2,5,3,5,6,7,4,1).groupedWhile(_ <= _)
res2: Array[Array[Int]] = Array(Array(1, 2, 5), Array(3, 5, 6, 7), Array(4), Array(1))
scala> "Hello there!!".groupedWhile(_.isLetter == _.isLetter)
res3: Vector[String] = Vector(Hello, , there, !!)
このコミット の時点で、Rexが優れた回答をしたときよりもScalaコレクションを「強化」する方がはるかに簡単です。単純なケースでは、次のようになります。
import scala.collection.generic.{ CanBuildFrom, FromRepr, HasElem }
import language.implicitConversions
class FilterMapImpl[A, Repr](val r : Repr)(implicit hasElem : HasElem[Repr, A]) {
def filterMap[B, That](f : A => Option[B])
(implicit cbf : CanBuildFrom[Repr, B, That]) : That = r.flatMap(f(_).toSeq)
}
implicit def filterMap[Repr : FromRepr](r : Repr) = new FilterMapImpl(r)
これにより、filterMap
操作に関する「同じ結果タイプ」がすべてのGenTraversableLike
sに追加されます。
scala> val l = List(1, 2, 3, 4, 5)
l: List[Int] = List(1, 2, 3, 4, 5)
scala> l.filterMap(i => if(i % 2 == 0) Some(i) else None)
res0: List[Int] = List(2, 4)
scala> val a = Array(1, 2, 3, 4, 5)
a: Array[Int] = Array(1, 2, 3, 4, 5)
scala> a.filterMap(i => if(i % 2 == 0) Some(i) else None)
res1: Array[Int] = Array(2, 4)
scala> val s = "Hello World"
s: String = Hello World
scala> s.filterMap(c => if(c >= 'A' && c <= 'Z') Some(c) else None)
res2: String = HW
質問の例では、ソリューションは次のようになります。
class GroupIdenticalImpl[A, Repr : FromRepr](val r: Repr)
(implicit hasElem : HasElem[Repr, A]) {
def groupIdentical[That](implicit cbf: CanBuildFrom[Repr,Repr,That]): That = {
val builder = cbf(r)
def group(r: Repr) : Unit = {
val first = r.head
val (same, rest) = r.span(_ == first)
builder += same
if(!rest.isEmpty)
group(rest)
}
if(!r.isEmpty) group(r)
builder.result
}
}
implicit def groupIdentical[Repr : FromRepr](r: Repr) = new GroupIdenticalImpl(r)
サンプルREPLセッション、
scala> val l = List(1, 1, 2, 2, 3, 3, 1, 1)
l: List[Int] = List(1, 1, 2, 2, 3, 3, 1, 1)
scala> l.groupIdentical
res0: List[List[Int]] = List(List(1, 1),List(2, 2),List(3, 3),List(1, 1))
scala> val a = Array(1, 1, 2, 2, 3, 3, 1, 1)
a: Array[Int] = Array(1, 1, 2, 2, 3, 3, 1, 1)
scala> a.groupIdentical
res1: Array[Array[Int]] = Array(Array(1, 1),Array(2, 2),Array(3, 3),Array(1, 1))
scala> val s = "11223311"
s: String = 11223311
scala> s.groupIdentical
res2: scala.collection.immutable.IndexedSeq[String] = Vector(11, 22, 33, 11)
繰り返しになりますが、同じ結果タイプの原則が、groupIdentical
がGenTraversableLike
で直接定義された場合とまったく同じ方法で観察されていることに注意してください。
このコミット 現在、魔法の呪文はマイルズが彼の優れた答えを出したときのものからわずかに変更されています。
以下は動作しますが、それは標準的ですか?カノンの1つがそれを修正することを願っています。 (むしろ、大砲、大きな銃の1つです。)ビュー境界が上限である場合、配列と文字列への適用が失われます。境界がGenTraversableLikeであるかTraversableLikeであるかは問題ではないようです。しかし、IsTraversableLikeはGenTraversableLikeを提供します。
import language.implicitConversions
import scala.collection.{ GenTraversable=>GT, GenTraversableLike=>GTL, TraversableLike=>TL }
import scala.collection.generic.{ CanBuildFrom=>CBF, IsTraversableLike=>ITL }
class GroupIdenticalImpl[A, R <% GTL[_,R]](val r: GTL[A,R]) {
def groupIdentical[That](implicit cbf: CBF[R, R, That]): That = {
val builder = cbf(r.repr)
def group(r: GTL[_,R]) {
val first = r.head
val (same, rest) = r.span(_ == first)
builder += same
if (!rest.isEmpty) group(rest)
}
if (!r.isEmpty) group(r)
builder.result
}
}
implicit def groupIdentical[A, R <% GTL[_,R]](r: R)(implicit fr: ITL[R]):
GroupIdenticalImpl[fr.A, R] =
new GroupIdenticalImpl(fr conversion r)
9匹の命を持つ猫の皮を剥ぐ方法は複数あります。このバージョンでは、ソースがGenTraversableLikeに変換されたら、GenTraversableから結果を作成できる限り、それを実行するだけです。私は私の古いReprに興味がありません。
class GroupIdenticalImpl[A, R](val r: GTL[A,R]) {
def groupIdentical[That](implicit cbf: CBF[GT[A], GT[A], That]): That = {
val builder = cbf(r.toTraversable)
def group(r: GT[A]) {
val first = r.head
val (same, rest) = r.span(_ == first)
builder += same
if (!rest.isEmpty) group(rest)
}
if (!r.isEmpty) group(r.toTraversable)
builder.result
}
}
implicit def groupIdentical[A, R](r: R)(implicit fr: ITL[R]):
GroupIdenticalImpl[fr.A, R] =
new GroupIdenticalImpl(fr conversion r)
この最初の試みには、ReprからGenTraversableLikeへの醜い変換が含まれます。
import language.implicitConversions
import scala.collection.{ GenTraversableLike }
import scala.collection.generic.{ CanBuildFrom, IsTraversableLike }
type GT[A, B] = GenTraversableLike[A, B]
type CBF[A, B, C] = CanBuildFrom[A, B, C]
type ITL[A] = IsTraversableLike[A]
class FilterMapImpl[A, Repr](val r: GenTraversableLike[A, Repr]) {
def filterMap[B, That](f: A => Option[B])(implicit cbf : CanBuildFrom[Repr, B, That]): That =
r.flatMap(f(_).toSeq)
}
implicit def filterMap[A, Repr](r: Repr)(implicit fr: ITL[Repr]): FilterMapImpl[fr.A, Repr] =
new FilterMapImpl(fr conversion r)
class GroupIdenticalImpl[A, R](val r: GT[A,R])(implicit fr: ITL[R]) {
def groupIdentical[That](implicit cbf: CBF[R, R, That]): That = {
val builder = cbf(r.repr)
def group(r0: R) {
val r = fr conversion r0
val first = r.head
val (same, other) = r.span(_ == first)
builder += same
val rest = fr conversion other
if (!rest.isEmpty) group(rest.repr)
}
if (!r.isEmpty) group(r.repr)
builder.result
}
}
implicit def groupIdentical[A, R](r: R)(implicit fr: ITL[R]):
GroupIdenticalImpl[fr.A, R] =
new GroupIdenticalImpl(fr conversion r)