rational_numbers

This commit is contained in:
Danil Negrienko 2024-03-10 07:27:55 -04:00
parent bb65cd30cc
commit 3c7f1d64f5
10 changed files with 542 additions and 0 deletions

View File

@ -0,0 +1,23 @@
{
"authors": [
"jiegillet"
],
"contributors": [
"angelikatyborska",
"kszambelanczyk"
],
"files": {
"solution": [
"lib/rational_numbers.ex"
],
"test": [
"test/rational_numbers_test.exs"
],
"example": [
".meta/example.ex"
]
},
"blurb": "Implement rational numbers.",
"source": "Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/Rational_number"
}

View File

@ -0,0 +1 @@
{"track":"elixir","exercise":"rational-numbers","id":"7dc127adcbfe4b6082e20167dc621378","url":"https://exercism.org/tracks/elixir/exercises/rational-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/rational-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").
rational_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/rational_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,62 @@
# Rational Numbers
Welcome to Rational Numbers on Exercism's Elixir Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
A rational number is defined as the quotient of two integers `a` and `b`, called the numerator and denominator, respectively, where `b != 0`.
~~~~exercism/note
Note that mathematically, the denominator can't be zero.
However in many implementations of rational numbers, you will find that the denominator is allowed to be zero with behaviour similar to positive or negative infinity in floating point numbers.
In those cases, the denominator and numerator generally still can't both be zero at once.
~~~~
The absolute value `|r|` of the rational number `r = a/b` is equal to `|a|/|b|`.
The sum of two rational numbers `r₁ = a₁/b₁` and `r₂ = a₂/b₂` is `r₁ + r₂ = a₁/b₁ + a₂/b₂ = (a₁ * b₂ + a₂ * b₁) / (b₁ * b₂)`.
The difference of two rational numbers `r₁ = a₁/b₁` and `r₂ = a₂/b₂` is `r₁ - r₂ = a₁/b₁ - a₂/b₂ = (a₁ * b₂ - a₂ * b₁) / (b₁ * b₂)`.
The product (multiplication) of two rational numbers `r₁ = a₁/b₁` and `r₂ = a₂/b₂` is `r₁ * r₂ = (a₁ * a₂) / (b₁ * b₂)`.
Dividing a rational number `r₁ = a₁/b₁` by another `r₂ = a₂/b₂` is `r₁ / r₂ = (a₁ * b₂) / (a₂ * b₁)` if `a₂` is not zero.
Exponentiation of a rational number `r = a/b` to a non-negative integer power `n` is `r^n = (a^n)/(b^n)`.
Exponentiation of a rational number `r = a/b` to a negative integer power `n` is `r^n = (b^m)/(a^m)`, where `m = |n|`.
Exponentiation of a rational number `r = a/b` to a real (floating-point) number `x` is the quotient `(a^x)/(b^x)`, which is a real number.
Exponentiation of a real number `x` to a rational number `r = a/b` is `x^(a/b) = root(x^a, b)`, where `root(p, q)` is the `q`th root of `p`.
Implement the following operations:
- addition, subtraction, multiplication and division of two rational numbers,
- absolute value, exponentiation of a given rational number to an integer power, exponentiation of a given rational number to a real (floating-point) power, exponentiation of a real number to a rational number.
Your implementation of rational numbers should always be reduced to lowest terms.
For example, `4/4` should reduce to `1/1`, `30/60` should reduce to `1/2`, `12/8` should reduce to `3/2`, etc.
To reduce a rational number `r = a/b`, divide `a` and `b` by the greatest common divisor (gcd) of `a` and `b`.
So, for example, `gcd(12, 8) = 4`, so `r = 12/8` can be reduced to `(12/4)/(8/4) = 3/2`.
The reduced form of a rational number should be in "standard form" (the denominator should always be a positive integer).
If a denominator with a negative integer is present, multiply both numerator and denominator by `-1` to ensure standard form is reached.
For example, `3/-4` should be reduced to `-3/4`
Assume that the programming language you are using does not have an implementation of rational numbers.
## Source
### Created by
- @jiegillet
### Contributed to by
- @angelikatyborska
- @kszambelanczyk
### Based on
Wikipedia - https://en.wikipedia.org/wiki/Rational_number

View File

@ -0,0 +1,80 @@
defmodule RationalNumbers do
@type rational :: {integer, integer}
alias Kernel, as: K
@doc """
Add two rational numbers
"""
@spec add(a :: rational, b :: rational) :: rational
def add({an, ad}, {bn, bd}), do: {an * bd + bn * ad, ad * bd} |> reduce()
@doc """
Subtract two rational numbers
"""
@spec subtract(a :: rational, b :: rational) :: rational
def subtract({an, ad}, {bn, bd}), do: {an * bd - bn * ad, ad * bd} |> reduce()
@doc """
Multiply two rational numbers
"""
@spec multiply(a :: rational, b :: rational) :: rational
def multiply({an, ad}, {bn, bd}), do: {an * bn, ad * bd} |> reduce()
@doc """
Divide two rational numbers
"""
@spec divide_by(num :: rational, den :: rational) :: rational
def divide_by({an, ad}, {bn, bd}) when bn != 0, do: {an * bd, bn * ad} |> reduce()
@doc """
Absolute value of a rational number
"""
@spec abs(a :: rational) :: rational
def abs({an, ad}), do: {K.abs(an), K.abs(ad)} |> reduce()
@doc """
Exponentiation of a rational number by an integer
"""
@spec pow_rational(a :: rational, n :: integer | float) :: rational
def pow_rational({an, ad}, n) when is_integer(n) and n >= 0, do: {an ** n, ad ** n} |> reduce()
def pow_rational({an, ad}, n) when is_integer(n) and n < 0,
do: {ad ** K.abs(n), an ** K.abs(n)} |> reduce()
def pow_rational({an, ad}, x) when is_float(x), do: ad ** x / an ** x
@doc """
Exponentiation of a real number by a rational number
"""
@spec pow_real(x :: integer, n :: rational) :: float
def pow_real(x, {_nn, nd} = n) when is_integer(x) and nd < 0, do: pow_real(x, normalize(n))
def pow_real(x, {nn, nd}) when is_integer(x), do: x ** nn ** (1 / nd)
@doc """
Reduce a rational number to its lowest terms
"""
@spec reduce(a :: rational) :: rational
def reduce({an, ad} = a),
do: a |> normalize() |> cut(Integer.gcd(an, ad))
# @doc """
# Divides a nominator and denominator by integer
# """
@spec cut(a :: rational, n :: integer) :: rational
defp cut({an, ad}, n), do: {K.div(an, n), K.div(ad, n)}
# @doc """
# Turns signs of nominator and denominator when negative denominator
# """
@spec normalize(a :: rational) :: rational
defp normalize({an, ad}) when ad < 0, do: {K.-(an), K.-(ad)}
defp normalize({an, ad}), do: {an, ad}
# @doc """
# Calculate a greatest common divisor of two integers
# """
# @spec gcd(i :: integer, j :: integer) :: integer
# defp gcd(i, 0), do: K.abs(i)
# defp gcd(0, j), do: K.abs(j)
# defp gcd(i, j), do: gcd(j, K.rem(i, j))
end

View File

@ -0,0 +1,28 @@
defmodule RationalNumbers.MixProject do
use Mix.Project
def project do
[
app: :rational_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,243 @@
defmodule RationalNumbersTest do
use ExUnit.Case
describe "Addition" do
# # @tag :pending
test "Add two positive rational numbers" do
assert RationalNumbers.add({1, 2}, {2, 3}) == {7, 6}
end
# @tag :pending
test "Add a positive rational number and a negative rational number" do
assert RationalNumbers.add({1, 2}, {-2, 3}) == {-1, 6}
end
# @tag :pending
test "Add two negative rational numbers" do
assert RationalNumbers.add({-1, 2}, {-2, 3}) == {-7, 6}
end
# @tag :pending
test "Add a rational number to its additive inverse" do
assert RationalNumbers.add({1, 2}, {-1, 2}) == {0, 1}
end
end
describe "Subtraction" do
# @tag :pending
test "Subtract two positive rational numbers" do
assert RationalNumbers.subtract({1, 2}, {2, 3}) == {-1, 6}
end
# @tag :pending
test "Subtract a positive rational number and a negative rational number" do
assert RationalNumbers.subtract({1, 2}, {-2, 3}) == {7, 6}
end
# @tag :pending
test "Subtract two negative rational numbers" do
assert RationalNumbers.subtract({-1, 2}, {-2, 3}) == {1, 6}
end
# @tag :pending
test "Subtract a rational number from itself" do
assert RationalNumbers.subtract({1, 2}, {1, 2}) == {0, 1}
end
end
describe "Multiplication" do
# @tag :pending
test "Multiply two positive rational numbers" do
assert RationalNumbers.multiply({1, 2}, {2, 3}) == {1, 3}
end
# @tag :pending
test "Multiply a negative rational number by a positive rational number" do
assert RationalNumbers.multiply({-1, 2}, {2, 3}) == {-1, 3}
end
# @tag :pending
test "Multiply two negative rational numbers" do
assert RationalNumbers.multiply({-1, 2}, {-2, 3}) == {1, 3}
end
# @tag :pending
test "Multiply a rational number by its reciprocal" do
assert RationalNumbers.multiply({1, 2}, {2, 1}) == {1, 1}
end
# @tag :pending
test "Multiply a rational number by 1" do
assert RationalNumbers.multiply({1, 2}, {1, 1}) == {1, 2}
end
# @tag :pending
test "Multiply a rational number by 0" do
assert RationalNumbers.multiply({1, 2}, {0, 1}) == {0, 1}
end
end
describe "Division" do
# @tag :pending
test "Divide two positive rational numbers" do
assert RationalNumbers.divide_by({1, 2}, {2, 3}) == {3, 4}
end
# @tag :pending
test "Divide a positive rational number by a negative rational number" do
assert RationalNumbers.divide_by({1, 2}, {-2, 3}) == {-3, 4}
end
# @tag :pending
test "Divide two negative rational numbers" do
assert RationalNumbers.divide_by({-1, 2}, {-2, 3}) == {3, 4}
end
# @tag :pending
test "Divide a rational number by 1" do
assert RationalNumbers.divide_by({1, 2}, {1, 1}) == {1, 2}
end
end
describe "Absolute value" do
# @tag :pending
test "Absolute value of a positive rational number" do
assert RationalNumbers.abs({1, 2}) == {1, 2}
end
# @tag :pending
test "Absolute value of a positive rational number with negative numerator and denominator" do
assert RationalNumbers.abs({-1, -2}) == {1, 2}
end
# @tag :pending
test "Absolute value of a negative rational number" do
assert RationalNumbers.abs({-1, 2}) == {1, 2}
end
# @tag :pending
test "Absolute value of a negative rational number with negative denominator" do
assert RationalNumbers.abs({1, -2}) == {1, 2}
end
# @tag :pending
test "Absolute value of zero" do
assert RationalNumbers.abs({0, 1}) == {0, 1}
end
# @tag :pending
test "Absolute value of a rational number is reduced to lowest terms" do
assert RationalNumbers.abs({2, 4}) == {1, 2}
end
end
describe "Exponentiation of a rational number" do
# @tag :pending
test "Raise a positive rational number to a positive integer power" do
assert RationalNumbers.pow_rational({1, 2}, 3) == {1, 8}
end
# @tag :pending
test "Raise a negative rational number to a positive integer power" do
assert RationalNumbers.pow_rational({-1, 2}, 3) == {-1, 8}
end
# @tag :pending
test "Raise a positive rational number to a negative integer power" do
assert RationalNumbers.pow_rational({3, 5}, -2) == {25, 9}
end
# @tag :pending
test "Raise a negative rational number to an even negative integer power" do
assert RationalNumbers.pow_rational({-3, 5}, -2) == {25, 9}
end
# @tag :pending
test "Raise a negative rational number to an odd negative integer power" do
assert RationalNumbers.pow_rational({-3, 5}, -3) == {-125, 27}
end
# @tag :pending
test "Raise zero to an integer power" do
assert RationalNumbers.pow_rational({0, 1}, 5) == {0, 1}
end
# @tag :pending
test "Raise one to an integer power" do
assert RationalNumbers.pow_rational({1, 1}, 4) == {1, 1}
end
# @tag :pending
test "Raise a positive rational number to the power of zero" do
assert RationalNumbers.pow_rational({1, 2}, 0) == {1, 1}
end
# @tag :pending
test "Raise a negative rational number to the power of zero" do
assert RationalNumbers.pow_rational({-1, 2}, 0) == {1, 1}
end
end
describe "Exponentiation of a real number to a rational number" do
# @tag :pending
test "Raise a real number to a positive rational number" do
x = 8
r = {4, 3}
result = 16.0
assert_in_delta RationalNumbers.pow_real(x, r), result, 1.0e-10
end
# @tag :pending
test "Raise a real number to a negative rational number" do
x = 9
r = {-1, 2}
result = 0.3333333333333333
assert_in_delta RationalNumbers.pow_real(x, r), result, 1.0e-10
end
# @tag :pending
test "Raise a real number to a zero rational number" do
x = 2
r = {0, 1}
result = 1.0
assert_in_delta RationalNumbers.pow_real(x, r), result, 1.0e-10
end
end
describe "Reduction to lowest terms" do
# @tag :pending
test "Reduce a positive rational number to lowest terms" do
assert RationalNumbers.reduce({2, 4}) == {1, 2}
end
# @tag :pending
test "Reduce places the minus sign on the numerator" do
assert RationalNumbers.reduce({3, -4}) == {-3, 4}
end
# @tag :pending
test "Reduce a negative rational number to lowest terms" do
assert RationalNumbers.reduce({-4, 6}) == {-2, 3}
end
# @tag :pending
test "Reduce a rational number with a negative denominator to lowest terms" do
assert RationalNumbers.reduce({3, -9}) == {-1, 3}
end
# @tag :pending
test "Reduce zero to lowest terms" do
assert RationalNumbers.reduce({0, 6}) == {0, 1}
end
# @tag :pending
test "Reduce an integer to lowest terms" do
assert RationalNumbers.reduce({-14, 7}) == {-2, 1}
end
# @tag :pending
test "Reduce one to lowest terms" do
assert RationalNumbers.reduce({13, 13}) == {1, 1}
end
end
end

View File

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