Подтвердить что ты не робот

Бесконечные потоки в Scala

Скажем, у меня есть функция, например старая любимая

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)

но есть ли лучший способ?

(Я также знаю, что я мог бы получить решение рекурсивно, но это не то, что я ищу.)

4b9b3361

Ответ 1

Stream.from(1)

создает поток, начиная с 1 и увеличивая на 1. Все это в API документации.

Ответ 2

Решение с использованием итераторов

Вы также можете использовать 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

Ответ 3

как указал @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) для каждого нового элемента, что еще больше улучшит ваше время выполнения.

Ответ 4

Оригинальная "факториальная" функция не является оптимальной, поскольку факториалы каждый раз вычисляются с нуля. Простейшая/неизменная реализация, использующая запоминание, выглядит следующим образом:

val f : Stream[BigInt] = 1 #:: (Stream.from(1) zip f).map { case (x,y) => x * y }

И теперь ответ может быть вычислен так:

println( "count: " + (f takeWhile (_<Long.MaxValue)).length )

Ответ 5

Следующий вариант проверяет не текущее, а следующее целое число, чтобы найти и вернуть последнее действительное число:

Iterator.from(1).find(i => factorial(i+1) > Long.MaxValue).get

Использование .get здесь допустимо, так как find для бесконечной последовательности никогда не вернет None.