กุญแจสำคัญในการทำความเข้าใจปัญหานี้คือการตระหนักว่ามีสองวิธีที่แตกต่างกันในการสร้างและทำงานกับคอลเลกชันในไลบรารีคอลเลกชัน หนึ่งคืออินเทอร์เฟซคอลเลกชันสาธารณะพร้อมวิธีการที่ดีทั้งหมด อีกอันหนึ่งซึ่งใช้กันอย่างแพร่หลายในการสร้างไลบรารีคอลเลกชัน แต่แทบจะไม่เคยใช้ภายนอกเลยคือผู้สร้าง
ปัญหาของเราในการเพิ่มคุณค่าเป็นปัญหาเดียวกับที่ไลบรารีคอลเลกชันต้องเผชิญเมื่อพยายามส่งคืนคอลเล็กชันประเภทเดียวกัน นั่นคือเราต้องการสร้างคอลเลกชัน แต่เมื่อทำงานโดยทั่วไปเราไม่มีวิธีอ้างถึง "ประเภทเดียวกับที่มีคอลเล็กชันอยู่แล้ว" ดังนั้นเราจึงจำเป็นต้องสร้าง
คำถามคือเราจะเอาผู้สร้างของเรามาจากไหน? สถานที่ที่เห็นได้ชัดคือจากการรวบรวมเอง นี้ไม่ได้ทำงาน เราตัดสินใจแล้วว่าจะย้ายไปที่คอลเลกชันทั่วไปว่าเราจะลืมประเภทของคอลเลกชัน ดังนั้นแม้ว่าคอลเล็กชันจะส่งคืนตัวสร้างที่จะสร้างคอลเล็กชันประเภทที่เราต้องการได้มากขึ้น แต่ก็ไม่ทราบว่าเป็นประเภทใด
แต่เราได้รับผู้สร้างของเราจากCanBuildFrom
นัยที่ลอยอยู่รอบ ๆ สิ่งเหล่านี้มีอยู่โดยเฉพาะเพื่อจุดประสงค์ในการจับคู่ประเภทอินพุตและเอาต์พุตและทำให้คุณมีตัวสร้างที่พิมพ์ได้อย่างเหมาะสม
ดังนั้นเราจึงมีแนวคิดที่จะก้าวกระโดดสองครั้ง:
- เราไม่ได้ใช้การดำเนินการคอลเลกชันมาตรฐานเรากำลังใช้ตัวสร้าง
- เราได้ผู้สร้างเหล่านี้จากโดยนัย
CanBuildFrom
ไม่ใช่จากคอลเล็กชันของเราโดยตรง
ลองดูตัวอย่าง
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]]
ที่รวบรวมกลุ่มทั้งหมดเข้าด้วยกัน ดังนั้นเราจึงต้องสองผู้สร้างคนหนึ่งที่ใช้เวลาA
และสร้างC[A]
s และหนึ่งที่ใช้เวลาC[A]
และสร้างC[C[A]]
s เมื่อมองไปที่ลายเซ็นของCanBuildFrom
เราจะเห็น
CanBuildFrom[-From, -Elem, +To]
ซึ่งหมายความว่า CanBuildFrom ต้องการทราบประเภทของคอลเลกชันที่เราเริ่มต้น - ในกรณีของเราคือC[A]
จากนั้นองค์ประกอบของคอลเล็กชันที่สร้างขึ้นและประเภทของคอลเล็กชันนั้น ดังนั้นเรากรอกข้อมูลผู้ที่อยู่ในเป็นพารามิเตอร์โดยปริยายและcbfcc
cbfc
เมื่อตระหนักถึงสิ่งนี้นั่นคืองานส่วนใหญ่ เราสามารถใช้CanBuildFrom
s ของเราเพื่อให้เราสร้างผู้สร้าง (สิ่งที่คุณต้องทำคือใช้พวกเขา) และเป็นหนึ่งในผู้สร้างสามารถสร้างคอลเลกชันที่มีการ+=
แปลงเป็นคอลเลกชันที่มันควรจะเป็นในท้ายที่สุดด้วยและล้างตัวเองและพร้อมที่จะเริ่มต้นอีกครั้งกับresult
clear
ตัวสร้างเริ่มว่างเปล่าซึ่งจะแก้ข้อผิดพลาดในการคอมไพล์แรกของเราและเนื่องจากเราใช้ตัวสร้างแทนการเรียกซ้ำข้อผิดพลาดที่สองก็หายไปด้วย
รายละเอียดเล็ก ๆ น้อย ๆ สุดท้าย - นอกเหนือจากอัลกอริทึมที่ใช้งานได้จริง - อยู่ในการแปลงโดยนัย โปรดทราบว่าเราจะใช้ไม่ได้new GroupingCollection[A,C]
[A,C[A]]
เนื่องจากการประกาศคลาสนั้นมีไว้สำหรับ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))
มันได้ผล!
ปัญหาเดียวคือโดยทั่วไปแล้วเราไม่มีวิธีการเหล่านี้สำหรับอาร์เรย์เนื่องจากจะต้องมีการแปลงโดยนัยสองรายการติดต่อกัน มีหลายวิธีในการแก้ไขปัญหานี้รวมถึงการเขียนการแปลงโดยนัยที่แยกจากกันสำหรับอาร์เรย์การแคสต์ไปยังWrappedArray
และอื่น ๆ
แก้ไข: แนวทางที่ฉันชอบในการจัดการกับอาร์เรย์และสตริงและนั่นคือการทำให้โค้ดมีความกว้างมากขึ้นจากนั้นใช้การแปลงโดยนัยที่เหมาะสมเพื่อทำให้มีความเฉพาะเจาะจงมากขึ้นอีกครั้งในลักษณะที่อาร์เรย์ทำงานด้วย ในกรณีนี้โดยเฉพาะ:
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
}
}
ที่นี่เราได้เพิ่มนัยที่ให้เราIterable[A]
จากC
- สำหรับคอลเลกชันส่วนใหญ่สิ่งนี้จะเป็นข้อมูลประจำตัว (เช่น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[A]
สำหรับC
และสำหรับ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, !!)