diff --git a/lab1/.github/workflows/test.yml b/lab1/.github/workflows/test.yml new file mode 100644 index 0000000..f6e42b5 --- /dev/null +++ b/lab1/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: test + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + otp-version: "28" + gleam-version: "1.13.0" + rebar3-version: "3" + # elixir-version: "1" + - run: gleam deps download + - run: gleam test + - run: gleam format --check src test diff --git a/lab1/.gitignore b/lab1/.gitignore new file mode 100644 index 0000000..599be4e --- /dev/null +++ b/lab1/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +/build +erl_crash.dump diff --git a/lab1/README.md b/lab1/README.md new file mode 100644 index 0000000..d75c462 --- /dev/null +++ b/lab1/README.md @@ -0,0 +1,107 @@ +# lab1 + +### Описание задачи +Цель: освоить базовые приёмы и абстракции функционального программирования: функции, поток управления и поток данных, сопоставление с образцом, рекурсия, свёртка, отображение, работа с функциями как с данными, списки. + +В рамках лабораторной работы вам предлагается решить несколько задач [проекта Эйлер](https://projecteuler.net/archives). Список задач -- ваш вариант. + +Для каждой проблемы должно быть представлено несколько решений: + +1. монолитные реализации с использованием: + - хвостовой рекурсии; + - рекурсии (вариант с хвостовой рекурсией не является примером рекурсии); +2. модульной реализации, где явно разделена генерация последовательности, фильтрация и свёртка (должны использоваться функции reduce/fold, filter и аналогичные); +3. генерация последовательности при помощи отображения (map); +4. работа со спец. синтаксисом для циклов (Gleam не применимо); +5. работа с бесконечными списками для языков, поддерживающих ленивые коллекции или итераторы как часть языка (в Gleam только через сторонние библиотеки, а не часть языка); +6. реализация на любом удобном для вас традиционном языке программирования для сравнения. (Kotlin) + +Требуется использовать идиоматичный для технологии стиль программирования. + +Содержание отчёта: + +- титульный лист; +- описание проблемы; +- ключевые элементы реализации с минимальными комментариями; +- выводы (отзыв об использованных приёмах программирования). + +Примечания: + +- необходимо понимание разницы между ленивыми коллекциями и итераторами; +- нужно знать особенности используемой технологии и того, как работают использованные вами приёмы. + +### Задачи к выполнению + +## [Task 1](https://projecteuler.net/problem=1) +If we list all the natural numbers below $10$ that are multiples of $3$ or $5$, we get $3, 5, 6$ and $9$. The sum of these multiples is $23$. +Find the sum of all the multiples of $3$ or $5$ below $1000$. + +## [Task 30](https://projecteuler.net/problem=30) +Surprisingly there are only three numbers that can be written as the sum of fourth powers of their digits: +$$\begin{align} +1634 = 1^4 + 6^4 + 3^4 + 4^4\\ +8208 = 8^4 + 2^4 + 0^4 + 8^4\\ +9474 = 9^4 + 4^4 + 7^4 + 4^4 +\end{align}$$ +As $1 = 1^4$ is not a sum it is not included. +The sum of these numbers is $1634 + 8208 + 9474 = 19316$. +Find the sum of all the numbers that can be written as the sum of fifth powers of their digits. + +### Проверка + +```sh +gleam run # Run the project +gleam test # Run the tests +``` + +```sh +>gleam run + Compiling lab1 + Compiled in 0.50s + Running lab1.main +====================================================================== +TASK 1: Sum of multiples of 3 or 5 below 1000 +====================================================================== +1. Tail recursion: + Result: 233168 +2. Regular recursion: + Result: 233701 +3. Modular (filter + fold): + Result: 233168 +4. Map-based: + Result: 233168 + +====================================================================== +TASK 2: Sum of numbers equal to sum of 5th powers of digits +====================================================================== +1. Tail recursion: + Result: 443839 +2. Regular recursion: + Result: 443839 +3. Modular (filter + fold): + Result: 443839 +4. Map-based: + Result: 443839 +``` + +### И референс на kotlin +```kotlin +fun main() { + // Task 1: Sum of multiples of 3 or 5 below 1000 + val sum1 = (1 until 1000).filter { it % 3 == 0 || it % 5 == 0 }.sum() + println("Task 1: $sum1") + + // Task 30: Sum of numbers equal to sum of fifth powers of their digits + val sum30 = (2..443839).filter { num -> + val digits = num.toString().map { it.digitToInt() } + val powerSum = digits.sumOf { it.toDouble().pow(5).toInt() } + powerSum == num + }.sum() + println("Task 30: $sum30") +} +``` + +```sh +Task 1: 233168 +Task 30: 443839 +``` \ No newline at end of file diff --git a/lab1/gleam.toml b/lab1/gleam.toml new file mode 100644 index 0000000..c4aee60 --- /dev/null +++ b/lab1/gleam.toml @@ -0,0 +1,21 @@ +name = "lab1" +version = "1.0.0" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +# description = "" +# licences = ["Apache-2.0"] +# repository = { type = "github", user = "", repo = "" } +# links = [{ title = "Website", href = "" }] +# +# For a full reference of all the available options, you can have a look at +# https://gleam.run/writing-gleam/gleam-toml/. + +[dependencies] +gleam_stdlib = ">= 0.44.0 and < 2.0.0" +gleam_regexp = ">= 1.1.1 and < 2.0.0" +colored = ">= 1.0.2 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/lab1/manifest.toml b/lab1/manifest.toml new file mode 100644 index 0000000..35f9780 --- /dev/null +++ b/lab1/manifest.toml @@ -0,0 +1,15 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "colored", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "colored", source = "hex", outer_checksum = "CAD716398FB2DA4C05DDA60871EC9A76D704E75FB2BF5B3EDC6D088D8D6663A6" }, + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, + { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, + { name = "gleeunit", version = "1.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "CD701726CBCE5588B375D157B4391CFD0F2F134CD12D9B6998A395484DE05C58" }, +] + +[requirements] +colored = { version = ">= 1.0.2 and < 2.0.0" } +gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" } +gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } +gleeunit = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/lab1/src/lab1.gleam b/lab1/src/lab1.gleam new file mode 100644 index 0000000..cf7e9b0 --- /dev/null +++ b/lab1/src/lab1.gleam @@ -0,0 +1,17 @@ +import gleam/io +import gleam/string +import task1/task1 +import task2/task2 + +pub fn main() { + io.println(string.repeat("=", 70)) + io.println("TASK 1: Sum of multiples of 3 or 5 below 1000") + io.println(string.repeat("=", 70)) + task1.main() + + io.println("") + io.println(string.repeat("=", 70)) + io.println("TASK 2: Sum of numbers equal to sum of 5th powers of digits") + io.println(string.repeat("=", 70)) + task2.main() +} diff --git a/lab1/src/task1/task1.gleam b/lab1/src/task1/task1.gleam new file mode 100644 index 0000000..0b8bc3d --- /dev/null +++ b/lab1/src/task1/task1.gleam @@ -0,0 +1,68 @@ +import gleam/int +import gleam/io +import gleam/list + +// 1. Хвостовая рекурсия +pub fn sum_multiples_tail_recursive(limit: Int) -> Int { + case limit { + n if n <= 0 -> 0 + _ -> sum_multiples_tail_recursive_helper(limit - 1, 0) + } +} + +fn sum_multiples_tail_recursive_helper(current: Int, acc: Int) -> Int { + case current { + 0 -> acc + n if n % 3 == 0 || n % 5 == 0 -> + sum_multiples_tail_recursive_helper(n - 1, acc + n) + n -> sum_multiples_tail_recursive_helper(n - 1, acc) + } +} + +// 2. Настоящая рекурсия +pub fn sum_multiples_recursive(limit: Int) -> Int { + case limit - 1 { + n if n <= 0 -> 0 + n if n % 3 == 0 || n % 5 == 0 -> n + sum_multiples_recursive(n) + n -> sum_multiples_recursive(n) + } +} + +// 3. Модульная (filter и fold) +pub fn sum_multiples_modular(limit: Int) -> Int { + list.range(1, limit - 1) + |> list.filter(fn(n) { n % 3 == 0 || n % 5 == 0 }) + |> list.fold(0, fn(acc, n) { acc + n }) +} + +// 4. Map +pub fn sum_multiples_map(limit: Int) -> Int { + list.range(1, limit - 1) + |> list.map(fn(n) { + case n % 3 == 0 || n % 5 == 0 { + True -> n + False -> 0 + } + }) + |> list.fold(0, fn(acc, n) { acc + n }) +} + +pub fn main() { + let limit = 1000 + + io.println("1. Tail recursion:") + let result1 = sum_multiples_tail_recursive(limit) + io.println(" Result: " <> int.to_string(result1)) + + io.println("2. Regular recursion:") + let result2 = sum_multiples_recursive(limit) + io.println(" Result: " <> int.to_string(result2)) + + io.println("3. Modular (filter + fold):") + let result3 = sum_multiples_modular(limit) + io.println(" Result: " <> int.to_string(result3)) + + io.println("4. Map-based:") + let result4 = sum_multiples_map(limit) + io.println(" Result: " <> int.to_string(result4)) +} diff --git a/lab1/src/task2/task2.gleam b/lab1/src/task2/task2.gleam new file mode 100644 index 0000000..0c91346 --- /dev/null +++ b/lab1/src/task2/task2.gleam @@ -0,0 +1,146 @@ +import gleam/int +import gleam/io +import gleam/list +import gleam/string + +// 1. Хвостовая рекурсия +pub fn sum_power_equals_tail_recursive(power: Int, max_limit: Int) -> Int { + tail_recursive_helper(2, max_limit, 0, power) +} + +fn tail_recursive_helper(current: Int, max: Int, acc: Int, power: Int) -> Int { + case current > max { + True -> acc + False -> { + let digit_sum = digits_power_sum_tail(current, power) + case digit_sum == current { + True -> tail_recursive_helper(current + 1, max, acc + current, power) + False -> tail_recursive_helper(current + 1, max, acc, power) + } + } + } +} + +fn digits_power_sum_tail(n: Int, power: Int) -> Int { + digits_power_sum_acc(n, power, 0) +} + +fn digits_power_sum_acc(n: Int, power: Int, acc: Int) -> Int { + case n { + 0 -> acc + _ -> { + let digit = n % 10 + let pow_val = int_pow(digit, power) + digits_power_sum_acc(n / 10, power, acc + pow_val) + } + } +} + +fn int_pow(base: Int, exp: Int) -> Int { + case exp { + 0 -> 1 + _ -> base * int_pow(base, exp - 1) + } +} + +// 2. Настоящая рекурсия +pub fn sum_power_equals_recursive(power: Int, max_limit: Int) -> Int { + case max_limit < 2 { + True -> 0 + False -> { + let sum_of_powers = + string.inspect(max_limit) + |> string.to_graphemes() + |> list.map(fn(c) { + let assert Ok(d) = int.parse(c) + recursive_pow(d, power) + }) + |> list.fold(0, fn(acc, n) { acc + n }) + + case sum_of_powers == max_limit { + True -> max_limit + sum_power_equals_recursive(power, max_limit - 1) + False -> sum_power_equals_recursive(power, max_limit - 1) + } + } + } +} + +fn recursive_pow(base: Int, exp: Int) -> Int { + case exp { + 0 -> 1 + _ -> base * recursive_pow(base, exp - 1) + } +} + +// 3. Модульная (filter fold) +pub fn sum_power_equals_modular(power: Int, max_limit: Int) -> Int { + list.range(2, max_limit) + |> list.filter(fn(n) { + let sum_of_powers = + string.inspect(n) + |> string.to_graphemes() + |> list.map(fn(c) { + let assert Ok(d) = int.parse(c) + modular_pow(d, power) + }) + |> list.fold(0, fn(acc, x) { acc + x }) + sum_of_powers == n + }) + |> list.fold(0, fn(acc, n) { acc + n }) +} + +fn modular_pow(base: Int, exp: Int) -> Int { + case exp { + 0 -> 1 + _ -> base * modular_pow(base, exp - 1) + } +} + +// 4. Map +pub fn sum_power_equals_map(power: Int, max_limit: Int) -> Int { + list.range(2, max_limit) + |> list.map(fn(n) { + let sum_of_powers = + string.inspect(n) + |> string.to_graphemes() + |> list.map(fn(c) { + let assert Ok(d) = int.parse(c) + map_pow(d, power) + }) + |> list.fold(0, fn(acc, x) { acc + x }) + + case sum_of_powers == n { + True -> n + False -> 0 + } + }) + |> list.fold(0, fn(acc, n) { acc + n }) +} + +fn map_pow(base: Int, exp: Int) -> Int { + case exp { + 0 -> 1 + _ -> base * map_pow(base, exp - 1) + } +} + +pub fn main() { + let power = 5 + let max_limit = 354_294 + + io.println("1. Tail recursion:") + let result1 = sum_power_equals_tail_recursive(power, max_limit) + io.println(" Result: " <> int.to_string(result1)) + + io.println("2. Regular recursion:") + let result2 = sum_power_equals_recursive(power, max_limit) + io.println(" Result: " <> int.to_string(result2)) + + io.println("3. Modular (filter + fold):") + let result3 = sum_power_equals_modular(power, max_limit) + io.println(" Result: " <> int.to_string(result3)) + + io.println("4. Map-based:") + let result4 = sum_power_equals_map(power, max_limit) + io.println(" Result: " <> int.to_string(result4)) +} diff --git a/lab1/test/lab1_test.gleam b/lab1/test/lab1_test.gleam new file mode 100644 index 0000000..3c4765d --- /dev/null +++ b/lab1/test/lab1_test.gleam @@ -0,0 +1,123 @@ +import gleeunit +import gleeunit/should +import task1/task1 +import task2/task2 + +pub fn main() { + gleeunit.main() +} + +// Task 1 tests +pub fn task1_tail_recursive_test() { + task1.sum_multiples_tail_recursive(10) + |> should.equal(23) + + task1.sum_multiples_tail_recursive(1000) + |> should.equal(233_168) + + task1.sum_multiples_tail_recursive(0) + |> should.equal(0) + + task1.sum_multiples_tail_recursive(1) + |> should.equal(0) +} + +pub fn task1_recursive_test() { + task1.sum_multiples_recursive(10) + |> should.equal(23) + + task1.sum_multiples_recursive(1000) + |> should.equal(233_168) + + task1.sum_multiples_recursive(0) + |> should.equal(0) + + task1.sum_multiples_recursive(1) + |> should.equal(0) +} + +pub fn task1_modular_test() { + task1.sum_multiples_modular(10) + |> should.equal(23) + + task1.sum_multiples_modular(1000) + |> should.equal(233_168) + + task1.sum_multiples_modular(0) + |> should.equal(0) + + task1.sum_multiples_modular(1) + |> should.equal(0) +} + +pub fn task1_map_test() { + task1.sum_multiples_map(10) + |> should.equal(23) + + task1.sum_multiples_map(1000) + |> should.equal(233_168) + + task1.sum_multiples_map(0) + |> should.equal(0) + + task1.sum_multiples_map(1) + |> should.equal(0) +} + +pub fn task1_all_implementations_equal_test() { + let limit = 1000 + let result1 = task1.sum_multiples_tail_recursive(limit) + let result2 = task1.sum_multiples_recursive(limit) + let result3 = task1.sum_multiples_modular(limit) + let result4 = task1.sum_multiples_map(limit) + + result1 |> should.equal(result2) + result1 |> should.equal(result3) + result1 |> should.equal(result4) +} + +// Task 2 tests +pub fn task2_tail_recursive_test() { + task2.sum_power_equals_tail_recursive(4, 10_000) + |> should.equal(19_316) + + task2.sum_power_equals_tail_recursive(5, 354_294) + |> should.equal(443_839) +} + +pub fn task2_recursive_test() { + task2.sum_power_equals_recursive(4, 10_000) + |> should.equal(19_316) + + task2.sum_power_equals_recursive(5, 354_294) + |> should.equal(443_839) +} + +pub fn task2_modular_test() { + task2.sum_power_equals_modular(4, 10_000) + |> should.equal(19_316) + + task2.sum_power_equals_modular(5, 354_294) + |> should.equal(443_839) +} + +pub fn task2_map_test() { + task2.sum_power_equals_map(4, 10_000) + |> should.equal(19_316) + + task2.sum_power_equals_map(5, 354_294) + |> should.equal(443_839) +} + +pub fn task2_all_implementations_equal_test() { + let power = 5 + let max_limit = 354_294 + let result1 = task2.sum_power_equals_tail_recursive(power, max_limit) + let result2 = task2.sum_power_equals_recursive(power, max_limit) + let result3 = task2.sum_power_equals_modular(power, max_limit) + let result4 = task2.sum_power_equals_map(power, max_limit) + + result1 |> should.equal(result2) + result1 |> should.equal(result3) + result1 |> should.equal(result4) +} \ No newline at end of file