Chìa khóa để hiểu vấn đề này là nhận ra rằng có hai cách khác nhau để xây dựng và làm việc với các bộ sưu tập trong thư viện bộ sưu tập. Một là giao diện bộ sưu tập công khai với tất cả các phương pháp hay của nó. Cái khác, được sử dụng rộng rãi trong việc tạo thư viện bộ sưu tập, nhưng hầu như không bao giờ được sử dụng bên ngoài nó, là bộ xây dựng.
Vấn đề của chúng ta trong việc làm giàu giống hệt như vấn đề mà bản thân thư viện bộ sưu tập gặp phải khi cố gắng trả về các bộ sưu tập cùng loại. Đó là, chúng ta muốn xây dựng các bộ sưu tập, nhưng khi làm việc chung, chúng ta không có cách nào để tham chiếu đến "cùng một kiểu mà bộ sưu tập đã có". Vì vậy, chúng tôi cần những người xây dựng .
Bây giờ câu hỏi là: chúng ta lấy các nhà xây dựng của mình từ đâu? Nơi rõ ràng là từ chính bộ sưu tập. Điều này không hoạt động . Chúng tôi đã quyết định, khi chuyển sang một bộ sưu tập chung, rằng chúng tôi sẽ quên loại bộ sưu tập. Vì vậy, mặc dù bộ sưu tập có thể trả về một trình tạo sẽ tạo ra nhiều bộ sưu tập hơn thuộc loại chúng ta muốn, nó sẽ không biết loại đó là gì.
Thay vào đó, chúng tôi đưa các nhà xây dựng của mình từ CanBuildFrom
ẩn ý đang trôi nổi xung quanh. Chúng tồn tại đặc biệt cho mục đích khớp các loại đầu vào và đầu ra và cung cấp cho bạn một trình tạo được đánh máy thích hợp.
Vì vậy, chúng tôi có hai bước nhảy vọt về khái niệm cần thực hiện:
- Chúng tôi không sử dụng các hoạt động tập hợp tiêu chuẩn, chúng tôi đang sử dụng trình tạo.
- Chúng tôi nhận được những nhà xây dựng này
CanBuildFrom
từ các bộ sưu tập ngầm , không phải từ bộ sưu tập của chúng tôi trực tiếp.
Hãy xem một ví dụ.
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)
}
Hãy tách nó ra. Đầu tiên, để xây dựng bộ sưu tập, chúng ta biết rằng chúng ta sẽ cần tạo hai loại bộ sưu tập: C[A]
cho mỗi nhóm và C[C[A]]
tập hợp tất cả các nhóm lại với nhau. Vì vậy, chúng ta cần hai trình xây dựng, một trình xây dựng lấy A
s và xây dựng C[A]
s, và một C[A]
trình xây dựng lấy s và xây dựng C[C[A]]
s. Nhìn vào kiểu chữ ký của CanBuildFrom
, chúng ta thấy
CanBuildFrom[-From, -Elem, +To]
có nghĩa là CanBuildFrom muốn biết loại bộ sưu tập mà chúng tôi đang bắt đầu - trong trường hợp của chúng tôi, đó là C[A]
, và sau đó là các phần tử của bộ sưu tập đã tạo và loại bộ sưu tập đó. Vì vậy, chúng tôi điền những thông số đó vào dưới dạng các thông số ngầm định cbfcc
vàcbfc
.
Nhận ra điều này, đó là hầu hết công việc. Chúng tôi có thể sử dụng CanBuildFrom
s của chúng tôi để cung cấp cho chúng tôi các nhà xây dựng (tất cả những gì bạn cần làm là áp dụng chúng). Và một người xây dựng có thể xây dựng một bộ sưu tập với +=
, chuyển đổi nó thành bộ sưu tập mà nó được cho là cuối cùng result
, và tự làm trống và sẵn sàng bắt đầu lại vớiclear
. Các trình xây dựng bắt đầu trống, điều này giải quyết lỗi biên dịch đầu tiên của chúng tôi và vì chúng tôi đang sử dụng trình xây dựng thay vì đệ quy, lỗi thứ hai cũng biến mất.
Một chi tiết nhỏ cuối cùng - ngoài thuật toán thực sự hoạt động - là ở chuyển đổi ngầm định. Lưu ý rằng chúng tôi new GroupingCollection[A,C]
không sử dụng [A,C[A]]
. Điều này là do khai báo lớp dành cho C
một tham số, mà nó tự điền vào nó với giá trị A
được truyền cho nó. Vì vậy, chúng tôi chỉ giao cho nó loại C
, và để nó tạo C[A]
ra từ nó. Chi tiết nhỏ, nhưng bạn sẽ gặp lỗi thời gian biên dịch nếu bạn thử cách khác.
Ở đây, tôi đã thực hiện phương pháp chung chung hơn một chút so với tập hợp "các phần tử bằng nhau" - đúng hơn, phương pháp này sẽ cắt tập hợp ban đầu ra bất cứ khi nào việc kiểm tra các phần tử tuần tự của nó không thành công.
Hãy xem phương pháp của chúng tôi đang hoạt động:
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))
Nó hoạt động!
Vấn đề duy nhất là chúng ta nói chung không có sẵn các phương thức này cho mảng, vì điều đó sẽ yêu cầu hai chuyển đổi ngầm định liên tiếp. Có một số cách để giải quyết vấn đề này, bao gồm viết một chuyển đổi ngầm định riêng cho các mảng, truyền sang WrappedArray
, v.v.
Chỉnh sửa: Cách tiếp cận ưa thích của tôi để xử lý các mảng và chuỗi và như vậy là làm cho mã trở nên chung chung hơn và sau đó sử dụng các chuyển đổi ngầm định thích hợp để làm cho chúng cụ thể hơn theo cách mà các mảng cũng hoạt động. Trong trường hợp cụ thể này:
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
}
}
Ở đây chúng tôi đã thêm một hàm ẩn cung cấp cho chúng tôi một Iterable[A]
từ - đối với C
hầu hết các bộ sưu tập, đây sẽ chỉ là danh tính (ví dụ: List[A]
đã là một Iterable[A]
), nhưng đối với mảng, nó sẽ là một chuyển đổi ngầm thực sự. Và do đó, chúng tôi đã bỏ yêu cầu - C[A] <: Iterable[A]
về cơ bản chúng tôi chỉ đưa ra yêu cầu <%
rõ ràng, vì vậy chúng tôi có thể sử dụng nó một cách rõ ràng theo ý muốn thay vì để trình biên dịch điền vào cho chúng tôi. Ngoài ra, chúng tôi đã nới lỏng hạn chế mà bộ sưu tập của chúng tôi là - đầu C[C[A]]
tiên, nó là bất kỳ D[C]
, mà chúng tôi sẽ điền vào sau để trở thành những gì chúng tôi muốn. Vì chúng ta sẽ điền vào phần này sau, nên chúng tôi đã đẩy nó lên cấp lớp thay vì cấp phương thức. Nếu không, về cơ bản nó giống nhau.
Bây giờ câu hỏi là làm thế nào để sử dụng điều này. Đối với các bộ sưu tập thông thường, chúng tôi có thể:
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)
}
nơi bây giờ chúng tôi cắm C[A]
cho C
và C[C[A]]
cho D[C]
. Lưu ý rằng chúng ta cần các kiểu chung chung rõ ràng trong lệnh gọi để new GroupingCollection
nó có thể giữ thẳng những kiểu nào tương ứng với cái gì. Nhờ có implicit c2i: C[A] => Iterable[A]
, điều này tự động xử lý các mảng.
Nhưng chờ đã, nếu chúng ta muốn sử dụng chuỗi thì sao? Bây giờ chúng tôi đang gặp rắc rối, bởi vì bạn không thể có một "chuỗi các chuỗi". Đây là nơi mà sự trừu tượng bổ sung sẽ giúp ích: chúng ta có thể gọi D
một cái gì đó phù hợp để giữ các chuỗi. Hãy chọn Vector
và làm như sau:
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
)
}
Chúng ta cần một cái mới CanBuildFrom
để xử lý việc xây dựng một vectơ chuỗi (nhưng điều này thực sự dễ dàng, vì chúng ta chỉ cần gọi Vector.newBuilder[String]
), và sau đó chúng ta cần điền vào tất cả các loại để GroupingCollection
được nhập hợp lý. Lưu ý rằng chúng ta đã trôi nổi xung quanh một [String,Char,String]
CanBuildFrom, vì vậy các chuỗi có thể được tạo từ các tập hợp các ký tự.
Hãy thử nó ra:
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, !!)