我提出了一个解决方案,旨在实现以下目标:
- 通用性:您应该能够像拆分
Vector
一样拆分 Array
,以及像拆分任意对象集合一样拆分 Char
集合。
- 保留输入类型:一个
Array[A]
被拆分成一个 Array[Array[A]]
,一个 Vector[A]
被拆分成一个 Vector[Vector[A]]
。
- 如果需要,允许使用延迟方法(通过
Iterator
)。
- 为大多数情况提供紧凑的接口(只需在您的集合上调用
split
方法)。
在解释之前,请注意您可以在 这里的 Scastie 上 尝试下面的代码。
第一步是实现一个 Iterator
,它会将您的集合分块:
import scala.language.higherKinds
import scala.collection.generic.CanBuildFrom
final class Split[A, CC[_]](delimiter: A => Boolean, as: CC[A])(
implicit view: CC[A] => Seq[A], cbf: CanBuildFrom[Nothing, A, CC[A]])
extends Iterator[CC[A]] {
private[this] var it: Iterator[A] = view(as).iterator
private def skipDelimiters() = {
it = it.dropWhile(delimiter)
}
skipDelimiters()
override def hasNext: Boolean = it.hasNext
override def next(): CC[A] = {
val builder = cbf()
builder ++= it.takeWhile(!delimiter(_))
skipDelimiters()
builder.result()
}
}
我使用谓词而不是值来更加灵活地拆分集合,特别是在拆分非标量值(如
Char
)的集合时。
我使用集合类型上的隐式视图,以便将其应用于可以被视为
Seq
的所有种类的集合(如
Vector
和
Array
),并使用
CanBuildFrom
以便能够构建与输入相同类型的集合。
Iterator
的实现只需确保删除分隔符并将其余部分分块。
我们现在可以使用一个
implicit class
来提供友好的接口,并将
split
方法添加到所有集合中,既允许定义谓词也允许定义值作为分隔符:
final implicit class Splittable[A, CC[_]](val as: CC[A])(implicit ev1: CC[A] => Seq[A], ev2: CanBuildFrom[Nothing, A, CC[A]], ev3: CanBuildFrom[Nothing, CC[A], CC[CC[A]]]) {
def split(delimiter: A => Boolean): CC[CC[A]] = new Split(as)(delimiter).to[CC]
def split(delimiter: A): CC[CC[A]] = new Split(as)(_ == delimiter).to[CC]
}
现在你可以自由地在
Char
的集合上使用你的方法。
val a = Array('a', 'b', '\n', 'c', 'd', 'e', '\n', 'g', '\n')
val b = List('\n', '\n', '\n')
val c = Vector('\n', 'c', 'd', 'e', '\n', 'g', '\n')
val d = Array('a', 'b', 'c', 'd', 'e', 'g', '\n')
val e = Array('a', 'b', 'c', 'd', 'e', 'g', '\n')
a.split('\n')
b.split('\n')
c.split('\n')
d.split('\n')
e.split('\n')
和任意对象一样:
final case class N(n: Int, isDelimiter: Boolean)
Vector(N(1, false), N(2, false), N(3, true), N(4, false), N(5, false)).split(_.isDelimiter)
请注意,直接使用迭代器采用了一种惰性的方法,如果您在
next
方法中添加调试打印并尝试执行以下操作,则可以看到这一点:
new Split(Vector('\n', 'c', 'd', 'e', '\n', 'g', '\n'))(_ == '\n'}).take(1).foreach(println)
如果您愿意,可以向
Splittable
添加一些返回
Iterator
的方法,这样您就可以直接通过它公开懒惰的方法。
tail
等函数会导致数组通过隐式转换被包装并创建一个新数组。每个传递给高阶函数的函数字面量都需要加载和实例化一个类。将两个序列压缩会为每对元素创建一个Tuple2。我倾向于怀疑任何其他技术都不会比漂亮优雅的array.mkString.split('\n').map(_.toArray)
具有更少的开销。 - Randall SchulzArray[Int]
显然会在mkString
中出现问题:Array(123, 0, 10, 456, 20).mkString.split('0')
,而修复它肯定是很麻烦的。 - FaizmkString.split
选项是最好的选择,但正如Faiz所说,只要数组是字符串(或字符)之一。否则,这里发布的任何解决方案都很不错(除了我的解决方案,它表现得非常糟糕)。 - Ruben