Я придумал два решения. Я не нахожу и то, и другое полностью удовлетворительным, но, надеюсь, они могут помочь вам достичь вашей цели.
Первая - хвостовая рекурсивная, но обращает исходный ввод. Это может быть достаточно для вашего варианта использования, если вы не заботитесь о порядке, но если вам нужно, вы должны обратить вспять полученные списки, прежде чем возвращать их, что включает в себя второй проход по списку (O(n)
):
def zipAndPartition[A, B](as: List[A], bs: List[B])(p: ((A, B)) => Boolean): (List[(A, B)], List[(A, B)]) = {
@annotation.tailrec
def loop(left: List[A], right: List[B], acc: (List[(A, B)], List[(A, B)])): (List[(A, B)], List[(A, B)]) =
(left, right) match {
case (Nil, _) | (_, Nil) => acc
case (lhead :: ltail, rhead :: rtail) if p((lhead, rhead)) => loop(ltail, rtail, ((lhead, rhead) :: acc._1, acc._2))
case (lhead :: ltail, rhead :: rtail) => loop(ltail, rtail, (acc._1, (lhead, rhead) :: acc._2))
}
val (left, right) = loop(as, bs, (Nil, Nil))
(left.reverse, right.reverse)
}
Второй не требует реверсирования выходов перед их возвратом, но использует взаимно рекурсивные функции и поэтому не может быть аннотирован с помощью @annotation.tailrec
:
def zap[A, B](as: List[A], bs: List[B])(p: ((A, B)) => Boolean): (List[(A, B)], List[(A, B)]) = {
def loop(left: List[A], right: List[B], acc: (List[(A, B)], List[(A, B)])): (List[(A, B)], List[(A, B)]) =
(left, right) match {
case (Nil, _) | (_, Nil) => acc
case (lhead :: ltail, rhead :: rtail) if p((lhead, rhead)) =>
val tail = zap(ltail, rtail)(p)
((lhead, rhead) :: tail._1, tail._2)
case (lhead :: ltail, rhead :: rtail) =>
val tail = zap(ltail, rtail)(p)
(tail._1, (lhead, rhead) :: tail._2)
}
loop(as, bs, (Nil, Nil))
}
Вы можете поиграться с этим кодом здесь, в Scast ie.
Редактировать
Третье решение, которое сохраняет недостатки первого, но, вероятно, более читабельно, разбивает проблему на две части и использует состав функции:
val tmp = List(1, 2, 3, 15)
val bool = List(true, true, true, false)
def zip[A, B](as: List[A], bs: List[B]): List[(A, B)] = {
@annotation.tailrec
def loop(left: List[A], right: List[B], acc: List[(A, B)]): List[(A, B)] =
(left, right) match {
case (Nil, _) | (_, Nil) => acc
case (lhead :: ltail, rhead :: rtail) => loop(ltail, rtail, (lhead, rhead) :: acc)
}
loop(as, bs, Nil)
}
def partition[A](as: List[A])(p: A => Boolean): (List[A], List[A]) = {
@annotation.tailrec
def loop(list: List[A], acc: (List[A], List[A])): (List[A], List[A]) =
list match {
case Nil => acc
case head :: tail if p(head) => loop(tail, (head :: acc._1, acc._2))
case head :: tail => loop(tail, (acc._1, head:: acc._2))
}
loop(as, (Nil, Nil))
}
def zap[A, B] = (zip[A, B] _).tupled.andThen(partition[(A, B)] _)
zap(tmp, bool)(_._2)
Это решение доступно и в Scast ie.