Table of Contents
- Студент:
Дворкин Борис Александрович
- Группа:
P3331
- ИСУ:
368090
- Функциональный язык программирования:
Elixir
...........
Finished in 26.6 seconds (0.00s async, 26.6s sync)
11 tests, 0 failures
Randomized with seed 71505
----------------
COV FILE LINES RELEVANT MISSED
100.0% //lib/task21/inf_lists_lazy_collections. 38 9 0
100.0% //lib/task21/modular.ex 37 7 0
100.0% //lib/task21/recursion.ex 32 11 0
100.0% //lib/task21/sequence.ex 34 7 0
100.0% //lib/task21/special_loops.ex 43 10 0
100.0% //lib/task21/tail_recursion.ex 32 11 0
100.0% //lib/task9/inf_lists_lazy_collections.e 20 10 0
100.0% //lib/task9/modular.ex 33 8 0
88.8% //lib/task9/recursion.ex 25 9 1
100.0% //lib/task9/special_loops.ex 25 3 0
87.5% //lib/task9/tail_recursion.ex 24 8 1
[TOTAL] 97.8%
----------------
- Название:
Special Pythagorean Triplet
- Описание:
A Pythagorean triplet is a set of three natural numbers,$a < b < c$ , for which
$$a^2 + b^2 = c^2$$
For example,$3^2 + 4^2 = 9 + 16 = 25 = 5^2$ .
There exists exactly one Pythagorean triplet for which$a + b + c = 1000$ . - Задание:
Find the product abc.
Конечно, задачу можно банально решить полным перебором, как я и сделал на
императивном языке Python
. Но в функциональных языках принято пользоваться
рекурсией и функциями высшего порядка, а также фильтрацией, генерацией и
другими концепциями, поэтому для различных ситуаций пришлось придумать другие
решения.
Для рекурсии идея состоит в следующем:
Установим
Теперь заметим, что
А именно:
- если
$b < c$ , то можно безопасно увеличить$b$ на 1 и вызвать функцию для этого значения.- Так как
$c = \text{sum} - a - b$ , то получаем условие$b < \text{sum} - a - b$ .
- Так как
- Иначе, это значит, что мы перебрали все возможные значения
$b$ для данного$a$ , поэтому "сбрасываем" значение$b$ , делая его на 1 больше, чем$a$ , так как$a < b < c$ .
defmodule Euler9Recursion do
@moduledoc """
Module handling recursive implementations for Euler problem 9.
"""
def find_triplet(sum) do
do_find_triplet(1, 2, sum)
end
defp do_find_triplet(a, b, sum) when a < sum / 3 do
c = sum - a - b
cond do
a * a + b * b == c * c ->
a * b * c
b < sum - a - b ->
do_find_triplet(a, b + 1, sum)
true ->
do_find_triplet(a + 1, a + 2, sum)
end
end
defp do_find_triplet(_, _, _), do: nil
end
- то есть особого вида рекурсией, где любой рекурсивный вызов - последний перед
возвратом из функции, что позволяет выполнять
tail call optimisation
defmodule Euler9TailRecursion do
@moduledoc """
Module handling tail-recursive implementations for Euler problem 9.
"""
def find_triplet(sum) do
do_find_triplet(1, 2, sum)
end
defp do_find_triplet(a, b, sum) when a < sum / 3 do
c = sum - a - b
if a * a + b * b == c * c do
a * b * c
else
if b < sum - a - b do
do_find_triplet(a, b + 1, sum)
else
do_find_triplet(a + 1, a + 2, sum)
end
end
end
defp do_find_triplet(_, _, _), do: nil
end
- Генерация последовательности при помощи отображения (map)
defmodule Euler9Modular do
@moduledoc """
Module handling Euler problem 9 with clear separation of generation, filtering, and folding.
"""
def find_triplet(sum) do
generate_triplets(sum)
|> filter_triplets()
|> fold_triplets()
end
defp generate_triplets(sum) do
1..(sum - 2)
|> Enum.flat_map(fn a ->
(a + 1)..(sum - a - 1)
|> Enum.map(fn b ->
c = sum - a - b
{a, b, c}
end)
end)
end
defp filter_triplets(triplets) do
triplets
|> Enum.filter(fn {a, b, c} -> a * a + b * b == c * c end)
end
defp fold_triplets(triplets) do
Enum.reduce(triplets, nil, fn {a, b, c}, _acc ->
a * b * c
end)
end
end
- зачем тут flat_map? Если сделать просто map, вот, что будет:
[
[{1, 2, 997}, {1, 3, 996}, {1, 4, 995}, ...],
[{2, 3, 995}, {2, 4, 994}, {2, 5, 993}, ...],
...
]
а flat_map
выпрямит, чтобы далее было легко фильтровать каждый кортеж:
[
{1, 2, 997},
{1, 3, 996},
{1, 4, 995},
...
]
defmodule Euler9ListComp do
@moduledoc """
Provides list comprehension solutions for Project Euler Problem 9.
"""
@doc """
Finds the product of the Pythagorean triplet where the sum equals `sum`.
## Examples
iex> Euler9ListComp.find_triplet(1000)
31875000
"""
def find_triplet(sum) do
for(
a <- 1..(sum - 2),
b <- (a + 1)..(sum - a - 1),
c = sum - a - b,
a * a + b * b == c * c,
do: a * b * c
)
|> Enum.find(fn _product -> true end)
end
end
defmodule Euler9Stream do
@moduledoc """
Module handling lazy collections and infinite lists for Euler problem 9.
"""
def find_triplet(sum) do
Stream.iterate(1, &(&1 + 1))
|> Stream.take_while(&(&1 < sum / 3))
|> Stream.flat_map(fn a ->
Stream.iterate(a + 1, &(&1 + 1))
|> Stream.take_while(&(&1 < sum / 2))
|> Stream.map(fn b ->
c = sum - a - b
{a, b, c}
end)
end)
|> Stream.filter(fn {a, b, c} -> a * a + b * b == c * c end)
|> Enum.map(fn {a, b, c} -> a * b * c end)
|> Enum.at(0)
end
end
def find_pyth_triplet(sum_):
for a in range(1, sum_):
for b in range(1, sum_):
c = 1000 - a - b
if a**2 + b**2 == c**2:
return a, b, c
a, b, c = find_pyth_triplet(1000)
print(f"Pythagorean triplet: a = {a}. b = {b}, c = {c}")
print(f"Product abc = {a * b * c}")
-
Название:
Amicable Numbers
-
Описание:
Let$d(n)$ be defined as the sum of proper divisors of$n$ (numbers less than$n$ which divide evenly into$n$ ).
If$d(a) = b$ and$d(b) = a$ , where$a \neq b$ , then$a$ and$b$ are an amicable pair and each of$a$ and$b$ are called amicable numbers.For example, the proper divisors of 220 are 1, 2, 4, 5, 10, 11, 20, 22, 44, 55, and 110; therefore
$$d(220) = 284.$$
The proper divisors of 284 are 1, 2, 4, 71, and 142; so
$$d(284) = 220.$$ -
Задание:
Evaluate the sum of all the amicable numbers under 10000.
В этой задаче, как и в задаче №9, можно было бы записать большую часть решения, т.е. поиск делителей и a - перебором, что легко реализовывается на императивном языке вроде Python. Но в функциональном стиле нужно решить задачу с использованием рекурсии, функций высшего порядка и различных методов, в результате чего приходилось придумывать решения в зависимости от подхода.
Нам нужно найти сумму всех дружественных чисел меньше
-
$d(a) = b$ — сумма собственных делителей$a$ равна$b$ . -
$d(b) = a$ — сумма собственных делителей$b$ равна$a$ , при этом$a \neq b$ .
-
Функция для нахождения суммы делителей:
- Для каждого числа
$n$ мы должны найти его собственные делители и вычислить их сумму. - Здесь мы можем использовать подход, где сумма делителей считается с
использованием перебора до
$\frac{n}{2}$ , так как все делители$n$ меньше или равны половине числа$n$ .
- Для каждого числа
-
Перебор чисел от 2 до 10000:
- Для каждого числа
$a$ мы находим сумму его делителей$b = d(a)$ . - Если
$d(b) = a$ и$a \neq b$ , то$a$ и$b$ — дружественная пара, и мы добавляем оба числа в список дружественных чисел.
- Для каждого числа
-
Рекурсивный подход:
- Для рекурсии мы можем написать функцию, которая будет последовательно
увеличивать число
$n$ от 2 до предела (в нашем случае$10000$ ), проверять условие дружественности и накапливать сумму дружественных чисел.
- Для рекурсии мы можем написать функцию, которая будет последовательно
увеличивать число
Для рекурсивного решения мы можем разделить задачу на несколько частей:
-
Основная функция
sum_amicable_numbers(limit)
:- Она инициализирует процесс и вызывает рекурсивную функцию для каждого числа
$n$ от 2 до предела$limit$ .
- Она инициализирует процесс и вызывает рекурсивную функцию для каждого числа
-
Рекурсивная функция
do_sum/3
:- Эта функция принимает текущее число
$n$ , предел$limit$ , и аккумулятор для накопления суммы дружественных чисел. - Если
$n$ меньше предела, то:- Вычисляем сумму делителей
$d(n)$ . - Если
$n$ и$d(n)$ образуют дружественную пару, добавляем$n$ в аккумулятор и продолжаем рекурсию для следующего числа$n + 1$ .
- Вычисляем сумму делителей
- Когда
$n$ достигнет предела, рекурсия завершится и вернётся сумма всех дружественных чисел.
- Эта функция принимает текущее число
-
Функция
sum_of_divisors(n)
:- Она рекурсивно находит сумму собственных делителей числа
$n$ , перебирая числа от$\frac{n}{2}$ до 1 и проверяя, делится ли число$n$ на каждый делитель.
- Она рекурсивно находит сумму собственных делителей числа
defmodule Euler21Recursion do
@moduledoc """
Module handling recursive implementations for Euler problem 21.
"""
def sum_amicable_numbers(limit) do
do_sum(2, limit, [])
end
defp do_sum(n, limit, acc) when n < limit do
sum_div = sum_of_divisors(n)
if sum_div != n and sum_of_divisors(sum_div) == n do
do_sum(n + 1, limit, [n | acc])
else
do_sum(n + 1, limit, acc)
end
end
defp do_sum(_, _, acc), do: Enum.sum(acc)
defp sum_of_divisors(n), do: sum_of_divisors(n, div(n, 2), 0)
defp sum_of_divisors(_, i, acc) when i <= 0, do: acc
defp sum_of_divisors(n, i, acc) do
if rem(n, i) == 0 do
sum_of_divisors(n, i - 1, acc + i)
else
sum_of_divisors(n, i - 1, acc)
end
end
end
defmodule Euler21TailRecursion do
@moduledoc """
Module handling tail-recursive implementations for Euler problem 21.
"""
def sum_amicable_numbers(limit) do
do_sum(2, limit, 0)
end
defp do_sum(n, limit, acc) when n < limit do
sum_div = sum_of_divisors(n)
if sum_div != n and sum_of_divisors(sum_div) == n do
do_sum(n + 1, limit, acc + n)
else
do_sum(n + 1, limit, acc)
end
end
defp do_sum(_, _, acc), do: acc
defp sum_of_divisors(n), do: sum_of_divisors(n, n - 1, 0)
defp sum_of_divisors(_, 0, acc), do: acc
defp sum_of_divisors(n, i, acc) do
if rem(n, i) == 0 do
sum_of_divisors(n, i - 1, acc + i)
else
sum_of_divisors(n, i - 1, acc)
end
end
end
defmodule Euler21Modular do
@moduledoc """
Provides modular solutions for Project Euler Problem 21.
"""
@doc """
Sums all amicable numbers below `limit` using modular functions.
## Examples
iex> Euler21Modular.sum_amicable_numbers(10000)
31626
"""
def sum_amicable_numbers(limit) do
2..(limit - 1)
|> Enum.filter(&amicable?/1)
|> Enum.sum()
end
defp amicable?(n) do
sum_div = sum_of_divisors(n)
sum_div != n and sum_div < limit() and sum_of_divisors(sum_div) == n
end
defp sum_of_divisors(n) do
if n > 1 do
1..div(n, 2)
|> Enum.filter(&(rem(n, &1) == 0))
|> Enum.sum()
else
0
end
end
defp limit, do: 10_000
end
defmodule Euler21ListComp do
@moduledoc """
Provides list comprehension solutions for Project Euler Problem 21.
"""
@doc """
Sums all amicable numbers below `limit`.
## Examples
iex> Euler21ListComp.sum_amicable_numbers(10000)
31626
"""
def sum_amicable_numbers(limit) do
for(
n <- 2..(limit - 1),
amicable?(n),
do: n
)
|> Enum.sum()
end
defp amicable?(n) do
sum_div = sum_of_divisors(n)
sum_div != n and sum_div < limit() and sum_of_divisors(sum_div) == n
end
defp sum_of_divisors(n) do
if n > 1 do
for(
i <- 1..div(n, 2),
rem(n, i) == 0,
do: i
)
|> Enum.sum()
else
0
end
end
defp limit, do: 10_000
end
defmodule Euler21Stream do
@moduledoc """
Provides stream-based solutions for Project Euler Problem 21.
"""
@doc """
Sums all amicable numbers below `limit` using streams.
## Examples
iex> Euler21Stream.sum_amicable_numbers(10000)
31626
"""
def sum_amicable_numbers(limit) do
Stream.iterate(2, &(&1 + 1))
|> Stream.take_while(&(&1 < limit))
|> Stream.filter(&amicable?/1)
|> Enum.sum()
end
defp amicable?(n) do
sum_div = sum_of_divisors(n)
sum_div != n and sum_div < limit() and sum_of_divisors(sum_div) == n
end
defp sum_of_divisors(n) do
if n > 1 do
1..div(n, 2)
|> Enum.filter(&(rem(n, &1) == 0))
|> Enum.sum()
else
0
end
end
defp limit, do: 10_000
end
def sum_div(n):
sum = 0
for i in range(1, int(n**0.5) + 1):
if n % i == 0:
sum += i
if i != 1 and i != n // i:
sum += n // i
return sum
def find_amicable_nums(limit):
nums = set()
for a in range(2, limit):
b = sum_div(a)
if a != b and sum_div(b) == a:
nums.add(a)
nums.add(b)
return sum(nums)
limit = 10_000
print(f"Amicle numbers sum below {limit} is {find_amicable_nums(limit)}")
В ходе данной лабораторной работы я изучил значительную часть документации по
Elixir, познакомился с его типами данных и структурами. Я узнал о разнице между
Stream
и Enum
в контексте ленивых вычислений, а также познакомился с
концепцией бесконечных потоков. Меня приятно удивило, что в функциональном
программировании нет традиционных циклов for
, и вместо них используются
рекурсии, свёртывание и другие концепции, что может показаться сложным на первый
взгляд, но на самом деле приемлемо после знакомства с различными способами
реализации, многие из которых мимикрируют под стандартные циклы императивных
языков. Я изучил условные операторы, структуру модулей и функций в Elixir, а
также основы синтаксиса, анонимные функции, кложуры и оператор захвата. Решая
задачи из проекта Эйлера, я открыл для себя, насколько удобен и эффективен
Elixir.
Особенно понравилась работа с пайпами и генераторами, а также удобство объявления анонимных функций. Модульная структура языка оказалась интуитивной и систематизированной, глоток свежего воздуха после питона и js(ts), на которых пока работаю.
Прочитав первые 100 страниц книги "Elixir in Action", я вдохновился не только языком, но и всей инфраструктурой Erlang + OTP. BEAM VM оказалась впечатляющей: хотя в исходном коде встречаются "магические" числа и незадокументированные участки, виртуальная машина работает гораздо лучше, чем интерпретаторы Python или даже Java.
Хотел автоматизировать test coverage, посидеть попрофилировать решения, но завяз на 100 часов за электротехникой, скажите студофису чтоб убрали 11 предметов блин.
Также если хотите порофлить, в ./common/
лежит демка отчёта в латехе, но чёт я
подумал что маркдаун лучше проявит себя в долгосрочной перспективе в репозитории
гитхаба. Это не электротехника :)