complex_numbers

This commit is contained in:
Danil Negrienko 2024-08-22 00:58:24 -04:00
parent 6bcb4db837
commit 1a4c55bdaf
10 changed files with 647 additions and 0 deletions

View File

@ -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"
}

View File

@ -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}

View File

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

24
elixir/complex-numbers/.gitignore vendored Normal file
View File

@ -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

View File

@ -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/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,2 @@
ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true)