Ответ 1
Stream.from(1)
создает поток, начиная с 1 и увеличивая на 1. Все это в API документации.
Скажем, у меня есть функция, например старая любимая
def factorial(n:Int) = (BigInt(1) /: (1 to n)) (_*_)
Теперь я хочу найти наибольшее значение n
, для которого factorial(n)
подходит для Long. Я мог бы сделать
(1 to 100) takeWhile (factorial(_) <= Long.MaxValue) last
Это работает, но 100 - произвольное большое число; то, что я действительно хочу с левой стороны, - это бесконечный поток, который продолжает генерировать более высокие числа, пока не будет выполнено условие takeWhile
.
Я придумал
val s = Stream.continually(1).zipWithIndex.map(p => p._1 + p._2)
но есть ли лучший способ?
(Я также знаю, что я мог бы получить решение рекурсивно, но это не то, что я ищу.)
Stream.from(1)
создает поток, начиная с 1 и увеличивая на 1. Все это в API документации.
Вы также можете использовать Iterator
вместо Stream
. Stream
хранит ссылки на все вычисленные значения. Поэтому, если вы планируете посещать каждое значение только один раз, итератор - более эффективный подход. Недостатком итератора является его изменчивость.
Есть несколько удобных методов для создания Iterator
определенного для его объекта-компаньона.
К сожалению, нет короткого (поддерживаемого библиотекой) способа, которым я знаю, чтобы достичь чего-то вроде
Stream.from(1) takeWhile (factorial(_) <= Long.MaxValue) last
Подход, который я использую для продвижения Iterator
для определенного количества элементов, - это drop(n: Int)
или dropWhile
:
Iterator.from(1).dropWhile( factorial(_) <= Long.MaxValue).next - 1
- 1
работает для этой специальной цели, но не является общим решением. Но это не должно быть проблемой для реализации last
метода на Iterator
используя pimp my library. Проблема с получением последнего элемента бесконечного итератора может быть проблематичной. Поэтому он должен быть реализован как метод lastWith
интеграцией takeWhile
.
Ужасный обходной путь может быть сделан с помощью sliding
, которое реализовано для Iterator
:
scala> Iterator.from(1).sliding(2).dropWhile(_.tail.head < 10).next.head
res12: Int = 9
как указал @ziggystar, Streams
хранит список ранее вычисленных значений в памяти, поэтому использование Iterator
- отличное улучшение.
для дальнейшего улучшения ответа, я бы сказал, что "бесконечные потоки" обычно вычисляются (или могут быть вычислены) на основе предварительно вычисленных значений. если это так (и в вашем факториальном потоке это определенно есть), я бы предложил вместо этого использовать Iterator.iterate
.
будет выглядеть примерно так:
scala> val it = Iterator.iterate((1,BigInt(1))){case (i,f) => (i+1,f*(i+1))}
it: Iterator[(Int, scala.math.BigInt)] = non-empty iterator
тогда вы можете сделать что-то вроде:
scala> it.find(_._2 >= Long.MaxValue).map(_._1).get - 1
res0: Int = 22
или используйте решение @ziggystar sliding
...
еще один простой пример, который приходит на ум, - это число фибоначчи:
scala> val it = Iterator.iterate((1,1)){case (a,b) => (b,a+b)}.map(_._1)
it: Iterator[Int] = non-empty iterator
в этих случаях, вы каждый раз не вычисляете свой новый элемент с нуля, а скорее выполняете O (1) для каждого нового элемента, что еще больше улучшит ваше время выполнения.
Оригинальная "факториальная" функция не является оптимальной, поскольку факториалы каждый раз вычисляются с нуля. Простейшая/неизменная реализация, использующая запоминание, выглядит следующим образом:
val f : Stream[BigInt] = 1 #:: (Stream.from(1) zip f).map { case (x,y) => x * y }
И теперь ответ может быть вычислен так:
println( "count: " + (f takeWhile (_<Long.MaxValue)).length )
Следующий вариант проверяет не текущее, а следующее целое число, чтобы найти и вернуть последнее действительное число:
Iterator.from(1).find(i => factorial(i+1) > Long.MaxValue).get
Использование .get
здесь допустимо, так как find
для бесконечной последовательности никогда не вернет None
.