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ı CanBuildFrometrafta 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ı
CanBuildFromdoğ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 As alan ve C[A]s yapan, diğeri de C[A]s alan ve derleyen biri C[C[A]]. Tip imzasına CanBuildFrombaktığı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 cbfccve cbfc.
Bunu fark ettikten sonra, işin çoğu bu. Bizim kullanabilirsiniz CanBuildFrombize 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 resultve 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 Ckendisine Ailetilen ile kendisini doldurduğu tek bir parametre için olmasıdır. Bu yüzden ona sadece tipini veriyoruz Cve 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 Cve C[C[A]]için D[C]. Çağrıda açık jenerik türlere ihtiyacımız olduğuna dikkat edin, new GroupingCollectionbö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: Ddizeleri tutmak için uygun olan bir şeyi arayabiliriz . VectorAş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 CanBuildFromdizi 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 GroupingCollectionmantı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, !!)