В Scala как сбросить список и вернуть промежуточные результаты?
У меня есть список дней в месяце:
val days = List(31, 28, 31, ...)
Мне нужно вернуть список с суммарной суммой дней:
val cumDays = List(31, 59, 90)
Я думал об использовании оператора fold:
(0 /: days)(_ + _)
но это вернет только конечный результат (365), тогда как мне нужен список промежуточных результатов.
В любом случае я могу сделать это элегантно?
Ответы
Ответ 1
Scala 2.8 имеет методы scanLeft
и scanRight
, которые выполняют именно это.
В версии 2.7 вы можете определить свой собственный scanLeft
следующим образом:
def scanLeft[a,b](xs:Iterable[a])(s:b)(f : (b,a) => b) =
xs.foldLeft(List(s))( (acc,x) => f(acc(0), x) :: acc).reverse
И затем используйте его следующим образом:
scala> scanLeft(List(1,2,3))(0)(_+_)
res1: List[Int] = List(0, 1, 3, 6)
Ответ 2
Я не уверен, почему все, кажется, настаивают на использовании какой-то сворачивания, в то время как вы в основном хотите сопоставить значения с накопленными значениями...
val daysInMonths = List(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
val cumulated = daysInMonths.map{var s = 0; d => {s += d; s}}
//--> List[Int] = List(31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
Ответ 3
Вы можете просто выполнить его:
daysInMonths.foldLeft((0, List[Int]()))
{(acu,i)=>(i+acu._1, i+acu._1 :: acu._2)}._2.reverse
Ответ 4
Сложите в список вместо целого. Используйте пару (неполный список с накопленными значениями, аккумулятор с последней суммой) как состояние в сгибе.
Ответ 5
Сложите список в новый список. На каждой итерации добавьте значение, которое представляет собой сумму головы + следующего входа. Затем измените все.
scala> val daysInMonths = List(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
daysInMonths: List[Int] = List(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
scala> daysInMonths.foldLeft(Nil: List[Int]) { (acc,next) =>
| acc.firstOption.map(_+next).getOrElse(next) :: acc
| }.reverse
res1: List[Int] = List(31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
Ответ 6
Вы также можете создать моноидный класс, который объединяет два списка, добавляя ко второму последнее значение из первого. Нет изменений и без складки:
case class CumSum(v: List[Int]) { def +(o: CumSum) = CumSum(v ::: (o.v map (_ + v.last))) }
defined class CumSum
scala> List(1,2,3,4,5,6) map {v => CumSum(List(v))} reduce (_ + _)
res27: CumSum = CumSum(List(1, 3, 6, 10, 15, 21))
Ответ 7
Работает на 2.7.7:
def stepSum (sums: List [Int], steps: List [Int]) : List [Int] = steps match {
case Nil => sums.reverse.tail
case x :: xs => stepSum (sums.head + x :: sums, steps.tail) }
days
res10: List[Int] = List(31, 28, 31, 30, 31)
stepSum (List (0), days)
res11: List[Int] = List(31, 59, 90, 120, 151)
Ответ 8
Для любого:
val s:Seq[Int] = ...
Вы можете использовать один из них:
s.tail.scanLeft(s.head)(_ + _)
s.scanLeft(0)(_ + _).tail
или сгибы, предложенные в других ответах, но...
Имейте в виду, что решение Landei сложно, и вы должны избегать его.
БУДЬТЕ ВНИМАТЕЛЬНЫ
s.map { var s = 0; d => {s += d; s}}
//works as long 's' is strict collection
val s2:Seq[Int] = s.view //still seen as Seq[Int]
s2.map { var s = 0; d => {s += d; s}}
//makes really weird things!
//Each value'll be different whenever you'll access it!
Я должен предупредить об этом в качестве комментария ниже ответа Landei, но я не смог :(.