Потоки чрезвычайно полезны, когда вы выполняете последовательность рекурсивных вычислений, и один результат зависит от предыдущих результатов, таких как вычисление числа Пи.Вот более простой пример, рассмотрим классический рекурсивный алгоритм для вычисления чисел Фиббоначи (1, 2, 3, 5, 8, 13, ...):
def fib(n: Int) : Int = n match {
case 0 => 1
case 1 => 2
case _ => fib(n - 1) + fib(n - 2)
}
Одним из основных пунктов этого кода являетсяэто очень просто, но крайне неэффективно.fib(100)
чуть не разбил мой компьютер!Каждая рекурсия разветвляется на два вызова, и вы по сути вычисляете одни и те же значения много раз.
Потоки позволяют выполнять динамическое программирование рекурсивным способом, где после вычисления значения оно используется повторно каждый раз, когда оно необходимоснова.Для реализации вышеизложенного с использованием потоков:
val naturals: Stream[Int] = Stream.cons(0, naturals.map{_ + 1})
val fibs : Stream[Int] = naturals.map{
case 0 => 1
case 1 => 2
case n => fibs(n - 1) + fibs( n - 2)
}
fibs(1) //2
fibs(2) //3
fibs(3) //5
fibs(100) //1445263496
В то время как рекурсивное решение выполняется за O (2 ^ n), решение Streams выполняется за O (n ^ 2).Поскольку вам нужны только последние 2 сгенерированных элемента, вы можете легко оптимизировать это, используя Stream.drop
, чтобы размер потока не переполнял память.