Новая группа задач, в которой будем использовать стандартную конструкцию цикла со счетчиком во многих языка обозначается через FOR (именно этим словом обозначим примеры этой серии). В Форте описывается двумя словами "DO" и "LOOP". Первое берет со стека два числа - границы параметра цикла, второе увеличивает счетчик на единицу и заканчивает итерацию. Сперва идет верхняя граница, затем нижняя включительно. По определению цикл заканчивается, когда параметр цикла перейдет через интервал «верхняя граница минус один» - «верхняя граница».
Первый пример. Вывести данное целое число N раз.
: FOR1 ( K N>0 -> ) 0 \ K N>0 -> K N>0 0, - добавляем ноль, нижнюю границу
DO \ K N>0 0 -> K, «DO» сняло со стека два числа – «границы диапазона цикла»
DUP . \ K -> K – тело цикла просто дублируем и печатаем на экран
LOOP
DROP ; \ удаляем "K" очищаем стек после себя
Слово "DO" забирает со стека два верхних числа (N>0 0), остается только "K", которое в теле цикла дублируется и печатается от нуля до "N-1" раз, т. е. N раз ровно.
Тест слова "FOR1":
100 5 FOR1
100 100 100 100 100 Ok
Написанное слово работает соответственно условию задачи.
В примере два печатаем все числа от A до B включительно (A<B) и количество этих чисел.
: FOR2 ( A B -> ) \ {A<B}
1+ SWAP \ A B -> B+1 A
2DUP - \ B+1 A -> B+1 A B+1-A=N
." N= " . CR \ B+1 A N -> B+1 A - печатаем N
DO
I . \ в цикле печатаем каждую итерацию от A до B
LOOP ;
К "B" прибавляем один и меняем местами с "A", это нужно для корректной работы "DO". Слово "2DUP" дублирует два верхних элемента, а их разница дает значение "N", которое печатает четвертая строчка. Далее в цикле слово "I" перебирает все числа от A до B и они печатаются здесь же.
Тест слова "FOR2":
11 17 FOR2
N= 7
11 12 13 14 15 16 17 Ok
В примере три выводим числа в порядке убывания от B до A не включительно и их количество. Для этого используем модифицированный вариант цикла с параметром "DO <код> -1 +LOOP". Слово "+LOOP" берет со стека число, в данном случае "-1", и прибавляет к счетчику, таким образом мы перебираем числа не по возрастанию, а по убыванию (в общем случае вместо «-1» может быть любое целое число). И границы так же будут в обратном порядке (начинаем с большего B-1 до меньшего A+1). Уменьшаем «B» на единицу – нижняя граница (не включительно) и здесь же вычисляем «N» которое равно «B-1-A» и на следующей строчке печатаем это. Далее готовим границы параметра цикла, увеличив «A» на единицу. Диапазон («A-1»-1; «A-1») = (11;12) цикл перепрыгивает и останавливается на «11» (как указано по условию), не включительно «A» (для этого и было необходимо вычесть единицу из «A»).
: FOR3 ( A B -> ) \ {A<B}
1- 2DUP SWAP - \ A B -> A B-1 B-1-A=N
." N= " . CR \ A B-1 N -> A B-1, - печатаем N
SWAP 1+ SWAP \ A B-1 -> A+1 B-1
DO
I . -1
+LOOP ;
Тест примера "FOR3" демонстрирует его корректную работу:
11 17 FOR3
N= 5
16 15 14 13 12 Ok
Пример четыре. В цикле печатаем цену от одного до десяти кг конфет. Просто прогоняем цикл десять раз (от 1 до 10), а внутри умножаем цену на «I» параметр цикла, получив соответствующую стоимость «I» кг конфет, распечатав результат.
: FOR4 ( R -> ) \ 1.1E FOR4 –формат работы слова
11 1 DO
." Стоимость " I . ." кг конфет: "
FDUP \ дублируем «R» цену конфет
I 0 D>F \ «I» превращаем в число двойной точности и отправляем на вещественный стек
F* F. CR \ умножаем «R» на «I» и печатаем 1*R 2*R ... 10*R
LOOP FDROP ; \ удаляем из вещественного стека «R», очищаем стек после себя
Тест для цены конфет 1.1 за кг:
1.1E FOR4
Стоимость 1 кг конфет: 1.1000000
Стоимость 2 кг конфет: 2.2000000
Стоимость 3 кг конфет: 3.3000000
Стоимость 4 кг конфет: 4.4000000
Стоимость 5 кг конфет: 5.5000000
Стоимость 6 кг конфет: 6.6000000
Стоимость 7 кг конфет: 7.7000000
Стоимость 8 кг конфет: 8.8000000
Стоимость 9 кг конфет: 9.9000000
Стоимость 10 кг конфет: 11.000000
Ok
А теперь второй вариант этого же примера (без умножения):
: FOR4 ( R -> ) \ 1.1E FOR4 – формат работы слова
0E \ добавляем «0» на вещественный стек, в качестве сумматора
11 1 DO
." Стоимость " I . ." кг конфет: "
FOVER F+ \ R Sum -> R Sum+R
FDUP F. CR \ дублируем и печатаем (Sum+R), т. е. 1*R 2*R ... 10*R
LOOP FDROP FDROP ; \ удаляем «R» и «Sum»
Тест дает идентичный результат, проверяйте самостоятельно. Второй вариант предпочтительней, так как сложение более быстрая операция, чем умножение и на больших количествах итераций – это может быть ощутимо по времени.
Пример пять. Получаем из предыдущего примера делением цены на десять (здесь итератор «I» делим на десять, получаем тот же эффект).
: FOR5 ( R -> ) \ 1.2E FOR5 – формат работы слова
11 1 DO
FDUP I 0 D>F 10E F/ FDUP \ R -> R R 0.1*I
." Стоимость " F. ." кг конфет: "
F* F. CR \ Стоимость 0.1*R 0.2*R ... 1*R кг конфет
LOOP ;
Тест дает следующий результат:
1.2E FOR5
Стоимость 0.1000000 кг конфет: 0.1200000
Стоимость 0.2000000 кг конфет: 0.2400000
Стоимость 0.3000000 кг конфет: 0.3600000
Стоимость 0.4000000 кг конфет: 0.4800000
Стоимость 0.5000000 кг конфет: 0.6000000
Стоимость 0.6000000 кг конфет: 0.7200000
Стоимость 0.7000000 кг конфет: 0.8400000
Стоимость 0.8000000 кг конфет: 0.9600000
Стоимость 0.9000000 кг конфет: 1.0800000
Стоимость 1.0000000 кг конфет: 1.2000000
Ok
Можете самостоятельно переписать код с суммой, как в предыдущем случае, таким образом значительно уменьшив количество операций в цикле.
Пример шесть. Продолжение серии с другим шагом. Все тоже самое, меняем только пределы и шаг итерации. Тело цикла один в один как в примере пять.
: FOR6 ( R ->) ( 1.2E FOR6 )
22 12 DO
FDUP I 0 D>F 10E F/ FDUP ." Стоимость " F. ." кг конфет: "
F* F. CR \ Стоимость 1.2*R 1.4*R ... 2*R кг конфет
2 +LOOP ;
Так же самостоятельно оптимизируйте код.
Пример семь. Вычисляем сумму чисел от «A» до «B» включительно.
: FOR7 ( A B -> ) \ A<B
1+ SWAP \ A B -> B+1 A
0 ROT ROT \ B+1 A -> 0 B+1 A – пределы цикла, «0» начальное значение суммы
DO
I +
LOOP . ;
Во второй строке из «A» и «B» получаем пределы цикла, добавляем к «B» один, чтобы получить верхний предел включительно и меняем их местами, так как по синтаксису Форт сначала идет верхний предел, а затем нижний. В третьей добавляем ноль на стек – сумма где будет хранится итоговый результат, и выносим пределы цикла на «верх» для запуска цикла, который начинается со строки четыре и заканчивается в шестой, после «LOOP» выводим результат (оператор точка «.»). Тело цикла – пятая строка просто значение параметра цикла «I» прибавляет к текущей сумме.
Так как сумма чисел от «A» до «B» включительно, это арифметическая прогрессия, то ее можно вычислить по формуле (a1+an)*n/2. Пример «FOR7» можем переписать:
: FOR7 ( A B -> ) \ A<B
2DUP 1+ SWAP - \ A B -> A B B+1-A=n
ROT ROT + * 2/ \ A B n -> n*(A+B)/2
. ; \ конец – вывод результата
Так как это серия примеров с циклом, то в целях обучения пригоден только первый вариант, но полезно знать и про альтернативу, ибо в первом может производиться большое количество сложений, а во втором постоянное количество операций.
Оба варианты дают одинаковый результат на тестовых данных:
1 10 FOR7
55 Ok
1 100 FOR7
5050 Ok
1 1000 FOR7
500500 Ok
1 10000 FOR7
50005000 Ok
1 100000 FOR7
705082704 Ok
Обратим внимание на то, что для суммы от 1 до 100 000, уже не хватает обычных 32 бит, результат 5 000 050 000 не помещается в нем, в итоге видим неправильный результат. Так что будьте осторожны в своих экспериментах. Некорректный результат – это еще не признак неправильности кода. В данном случае можем работать с числами двойной длины, но и у них есть свои ограничения по размеру.
Пример восемь. Получаем из предыдущего заменой операции сложения на умножение и ноль сумматора заменяем на единицу (начальное значение итогового произведения).
: FOR8 ( A B -> ) \ A<B
1+ SWAP \ A B -> B+1 A
1 ROT ROT \ B+1 A -> 1 B+1 A – пределы цикла, «1» итоговое произведение
DO
I *
LOOP . ;
Тест восьмого примера:
1 3 FOR8
6 Ok
1 4 FOR8
24 Ok
1 5 FOR8
120 Ok
1 6 FOR8
720 Ok
10 15 FOR8
3603600 Ok
Если ввести первый параметр равный единице, то получим факториал второго предела.
Пример девять. Теперь вычисляем сумму квадратов чисел от «A» до «B» включительно. Так же, как и в предыдущем случае получаем из примера семь. Только суммируем уже квадраты чисел между «A» и «B».
: FOR9 ( A B -> ) \ A<B
1+ SWAP \ A B -> B+1 A
0 ROT ROT \ B+1 A -> 0 B+1 A – пределы цикла, «0» сумма
DO
I DUP * + \ Sum= Sum+I^2
LOOP . ;
Тест для примера девять:
1 1 FOR9
1 Ok
1 2 FOR9
5 Ok
1 3 FOR9
14 Ok
1 4 FOR9
30 Ok
1 5 FOR9
55 Ok
1 6 FOR9
91 Ok
Пример десять. Вычисляем сумму обратных чисел натурального ряда, результат само собой получаем как вещественное.
: FOR10 ( I: N>0 -> ) \ 1+1/2+1/3+1/N
1+ 1 \ I: N -> N+1 1 – пределы цикла на целочисленном стеке от 1 до N
0E \ F: 0 = Sum –сумма ряда
DO
1E I 0 D>F F/ F+ \ F: Sum=Sum+1/I
LOOP F. ;
Тест вышенаписанного кода:
1 FOR10
1.0000000 Ok
2 FOR10
1.5000000 Ok
3 FOR10
1.8333333 Ok
4 FOR10
2.0833333 Ok
5 FOR10
2.2833333 Ok
6 FOR10
2.4500000 Ok
7 FOR10
2.5928571 Ok