https://github.com/aalexuser/functional-programming-3
Лабораторная работа 3 по функциональному программированию (ИТМО, ПИиКТ-СППО, 4 курс)
https://github.com/aalexuser/functional-programming-3
Last synced: 21 days ago
JSON representation
Лабораторная работа 3 по функциональному программированию (ИТМО, ПИиКТ-СППО, 4 курс)
- Host: GitHub
- URL: https://github.com/aalexuser/functional-programming-3
- Owner: AaLexUser
- Created: 2024-10-29T17:06:08.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-10-29T18:42:42.000Z (over 1 year ago)
- Last Synced: 2024-10-29T18:49:28.379Z (over 1 year ago)
- Language: Clojure
- Size: 7.81 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Лабораторная работа №3
## Дисциплина
Функциональное программирование
## Выполнил
Лапин Алексей Александрович, P34102
## Цель работы
Получить навыки работы с вводом/выводом, потоковой обработкой данных, командной строкой.
## Требования
- обязательно должна быть реализована линейная интерполяция (отрезками, [link](https://en.wikipedia.org/wiki/Linear_interpolation));
- настройки алгоритма интерполяции и выводимых данных должны задаваться через аргументы командной строки:
- какие алгоритмы использовать (в том числе два сразу);
- частота дискретизации результирующих данных;
- и т.п.;
- входные данные должны задаваться в текстовом формате на подобии ".csv" (к примеру `x;y\n` или `x\ty\n`) и подаваться на стандартный ввод, входные данные должны быть отсортированы по возрастанию x;
- выходные данные должны подаваться на стандартный вывод;
- программа должна работать в потоковом режиме (пример -- `cat | grep 11`), это значит, что при запуске программы она должна ожидать получения данных на стандартный ввод, и, по мере получения достаточного количества данных, должна выводить рассчитанные точки в стандартный вывод;
Приложение должно быть организовано следующим образом:
```text
+---------------------------+
| обработка входного потока |
+---------------------------+
|
| поток / список / последовательность точек
v
+-----------------------+ +------------------------------+
| алгоритм интерполяции |<-----| генератор точек, для которых |
+-----------------------+ | необходимо вычислить |
| | промежуточные значения |
| +------------------------------+
|
| поток / список / последовательность рассчитанных точек
v
+------------------------+
| печать выходных данных |
+------------------------+
```
Потоковый режим для алгоритмов, работающих с группой точек должен работать следующим образом:
```text
o o o o o o . . x x x
x x x . . o . . x x x
x x x . . o . . x x x
x x x . . o . . x x x
x x x . . o . . x x x
x x x . . o . . x x x
x x x . . o o o o o o EOF
```
где:
- каждая строка -- окно данных, на основании которых производится расчёт алгоритма;
- строки сменяются по мере поступления в систему новых данных (старые данные удаляются из окна, новые -- добавляются);
- `o` -- рассчитанные данные, можно видеть:
- большинство окон используется для расчёта всего одной точки, так как именно в "центре" результат наиболее точен;
- первое и последнее окно используются для расчёта большого количества точек, так лучших данных для расчёта у нас не будет.
- `.` -- точки, задействованные в рассчете значения `o`.
- `x` -- точки, расчёт которых для "окон" не требуется.
Пример вычислений для шага 1.0 и функции sin(x):
```text
Ввод первых двух точек (в данном примере X Y через пробел):
0 0.00
1.571 1
Вывод:
Линейная (идем от первой точки из введенных (0.00) с шагом 1, покрывая все введенные X (1.571 < 2)):
0.00 1.00 2.00
0.00 0.64 1.27
Ввод третьей точки:
3.142 0
Следующий вывод:
Линейная (идем от второй точки из введенных (1.571) с шагом 1, покрывая все введенные X (3.142 < 3.57)):
1.57 2.57 3.57
1.00 0.36 -0.27
Ввод четвертой точки:
4.712 -1
Следующий вывод:
Линейная (идем от третьей точки из введенных (3.142) с шагом 1, покрывая все введенные X (4.712 < 5.14)):
3.14 4.14 5.14
0.00 -0.64 -1.27
Лагранж (теперь количество введенных точек повзоляет его рассчитать, идем от первой точки (0.00) из введенных с шагом 1, покрывая все введенные X (4.712 < 5)):
0.00 1.00 2.00 3.00 4.00 5.00
0.00 0.97 0.84 0.12 -0.67 -1.03
Ввод пятой точки:
12.568 0
Следующий вывод:
Линейная (идем от четвертой точки из введенных (4.712) с шагом 1, покрывая все введенные X (12.568 < 12.71))):
4.71 5.71 6.71 7.71 8.71 9.71 10.71 11.71 12.71
-1.00 -0.87 -0.75 -0.62 -0.49 -0.36 -0.24 -0.11 0.02
Лагранж (идем от второй точки из введенных (1.571) с шагом 1, покрывая все введенные X (12.568 < 12.57))):
1.57 2.57 3.57 4.57 5.57 6.57 7.57 8.57 9.57 10.57 11.57 12.57
1.00 0.37 -0.28 -0.91 -1.49 -1.95 -2.26 -2.38 -2.25 -1.84 -1.11 0.00
И т.д.
```
Как видно из примера выше, окна для каждого метода двигаются по-разному. Для линейной окно начало сдвигаться уже при вводе третьей точки (т.к. для вычисления нужно всего две), в то время как для Лагранжа окно начало двигаться только когда была введена пятая точка (т.к. здесь для вычислений нужно больше точек).
Общие требования:
- программа должна быть реализована в функциональном стиле;
- ввод/вывод должен быть отделён от алгоритмов интерполяции;
- требуется использовать идиоматичный для технологии стиль программирования.
## Ход работы
### Структура проекта
```shell
├── README.md
├── src/
│ ├── core.clj
│ ├── input.clj
│ └── interpolation.clj
├── test/
│ └── interpolation_test.clj
│
├── .github/workflows/
│ └── clojure.yml
│
├── .gitingore
├── tests.edn
└── deps.edn
```
#### Парсер аргументов командной строки.
Парсер аргументов командной строки реализован с помощью библиотеки [clojure.tools.cli](https://github.com/clojure/tools.cli).
```clojure
(def cli-options
[["-a" "--algorithms ALGORITHMS" "Comma-separated list of interpolation algorithms (linear,lagrange)"
:default []
:parse-fn #(mapv str/lower-case (map str/trim (str/split % #",")))
:validate [#(every? algorithms %) "Unknown algorithm specified"]]
["-s" "--step STEP" "Data sampling frequency"
:parse-fn #(Double/parseDouble %)
:validate [#(pos? %) "Step must be positive"]
:default 1.0]
["-h" "--help"]])
```
В данном случае программа принимает на вход два аргумента:
- `-a` или `--algorithms` -- список алгоритмов, которые необходимо применить к входным данным;
- `-s` или `--step` -- шаг, с которым будут выводиться результаты.
#### Функция main
```clojure
(defn -main [& args]
(let [{:keys [algorithms step]} (parse-args args)]
(try
(let [points-seq (read-input-seq)
accumulated-points (reductions conj [] points-seq)]
(doseq [points accumulated-points]
(run-interpolation algorithms step points)))
(catch Exception e
(exit 1 (str "Error during interpolation: " (.getMessage e)))))))
```
#### Обработка входного потока.
```clojure
(defn parse-point [input]
(let [tokens (str/split input #"[,\t ;]+")]
(if (str/blank? input) (do (println "Blank input received. Exiting.") (System/exit 0))
(if (= (count tokens) 2)
(let [[x y] tokens]
(try
(->Point (Double/parseDouble x) (Double/parseDouble y))
(catch NumberFormatException e
(throw (ex-info "Both coordinates must be valid numbers." {:input input} e)))))
(throw (ex-info "Input must contain exactly two numerical values separated by comma, tab, space, or semicolon." {:input input}))))))
(defn read-input-seq
"Lazily reads input lines from standard input and parses them into Point records."
[]
(->> (line-seq (java.io.BufferedReader. *in*))
(map str/trim)
(map parse-point)
(filter some?)))
```
#### Функция генерации точек, для которых будут вычисляться значения.
```clojure
(defn generate-steps
"Generates a sequence of steps from x-min to x-max with the specified step size."
[x-min x-max step]
(let [steps (take-while #(<= % x-max)
(iterate #(+ % step) x-min))
last-step (+ (last steps) step)]
(if (= (last steps) x-max)
(vec steps)
(conj (vec steps) last-step))))
```
Функция `generate-steps` генерирует последовательность значений x, с заданным шагом.
#### Алгоритмы интерполяции:
- Линейная интерполяция
```clojure
(defn linear-interpolation
"Performs linear interpolation between the last two points for a given x."
[points x]
(let [[p0 p1] (take-last 2 points)
x0 (:x p0)
x1 (:x p1)
y0 (:y p0)
y1 (:y p1)]
(+ (* (- y1 y0) (/ (- x x0) (- x1 x0))) y0)))
```
- Интерполяция Лагранжа
```clojure
(defn lagrange-basis
"Calculates the Lagrange basis polynomial for a given point x."
[points i x]
(reduce
(fn [acc j]
(if (= i j)
acc
(/ (* acc (- x (:x (nth points j))))
(- (:x (nth points i)) (:x (nth points j))))))
1
(range (count points))))
(defn lagrange-interpolation
"Performs Lagrange interpolation between the last four points for a given x."
[points x]
(reduce
(fn [acc i]
(+ acc (* (lagrange-basis points i x) (:y (nth points i)))))
0
(range (count points))))
```
## Вывод программы
```shell
> clojure -M -m core -a linear,lagrange
0 0
1.571 1
Linear interpolation result:
0.00 1.00 2.00
0.00 0.64 1.27
3.142 0
Linear interpolation result:
1.57 2.57 3.57
1.00 0.36 -0.27
4.712 -1
Linear interpolation result:
3.14 4.14 5.14
0.00 -0.64 -1.27
Lagrange interpolation result:
0.00 1.00 2.00 3.00 4.00 5.00
0.00 0.97 0.84 0.12 -0.67 -1.03
12.568 0
Linear interpolation result:
4.71 5.71 6.71 7.71 8.71 9.71 10.71 11.71 12.71
-1.00 -0.87 -0.75 -0.62 -0.49 -0.36 -0.24 -0.11 0.02
Lagrange interpolation result:
1.57 2.57 3.57 4.57 5.57 6.57 7.57 8.57 9.57 10.57 11.57 12.57
1.00 0.37 -0.28 -0.91 -1.49 -1.95 -2.26 -2.38 -2.25 -1.84 -1.11 0.00
Blank input received. Exiting.
```
```shell
> clojure -M -m core -a linear,lagrange -s 0.5
0 0
1 1
Linear interpolation result:
0.00 0.50 1.00
0.00 0.50 1.00
2 2
Linear interpolation result:
1.00 1.50 2.00
1.00 1.50 2.00
3 3
Linear interpolation result:
2.00 2.50 3.00
2.00 2.50 3.00
Lagrange interpolation result:
0.00 0.50 1.00 1.50 2.00 2.50 3.00
0.00 0.50 1.00 1.50 2.00 2.50 3.00
4 4
Linear interpolation result:
3.00 3.50 4.00
3.00 3.50 4.00
Lagrange interpolation result:
1.00 1.50 2.00 2.50 3.00 3.50 4.00
1.00 1.50 2.00 2.50 3.00 3.50 4.00
Blank input received. Exiting.
```
## Заключение
В ходе выполнения лабораторной работы были достигнуты следующие результаты:
1. Реализована программа для интерполяции данных с поддержкой:
- Линейной интерполяции
- Интерполяции методом Лагранжа
- Потоковой обработки входных данных
- Настраиваемой частоты дискретизации
2. Получены практические навыки:
- Работы с потоковым вводом/выводом в Clojure
- Обработки аргументов командной строки
- Реализации алгоритмов интерполяции в функциональном стиле
3. Программа полностью соответствует заданным требованиям и демонстрирует эффективное применение функционального подхода к решению поставленной задачи.