From 1a4c55bdaf65e70915abf6daf629b5ce8cf0d5e3 Mon Sep 17 00:00:00 2001 From: Danylo Negrienko Date: Thu, 22 Aug 2024 00:58:24 -0400 Subject: [PATCH] complex_numbers --- elixir/complex-numbers/.exercism/config.json | 19 + .../complex-numbers/.exercism/metadata.json | 1 + elixir/complex-numbers/.formatter.exs | 4 + elixir/complex-numbers/.gitignore | 24 ++ elixir/complex-numbers/HELP.md | 75 ++++ elixir/complex-numbers/README.md | 48 +++ elixir/complex-numbers/lib/complex_numbers.ex | 73 ++++ elixir/complex-numbers/mix.exs | 28 ++ .../test/complex_numbers_test.exs | 373 ++++++++++++++++++ elixir/complex-numbers/test/test_helper.exs | 2 + 10 files changed, 647 insertions(+) create mode 100644 elixir/complex-numbers/.exercism/config.json create mode 100644 elixir/complex-numbers/.exercism/metadata.json create mode 100644 elixir/complex-numbers/.formatter.exs create mode 100644 elixir/complex-numbers/.gitignore create mode 100644 elixir/complex-numbers/HELP.md create mode 100644 elixir/complex-numbers/README.md create mode 100644 elixir/complex-numbers/lib/complex_numbers.ex create mode 100644 elixir/complex-numbers/mix.exs create mode 100644 elixir/complex-numbers/test/complex_numbers_test.exs create mode 100644 elixir/complex-numbers/test/test_helper.exs diff --git a/elixir/complex-numbers/.exercism/config.json b/elixir/complex-numbers/.exercism/config.json new file mode 100644 index 0000000..24ad6bf --- /dev/null +++ b/elixir/complex-numbers/.exercism/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "jiegillet" + ], + "files": { + "solution": [ + "lib/complex_numbers.ex" + ], + "test": [ + "test/complex_numbers_test.exs" + ], + "example": [ + ".meta/example.ex" + ] + }, + "blurb": "Implement complex numbers.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Complex_number" +} diff --git a/elixir/complex-numbers/.exercism/metadata.json b/elixir/complex-numbers/.exercism/metadata.json new file mode 100644 index 0000000..d856552 --- /dev/null +++ b/elixir/complex-numbers/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"complex-numbers","id":"e861817a97db485aab96fb825626df43","url":"https://exercism.org/tracks/elixir/exercises/complex-numbers","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/complex-numbers/.formatter.exs b/elixir/complex-numbers/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/complex-numbers/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/complex-numbers/.gitignore b/elixir/complex-numbers/.gitignore new file mode 100644 index 0000000..6b30b2c --- /dev/null +++ b/elixir/complex-numbers/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +complex_numbers-*.tar + diff --git a/elixir/complex-numbers/HELP.md b/elixir/complex-numbers/HELP.md new file mode 100644 index 0000000..12ffdb1 --- /dev/null +++ b/elixir/complex-numbers/HELP.md @@ -0,0 +1,75 @@ +# Help + +## Running the tests + +From the terminal, change to the base directory of the exercise then execute the tests with: + +```bash +$ mix test +``` + +This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs` + +Documentation: + +* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html) +* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html) + +## Pending tests + +In test suites of practice exercises, all but the first test have been tagged to be skipped. + +Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol. + +For example: + +```elixir +# @tag :pending +test "shouting" do + assert Bob.hey("WATCH OUT!") == "Whoa, chill out!" +end +``` + +If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command: + +```bash +$ mix test --include pending +``` + +Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`. + +```elixir +# ExUnit.configure(exclude: :pending, trace: true) +``` + +## Useful `mix test` options + +* `test/.exs:LINENUM` - runs only a single test, the test from `.exs` whose definition is on line `LINENUM` +* `--failed` - runs only tests that failed the last time they ran +* `--max-failures` - the suite stops evaluating tests when this number of test failures +is reached +* `--seed 0` - disables randomization so the tests in a single file will always be ran +in the same order they were defined in + +## Submitting your solution + +You can submit your solution using the `exercism submit lib/complex_numbers.ex` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir) +- The [Elixir track's programming category on the forum](https://forum.exercism.org/c/programming/elixir) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found. \ No newline at end of file diff --git a/elixir/complex-numbers/README.md b/elixir/complex-numbers/README.md new file mode 100644 index 0000000..2d71f96 --- /dev/null +++ b/elixir/complex-numbers/README.md @@ -0,0 +1,48 @@ +# Complex Numbers + +Welcome to Complex Numbers on Exercism's Elixir Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +A complex number is a number in the form `a + b * i` where `a` and `b` are real and `i` satisfies `i^2 = -1`. + +`a` is called the real part and `b` is called the imaginary part of `z`. +The conjugate of the number `a + b * i` is the number `a - b * i`. +The absolute value of a complex number `z = a + b * i` is a real number `|z| = sqrt(a^2 + b^2)`. The square of the absolute value `|z|^2` is the result of multiplication of `z` by its complex conjugate. + +The sum/difference of two complex numbers involves adding/subtracting their real and imaginary parts separately: +`(a + i * b) + (c + i * d) = (a + c) + (b + d) * i`, +`(a + i * b) - (c + i * d) = (a - c) + (b - d) * i`. + +Multiplication result is by definition +`(a + i * b) * (c + i * d) = (a * c - b * d) + (b * c + a * d) * i`. + +The reciprocal of a non-zero complex number is +`1 / (a + i * b) = a/(a^2 + b^2) - b/(a^2 + b^2) * i`. + +Dividing a complex number `a + i * b` by another `c + i * d` gives: +`(a + i * b) / (c + i * d) = (a * c + b * d)/(c^2 + d^2) + (b * c - a * d)/(c^2 + d^2) * i`. + +Raising e to a complex exponent can be expressed as `e^(a + i * b) = e^a * e^(i * b)`, the last term of which is given by Euler's formula `e^(i * b) = cos(b) + i * sin(b)`. + +Implement the following operations: + +- addition, subtraction, multiplication and division of two complex numbers, +- conjugate, absolute value, exponent of a given complex number. + +Assume the programming language you are using does not have an implementation of complex numbers. + +In this exercise, complex numbers are represented as a tuple-pair containing the real and imaginary parts. + +For example, the real number `1` is `{1, 0}`, the imaginary number `i` is `{0, 1}` and the complex number `4+3i` is `{4, 3}`. + +## Source + +### Created by + +- @jiegillet + +### Based on + +Wikipedia - https://en.wikipedia.org/wiki/Complex_number \ No newline at end of file diff --git a/elixir/complex-numbers/lib/complex_numbers.ex b/elixir/complex-numbers/lib/complex_numbers.ex new file mode 100644 index 0000000..b02b906 --- /dev/null +++ b/elixir/complex-numbers/lib/complex_numbers.ex @@ -0,0 +1,73 @@ +defmodule ComplexNumbers do + @typedoc """ + In this module, complex numbers are represented as a tuple-pair containing the real and + imaginary parts. + For example, the real number `1` is `{1, 0}`, the imaginary number `i` is `{0, 1}` and + the complex number `4+3i` is `{4, 3}'. + """ + @type complex :: {number, number} + + @doc """ + Return the real part of a complex number + """ + @spec real(a :: complex) :: number + def real({r, _i}), do: r + + @doc """ + Return the imaginary part of a complex number + """ + @spec imaginary(a :: complex) :: number + def imaginary({_r, i}), do: i + + @doc """ + Multiply two complex numbers, or a real and a complex number + """ + @spec mul(a :: complex | number, b :: complex | number) :: complex + def mul(n, {r, i}) when is_number(n), do: mul({r, i}, to_complex(n)) + def mul({r, i}, n) when is_number(n), do: mul(to_complex(n), {r, i}) + def mul({ar, ai}, {br, bi}), do: {ar * br - ai * bi, ar * bi + ai * br} + + @doc """ + Add two complex numbers, or a real and a complex number + """ + @spec add(a :: complex | number, b :: complex | number) :: complex + def add(n, {r, i}) when is_number(n), do: add({r, i}, to_complex(n)) + def add({r, i}, n) when is_number(n), do: add(to_complex(n), {r, i}) + def add({ar, ai}, {br, bi}), do: {ar + br, ai + bi} + + @doc """ + Subtract two complex numbers, or a real and a complex number + """ + @spec sub(a :: complex | number, b :: complex | number) :: complex + def sub(n, {r, i}) when is_number(n), do: sub(to_complex(n), {r, i}) + def sub({r, i}, n) when is_number(n), do: sub({r, i}, to_complex(n)) + def sub({ar, ai}, {br, bi}), do: {ar - br, ai - bi} + + @doc """ + Divide two complex numbers, or a real and a complex number + """ + @spec div(a :: complex | number, b :: complex | number) :: complex + def div(n, {r, i}) when is_number(n), do: __MODULE__.div(to_complex(n), {r, i}) + def div({r, i}, n) when is_number(n), do: __MODULE__.div({r, i}, to_complex(n)) + def div({ar, ai}, {br, bi}), do: {(ar * br + ai * bi)/(br ** 2 + bi ** 2), (ai * br - ar * bi)/(br ** 2 + bi ** 2)} + + @doc """ + Absolute value of a complex number + """ + @spec abs(a :: complex) :: number + def abs({r, i}), do: (r ** 2 + i ** 2) ** 0.5 + + @doc """ + Conjugate of a complex number + """ + @spec conjugate(a :: complex) :: complex + def conjugate({r, i}), do: {r, -i} + + @doc """ + Exponential of a complex number + """ + @spec exp(a :: complex) :: complex + def exp({r, i}), do: mul({:math.exp(r), 0}, {:math.cos(i), :math.sin(i)}) + + defp to_complex(n) when is_number(n), do: {n, 0} +end diff --git a/elixir/complex-numbers/mix.exs b/elixir/complex-numbers/mix.exs new file mode 100644 index 0000000..be48366 --- /dev/null +++ b/elixir/complex-numbers/mix.exs @@ -0,0 +1,28 @@ +defmodule ComplexNumbers.MixProject do + use Mix.Project + + def project do + [ + app: :complex_numbers, + version: "0.1.0", + # elixir: "~> 1.8", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/elixir/complex-numbers/test/complex_numbers_test.exs b/elixir/complex-numbers/test/complex_numbers_test.exs new file mode 100644 index 0000000..810fb27 --- /dev/null +++ b/elixir/complex-numbers/test/complex_numbers_test.exs @@ -0,0 +1,373 @@ +defmodule ComplexNumbersTest do + use ExUnit.Case + + def equal({a, b}, {c, d}) do + assert_in_delta a, c, 1.0e-12 + assert_in_delta b, d, 1.0e-12 + end + + def equal(a, b) do + assert_in_delta a, b, 1.0e-12 + end + + describe "Real part" do + test "Real part of a purely real number" do + z = {1, 0} + output = ComplexNumbers.real(z) + expected = 1 + + equal(output, expected) + end + + test "Real part of a purely imaginary number" do + z = {0, 1} + output = ComplexNumbers.real(z) + expected = 0 + + equal(output, expected) + end + + test "Real part of a number with real and imaginary part" do + z = {1, 2} + output = ComplexNumbers.real(z) + expected = 1 + + equal(output, expected) + end + end + + describe "Imaginary part" do + test "Imaginary part of a purely real number" do + z = {1, 0} + output = ComplexNumbers.imaginary(z) + expected = 0 + + equal(output, expected) + end + + test "Imaginary part of a purely imaginary number" do + z = {0, 1} + output = ComplexNumbers.imaginary(z) + expected = 1 + + equal(output, expected) + end + + test "Imaginary part of a number with real and imaginary part" do + z = {1, 2} + output = ComplexNumbers.imaginary(z) + expected = 2 + + equal(output, expected) + end + end + + test "Imaginary unit" do + z1 = {0, 1} + z2 = {0, 1} + output = ComplexNumbers.mul(z1, z2) + expected = {-1, 0} + + equal(output, expected) + end + + describe "Addition" do + test "Add purely real numbers" do + z1 = {1, 0} + z2 = {2, 0} + output = ComplexNumbers.add(z1, z2) + expected = {3, 0} + + equal(output, expected) + end + + test "Add purely imaginary numbers" do + z1 = {0, 1} + z2 = {0, 2} + output = ComplexNumbers.add(z1, z2) + expected = {0, 3} + + equal(output, expected) + end + + test "Add numbers with real and imaginary part" do + z1 = {1, 2} + z2 = {3, 4} + output = ComplexNumbers.add(z1, z2) + expected = {4, 6} + + equal(output, expected) + end + end + + describe "Subtraction" do + test "Subtract purely real numbers" do + z1 = {1, 0} + z2 = {2, 0} + output = ComplexNumbers.sub(z1, z2) + expected = {-1, 0} + + equal(output, expected) + end + + test "Subtract purely imaginary numbers" do + z1 = {0, 1} + z2 = {0, 2} + output = ComplexNumbers.sub(z1, z2) + expected = {0, -1} + + equal(output, expected) + end + + test "Subtract numbers with real and imaginary part" do + z1 = {1, 2} + z2 = {3, 4} + output = ComplexNumbers.sub(z1, z2) + expected = {-2, -2} + + equal(output, expected) + end + end + + describe "Multiplication" do + test "Multiply purely real numbers" do + z1 = {1, 0} + z2 = {2, 0} + output = ComplexNumbers.mul(z1, z2) + expected = {2, 0} + + equal(output, expected) + end + + test "Multiply purely imaginary numbers" do + z1 = {0, 1} + z2 = {0, 2} + output = ComplexNumbers.mul(z1, z2) + expected = {-2, 0} + + equal(output, expected) + end + + test "Multiply numbers with real and imaginary part" do + z1 = {1, 2} + z2 = {3, 4} + output = ComplexNumbers.mul(z1, z2) + expected = {-5, 10} + + equal(output, expected) + end + end + + describe "Division" do + test "Divide purely real numbers" do + z1 = {1, 0} + z2 = {2, 0} + output = ComplexNumbers.div(z1, z2) + expected = {0.5, 0} + + equal(output, expected) + end + + test "Divide purely imaginary numbers" do + z1 = {0, 1} + z2 = {0, 2} + output = ComplexNumbers.div(z1, z2) + expected = {0.5, 0} + + equal(output, expected) + end + + test "Divide numbers with real and imaginary part" do + z1 = {1, 2} + z2 = {3, 4} + output = ComplexNumbers.div(z1, z2) + expected = {0.44, 0.08} + + equal(output, expected) + end + end + + describe "Absolute value" do + test "Absolute value of a positive purely real number" do + z = {5, 0} + output = ComplexNumbers.abs(z) + expected = 5 + + equal(output, expected) + end + + test "Absolute value of a negative purely real number" do + z = {-5, 0} + output = ComplexNumbers.abs(z) + expected = 5 + + equal(output, expected) + end + + test "Absolute value of a purely imaginary number with positive imaginary part" do + z = {0, 5} + output = ComplexNumbers.abs(z) + expected = 5 + + equal(output, expected) + end + + test "Absolute value of a purely imaginary number with negative imaginary part" do + z = {0, -5} + output = ComplexNumbers.abs(z) + expected = 5 + + equal(output, expected) + end + + test "Absolute value of a number with real and imaginary part" do + z = {3, 4} + output = ComplexNumbers.abs(z) + expected = 5 + + equal(output, expected) + end + end + + describe "Complex conjugate" do + test "Conjugate a purely real number" do + z = {5, 0} + output = ComplexNumbers.conjugate(z) + expected = {5, 0} + + equal(output, expected) + end + + test "Conjugate a purely imaginary number" do + z = {0, 5} + output = ComplexNumbers.conjugate(z) + expected = {0, -5} + + equal(output, expected) + end + + test "Conjugate a number with real and imaginary part" do + z = {1, 1} + output = ComplexNumbers.conjugate(z) + expected = {1, -1} + + equal(output, expected) + end + end + + describe "Complex exponential function" do + test "Euler's identity/formula" do + z = {0, :math.pi()} + output = ComplexNumbers.exp(z) + expected = {-1, 0} + + equal(output, expected) + end + + test "Exponential of 0" do + z = {0, 0} + output = ComplexNumbers.exp(z) + expected = {1, 0} + + equal(output, expected) + end + + test "Exponential of a purely real number" do + z = {1, 0} + output = ComplexNumbers.exp(z) + expected = {:math.exp(1), 0} + + equal(output, expected) + end + + test "Exponential of a number with real and imaginary part" do + z = {:math.log(2), :math.pi()} + output = ComplexNumbers.exp(z) + expected = {-2, 0} + + equal(output, expected) + end + + test "Exponential resulting in a number with real and imaginary part" do + z = {:math.log(2) / 2, :math.pi() / 4} + output = ComplexNumbers.exp(z) + expected = {1, 1} + + equal(output, expected) + end + end + + describe "Operations between real numbers and complex numbers" do + test "Add real number to complex number" do + z = {1, 2} + r = 5 + output = ComplexNumbers.add(z, r) + expected = {6, 2} + + equal(output, expected) + end + + test "Add complex number to real number" do + r = 5 + z = {1, 2} + output = ComplexNumbers.add(r, z) + expected = {6, 2} + + equal(output, expected) + end + + test "Subtract real number from complex number" do + z = {5, 7} + r = 4 + output = ComplexNumbers.sub(z, r) + expected = {1, 7} + + equal(output, expected) + end + + test "Subtract complex number from real number" do + r = 4 + z = {5, 7} + output = ComplexNumbers.sub(r, z) + expected = {-1, -7} + + equal(output, expected) + end + + test "Multiply complex number by real number" do + z = {2, 5} + r = 5 + output = ComplexNumbers.mul(z, r) + expected = {10, 25} + + equal(output, expected) + end + + test "Multiply real number by complex number" do + r = 5 + z = {2, 5} + output = ComplexNumbers.mul(r, z) + expected = {10, 25} + + equal(output, expected) + end + + test "Divide complex number by real number" do + z = {10, 100} + r = 10 + output = ComplexNumbers.div(z, r) + expected = {1, 10} + + equal(output, expected) + end + + test "Divide real number by complex number" do + r = 5 + z = {1, 1} + output = ComplexNumbers.div(r, z) + expected = {2.5, -2.5} + + equal(output, expected) + end + end +end diff --git a/elixir/complex-numbers/test/test_helper.exs b/elixir/complex-numbers/test/test_helper.exs new file mode 100644 index 0000000..35fc5bf --- /dev/null +++ b/elixir/complex-numbers/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true)