
8.4 Еще раз о числах Фибоначчи
Интересно, какую выгоду нам могут дать бесконечные потоки, применимые рядам чисел, поучаемых рекуррентным способом?
Как известно, последовательность чисел Фибоначчи начинается с двух единиц
Остальная её часть может быть выражена как сумма последовательности Фибоначчи и этой же последовательности, смещенной на один шаг, т.е.
1 |
1 |
2 |
3 |
5 |
8 |
13 |
... |
Fn |
... |
|
1 |
1 |
2 |
3 |
5 |
8 |
... |
Fn-1 |
... |
|
|
1 |
1 |
2 |
3 |
5 |
... |
Fn-2 |
... |
По аналогии с потоком целых чисел, мы можем определить бесконечный поток чисел Фибоначчи и написать новую версию процедуры fib. Он определяется как сумма двух бесконечных потоков.
Сначала определим построение потока:
>(define fibST
(cons-stream 1
(cons-stream 1
(stream-add fibST (tail fibST) ))))
Теперь новое определение чисел Фибоначчи:
> (define (fibS n) (elem-no n fibST))
Смотрим, сколько времени затрачивается на получение числа Фибоначчи для n=100000
>(time (fibS 10000))
cpu time: 32 real time: 31 gc time: 0
Нет возможности привести это число, поскольку его длина займет не одну страницу
>(string-length (number->string (fib 100000)))
20897
Более интересно посмотреть на время его получения по ранее рассмотренной хвостовой рекурсии
>(define (fib-iter k ks count)
(cond ((= count 0) k)
(else (fib-iter ks (+ k ks) (- count 1)))))
>(define (fib n) (fib-iter 1 1 (- n 1)))
>(time (fib 10000))
cpu time: 15 real time: 15 gc time: 0
Заметим, что времена где-то одного порядка. НО ! Чтобы получить итерационную процедуру нам пришлось «покумекать», а реализация бесконечным потоком получилась как-то сама собой !
8.5 Простые числа
Рассмотрим последний пример организации бесконечного потока на задаче получения множества простых чисел.
Первый вариант.
Пусть критерием простоты числа n будет такое условие: если перебирая все числа от 2 до (ВАЖНО!) корня из n, мы не найдем ни одного числа на которое это n делится – оно простое.
>(define (prime? n)
(nodiv? 2 n (sqrt n)))
>(define (nodiv? m n limit)
(or (> m limit)
(and (not (= (remainder n m) 0)) ; не может быть четно
(nodiv? (+ m 1) n limit))))
> (prime? 123)
#f
> (prime? 101)
#t
Теперь множество простых чисел можно построить, отфильтровав бесконечный поток целых чисел с помощью написанного предиката prime?.
> (define primeSD
(stream-filter prime? (integers-from 2)))
Второй вариант. Наиболее «функциональный». Другой бесконечный поток.
Сначала изменим критерий простоты числа, чтобы проверять только делимость на простые числа.
> (define (primed? n)
(define (nodiv? s limit)
(or (> (head s) limit)
(and (not (= (remainder n (head s)) 0))
(nodiv? (tail s) limit))))
(nodiv? primeSD (sqrt n)))
> (primed? 127)
#t
> (primed? 125)
#f
И вот сам поток.
> (define primeSD
(cons-stream 2 (stream-filter primed? (integers-from 3))))
Это определение дважды рекурсивно, так как поток primeSD определяется через предикат primed?, который в свою очередь использует primeSD. Но в любой момент созданной части потока достаточно для проверки простоты очередного числа.
И, наконец, сравнения по времени выполнения
>(time (primed? 127691))