Benchmark
Поскольку у меня было слишком много времени ;-), я спросил себя, так как это связано со временем выполнения различных подходов, чтобы понять, скрывается ли за легким синтаксисом тяжелая конструкция.
Итак, я создал микро-тест для измерения времени работы для трех последовательностей
(1, 3, 3, 3, 1, 2, 2, 1)
(1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 8, 7, 6, 5, 4, 3, 3, 3, 2, 1, 2, 3)
(2, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 7, 6, 5, 4, 4, 4, 4, 3, 3, 3, 2, 1)
и получил следующий результат:
Резюме
РЕДАКТИРОВАТЬ: новые результаты (начало):
При включении результатов в мой реальный проект я натолкнулся на несоответствия относительно ориентиров. Поэтому я снова повторил тест с большим количеством прогревочных кругов (теперь 1000), чтобы компилятор JIT извлек максимальную пользу из кода. Таким образом, я перетасовывал результаты и родился для меня новым фаворитом: X7 (pimp my lib) = удовольствие без угрызений совести . И List
версия X8 (reverse.foldLeft) теперь тоже очень быстрая.
Nr (Approach) Running time (ns) Contributor
X2 (poor.reference.impl) in 15.202 ns
X1 (original while loop) in 8.166 ns
X3 (tail recursion) in 7.473 ns
X4 (recursion with ++) in 6.671 ns Peter Schmitz
X5 (simplified recursion with ++) in 6.161 ns Peter Schmitz
X6 (foldRight) in 4.083 ns tenshi
X7 (pimp my lib) in 1.677 ns Rex Kerr
X8 (reverse.foldLeft) in 1.349 ns tenshi
РЕДАКТИРОВАТЬ: новые результаты (конец)
старые результаты:
Nr (Approach) Running time (ns) Contributor
X2 (poor.reference.impl) in 2.972.015 ns
X7 (pimp my lib) in 1.185.599 ns Rex Kerr
X3 (tail recursion) in 1.027.008 ns
X8 (reverse.foldLeft) in 643.840 ns tenshi
X6 (foldRight) in 608.112 ns ""
X1 (original while loop) in 564.726 ns
X4 (recursion with ++) in 468.478 ns Peter Schmitz
X5 (simplified recursion with ++) in 447.699 ns ""
Подробнее
X2 (плохое.референция.impl)
// in 15.202 ns
import collection.mutable.ArrayBuffer
def join2(seq: Seq[X]): Seq[Seq[X]] = {
var pp = Seq[ArrayBuffer[X]](ArrayBuffer(seq(0)))
for (i <- 1 until seq.size) {
if (seq(i) canJoin seq(i - 1)) {
pp.last += seq(i)
} else {
pp :+= ArrayBuffer(seq(i))
}
}
pp
}
X1 (цикл while)
// in 8.166 ns
def join(xs: Seq[X]): Seq[Seq[X]] = {
var xss = Seq.empty[Seq[X]]
var s = xs
while (!s.isEmpty) {
val (p, r) = split(s)
xss :+= p
s = r
}
xss
}
Это первоначальный императивный подход в начале вопроса.
X3 (рекурсия хвоста)
// in 7.473 ns
def join(xs: Seq[X]): Seq[Seq[X]] = {
@annotation.tailrec
def jointr(xss: Seq[Seq[X]], rxs: Seq[X]): Seq[Seq[X]] = {
val (g, r) = split(rxs)
val xsn = xss :+ g
if (r.isEmpty) xsn else jointr(xsn, r)
}
jointr(Seq(), xs)
}
X4 (рекурсия с ++)
// in 6.671 ns
def join(seq: Seq[X]): Seq[Seq[X]] = {
if (seq.isEmpty) return Seq()
val (p, r) = split(seq)
Seq(p) ++ join(r)
}
X5 (упрощенная рекурсия с ++)
// in 6.161 ns
def join(xs: Seq[X]): Seq[Seq[X]] = if (xs.isEmpty) Seq() else {
val (p, r) = split(xs)
Seq(p) ++ join(r)
}
Упрощение почти такое же, но все же немного быстрее.
X6 (foldRight)
// in 4.083 ns
def join(xs: Seq[X]) = xs.foldRight(Nil: List[List[X]]) {
case (x, (top :: group) :: rest) if x canJoin top => (x :: top :: group) :: rest
case (x, list) => (x :: Nil) :: list
}
Пытался избежать reverse
, но foldRight
кажется даже немного хуже, чем reverse.foldLeft
для списка.
X7 (прокачай мою библиотеку)
// in 1.677 ns
import collection.generic.CanBuildFrom
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
}
}
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)
}
// xs.groupedWhile(_ canJoin _)
X8 (reverse.foldLeft)
// in 1.349 ns
def join(xs: Seq[X]) = xs.reverse.foldLeft(Nil: List[List[X]]) {
case ((top :: group) :: rest, x) if x canJoin top => (x :: top :: group) :: rest
case (list, x) => (x :: Nil) :: list
}
Заключение
Различные подходы (X1, X3, X4, X5, X6) играют в одной лиге.
Поскольку X7 (pimp my lib) позволяет очень кратко использовать xs.groupedWhile(_ canJoin _)
и потому, что столь необходимый код может быть скрыт в собственных библиотеках утилит, я решил использовать это в своем реальном проекте .