Как вернуть все положительные значения и первое отрицательное число в списке, используя функциональное программирование?
Представьте, что у меня есть несортированный список положительных и отрицательных ints. Я хочу вернуть список, содержащий все положительные int и первое отрицательное число, но затем игнорировать все последующие отрицательные числа из списка, сохраняя порядок.
Императивно я мог бы сделать:
l = [1, 2, -4, 5, -6, -1, 3]
out = []
first = true
for n in l:
if n >= 0:
out.push(n)
else if first:
out.push(n)
first = false
// out = [1, 2, -4, 5, 3]
Как я могу сделать это с помощью FP в Scala? Я думал (вероятно, не компилируется...):
val l = List(1, 2, -4, 5, -6, -1, 3)
val posl = l.map(_ >= 0)
val negl = l.zipWithIndex.map((n, i) => if (n < 0) (i, n) else (None, None)).head
// now split posl at negl._1, and create a new list of leftSlice :: negl._2 :: rightSlice?
Это правильный подход, или есть более элегантный, лаконичный способ?
Ответы
Ответ 1
Вот хвост-рекурсивный путь. По сравнению с ответом m-z он повторяет ваш список только один раз, по сравнению с ответом Димаса, он не использует изменчивое состояние, поэтому он является чисто функциональным.
def firstNegAllPos(list: List[Int]) : List[Int] = {
def loop(in: List[Int], out: List[Int], negfound: Boolean) : List [Int] = {
in match {
case Nil => out
case head :: tail =>
if (negfound)
loop(tail, if (head < 0) out else head :: out, true)
else
loop(tail, head :: out, head < 0)
}
}
loop(list, Nil, false)
}
firstNegAllPos(List(1, 2, -4, 5, -6, -1, 3)) // List(3, 5, -4, 2, 1)
Edit
Вышеупомянутая реализация обеспечивает обратный результат. Чтобы сохранить порядок, вы можете сделать следующее:
def firstNegAllPos(list: List[Int]) : List[Int] = {
def loop(in: List[Int], out: List[Int], negfound: Boolean) : List [Int] = {
in match {
case Nil => out
case head :: tail =>
if (negfound)
loop(tail, if (head < 0) out else head :: out, true)
else
loop(tail, head :: out, head < 0)
}
}
loop(list, Nil, false).reverse
}
firstNegAllPos(List(1, 2, -4, 5, -6, -1, 3)) // List(1, 2, -4, 5, 3)
Ответ 2
Это не был бы правильный вопрос о функциональном программировании без ответа с слишком умной рекурсией + шаблоном.
def firstNegAllPos(l:List[Int]):List[Int] = {
l match{
case x::xs if x>=0 => x::firstNegAllPos(xs)
case x::xs if x<0 => x::xs.filter(_>=0)
case Nil => Nil
}
}
Ответ 3
Вы можете сделать это за один проход, если вы не возражаете поддерживать немного состояния - подумайте var neg
:
var neg = false
list.filter {
case x if x > 0 => true
case _ if !neg => neg = true
}
Ответ 4
Прямой перевод требования довольно ясен, проходит один проход по списку и функционирует:
val l = List(1, 2, -4, 5, -6, -1, 3)
// split into any initial positive numbers, and the rest of the list
val (pos, firstneg) = l.span(_ >= 0)
// if there were no negative numbers, return the original list.
// otherwise, it the initial positives, the first negative, and
// the positive numbers from the rest of the list.
if (firstNeg.isEmpty) l else pos:::List(firstneg.head):::firstneg.tail.filter(_>=0)
//> res0: List[Int] = List(1, 2, -4, 5, 3)
(List
вокруг firstneg.head
справедливо только для симметрии :::
с обеих сторон)
Ответ 5
val l = List(1, 2, -4, 5, -6, -1, 3)
val (left, right) = l.span(_ > 0)
val result = right.headOption match {
case Some(n) => (left :+ n) ++ right.tail.filter(_ > 0)
case None => left
}
Ответ 6
Это очевидная работа для операции сгиба!
val l = List(1, 2, -4, 5, -6, -1, 3)
var result = l.foldLeft((true, Vector.empty[Int])) {
case ((f, r), x) if x >= 0 => (f, r :+ x)
case ((f, r), x) if f => (false, r :+ x)
case ((f, r), x) => (f, r)
}._2
println(result) // Vector(1, 2, -4, 5, 3)
Я использовал вектор как промежуточную структуру; вы можете преобразовать его в список с toList
, если вам это нужно. Или вы можете использовать его вместо Vector
, но вам придется отменить порядок добавления (r :+ x
= > x :: r
), а затем перевернуть список в конце с помощью метода reverse
.
Ответ 7
Если вы хотите сохранить порядок (т.е. положение отрицательного), вы можете сделать что-то вроде этого:
val list = List(1, 2, -4, 5, -6, -1, 3)
val negIndex = list.indexWhere(_ < 0)
val positive = list.zipWithIndex.filter { case (num, index) =>
num >= 0 || index == negIndex
}.map(_._1)
Отрицательное требование затрудняет его сжатие. Моя стратегия состоит в том, чтобы просто захватить индекс первого отрицательного значения с помощью indexWhere
(будет -1, если его нет), а затем filter
все отрицательные элементы из списка, за исключением индекса первого отрицательного. Если индекс был равен -1, никакого вреда не было.
Ответ 8
Ниже приведен порядок сохранения хвостового рекурсивного решения:
def posNeg(xs: List[Int]): List[Int] = {
@tailrec
def go(tail: List[Int], res: List[Int]): List[Int] = {
tail match {
case h :: t =>
if (h >= 0) go(t, h :: res)
else (h :: res).reverse ::: t.filter(_ > 0)
case _ => res
}
}
go(xs, Nil)
}
Ответ 9
Существует много рекурсивных решений, но они все дольше, чем они должны быть.
def oneNeg(xs: List[Int]): List[Int] = {
def loop(in: List[Int], out: List[Int], neg: Int): List[Int] = in match {
case Nil => out
case x :: rest =>
if (neg < 0 && x < 0) loop(rest, out, neg)
else loop(rest, x :: out, x min neg)
}
loop(xs, Nil, 0).reverse
}
Если это не общедоступный API, вы можете сделать его короче, просто разоблачив только внутренний метод:
def oneNeg(in: List[Int], out: List[Int] = Nil, neg: Int = 0): List[Int] =
in match {
case Nil => out.reverse
case x :: rest =>
if (neg < 0 && x < 0) oneNeg(rest, out, neg)
else oneNeg(rest, x :: out, x min neg)
}
Ответ 10
Вот улучшение на наиболее краткий ответ, который я увидел, @sschaef в комментарий:
val l = List(1, 2, -4, 5, -6, -1, 3)
l.span(_>=0) match { case (left, Nil) => left
case (left, x::right) => left ++ (x +: right.filter(_>=0)) }
Ответ 11
Вы можете попробовать:
val list = List(1, 2, -4, 5, -6, -1, 3)
val index = list.indexWhere(_ < 0) + 1
list.take(index) ++ list.drop(index).filter(_ > 0)