Bu sorunu anlamanın anahtarı , koleksiyon kitaplığında koleksiyonlar oluşturmanın ve bunlarla çalışmanın iki farklı yolu olduğunu fark etmektir . Biri, tüm güzel yöntemleriyle genel koleksiyon arayüzü. Koleksiyon kitaplığının oluşturulmasında yoğun olarak kullanılan , ancak dışında neredeyse hiç kullanılmayan diğeri ise inşaatçılar.
Zenginleştirme konusundaki sorunumuz, aynı türden koleksiyonları döndürmeye çalışırken koleksiyon kitaplığının kendisinin karşılaştığı sorunla tamamen aynıdır. Yani, koleksiyonlar oluşturmak istiyoruz, ancak genel olarak çalışırken, "koleksiyonun halihazırda olduğu türden" bahsetmek için bir yolumuz yok. Bu yüzden inşaatçılara ihtiyacımız var .
Şimdi soru şu: inşaatçılarımızı nereden alıyoruz? Bariz yer koleksiyonun kendisidir. Bu çalışmıyor . Jenerik bir koleksiyona geçerken, koleksiyonun türünü unutmaya karar verdik. Dolayısıyla koleksiyon, istediğimiz türde daha fazla koleksiyon oluşturacak bir kurucu döndürebilseydi bile, türün ne olduğunu bilemezdi.
Bunun yerine, inşaatçılarımızı CanBuildFrom
etrafta dolaşan sonuçlardan alıyoruz . Bunlar, özellikle girdi ve çıktı türlerini eşleştirmek ve size uygun şekilde yazılmış bir kurucu sağlamak amacıyla mevcuttur.
Yani, yapmamız gereken iki kavramsal adım var:
- Standart tahsilat işlemlerini kullanmıyoruz, inşaatçılar kullanıyoruz.
- Bu oluşturucuları
CanBuildFrom
doğrudan koleksiyonumuzdan değil, örtük e- postalardan alıyoruz.
Bir örneğe bakalım.
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)
}
Bunu parçalara ayıralım. İlk olarak, koleksiyon koleksiyonunu oluşturmak için iki tür koleksiyon oluşturmamız gerektiğini biliyoruz: C[A]
her grup için ve C[C[A]]
bu tüm grupları bir araya toplar. Bu nedenle, iki kurucuya ihtiyacımız var, biri A
s alan ve C[A]
s yapan, diğeri de C[A]
s alan ve derleyen biri C[C[A]]
. Tip imzasına CanBuildFrom
baktığımızda görüyoruz
CanBuildFrom[-From, -Elem, +To]
Bu, CanBuildFrom'un başladığımız koleksiyon türünü bilmek istediği anlamına gelir - bizim durumumuzda, bu C[A]
ve sonra oluşturulan koleksiyonun öğeleri ve bu koleksiyonun türü. Bu yüzden bunları örtük parametreler olarak doldururuz cbfcc
ve cbfc
.
Bunu fark ettikten sonra, işin çoğu bu. Bizim kullanabilirsiniz CanBuildFrom
bize inşaatçılar vermek için s (yapmanız gereken tüm bunları uygulamak olduğunu). Ve bir inşaatçı, bir koleksiyon oluşturabilir +=
, onu nihayetinde birlikte olması gereken koleksiyona dönüştürebilir result
ve kendini boşaltarak yeniden başlamaya hazır olabilir clear
. Oluşturucular boş başlarlar, bu da ilk derleme hatamızı çözer ve özyineleme yerine derleyiciler kullandığımızdan, ikinci hata da ortadan kalkar.
Son bir küçük ayrıntı - işi gerçekten yapan algoritma dışında - örtük dönüştürmede. Kullandığımız unutmayın new GroupingCollection[A,C]
değildir [A,C[A]]
. Bunun nedeni, sınıf bildiriminin C
kendisine A
iletilen ile kendisini doldurduğu tek bir parametre için olmasıdır. Bu yüzden ona sadece tipini veriyoruz C
ve onu yaratmasına izin veriyoruz C[A]
. Küçük ayrıntı, ancak başka bir yol denerseniz derleme zamanı hataları alırsınız.
Burada, yöntemi "eşit öğeler" koleksiyonundan biraz daha genel yaptım - daha ziyade, yöntem, sıralı öğelerin testi başarısız olduğunda orijinal koleksiyonu parçalara ayırıyor.
Yöntemimizi iş başında görelim:
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))
İşe yarıyor!
Tek sorun, genel olarak diziler için bu yöntemlerin mevcut olmamasıdır, çünkü bu, arka arkaya iki örtük dönüşüm gerektirir. Diziler için ayrı bir örtük dönüşüm yazmak, dönüştürmek, vb. Dahil olmak üzere bunu aşmanın birkaç yolu vardır WrappedArray
.
Düzenleme: Dizilerle ve dizelerle ve benzeri şeylerle uğraşmak için tercih ettiğim yaklaşımım, kodu daha genel hale getirmek ve ardından dizilerin de çalışacağı şekilde tekrar daha spesifik hale getirmek için uygun örtük dönüştürmeleri kullanmaktır. Bu özel durumda:
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
}
}
Burada bize bir verdiği örtük ekledik Iterable[A]
dan C
(örn Bora'nın en koleksiyonları bu sadece kimlik olacak List[A]
zaten olduğunu Iterable[A]
), ancak diziler için gerçek bir örtük dönüşüm olacak. Ve sonuç olarak, - C[A] <: Iterable[A]
temelde sadece <%
açık olan gereksinimi yerine getirdik , böylece derleyicinin bizim için doldurması yerine onu açıkça isteyerek kullanabiliriz. Ayrıca, koleksiyon koleksiyonumuzun - C[C[A]]
bunun D[C]
yerine, daha sonra istediğimiz gibi dolduracağımız herhangi bir koleksiyon olduğu şeklindeki kısıtlamayı gevşettik . Bunu daha sonra dolduracağımız için, metot seviyesi yerine sınıf seviyesine kadar yükselttik. Aksi takdirde, temelde aynıdır.
Şimdi soru bunun nasıl kullanılacağıdır. Normal koleksiyonlar için şunları yapabiliriz:
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)
}
nerede şimdi takın C[A]
için C
ve C[C[A]]
için D[C]
. Çağrıda açık jenerik türlere ihtiyacımız olduğuna dikkat edin, new GroupingCollection
böylece hangi türlerin neye karşılık geldiğini açıklığa kavuşturabilir. Sayesinde implicit c2i: C[A] => Iterable[A]
, bu dizileri otomatik olarak işler.
Ama bekleyin, ya dizeleri kullanmak istersek? Şimdi başımız belada, çünkü bir "dizginiz" olamaz. Ekstra soyutlamanın yardımcı olduğu yer burasıdır: D
dizeleri tutmak için uygun olan bir şeyi arayabiliriz . Vector
Aşağıdakileri seçip yapalım :
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
)
}
Bir CanBuildFrom
dizi vektörünün oluşturulmasını idare etmek için yeniye ihtiyacımız var (ama bu gerçekten kolay, çünkü sadece aramamız gerekiyor Vector.newBuilder[String]
) ve sonra GroupingCollection
mantıklı bir şekilde yazılabilmesi için tüm türleri doldurmamız gerekiyor . Zaten bir [String,Char,String]
CanBuildFrom etrafında kayan bir sistemimiz olduğuna dikkat edin, böylece karakter koleksiyonlarından karakter dizileri oluşturulabilir.
Hadi deneyelim:
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, !!)