diff --git a/config.json b/config.json index 970222b..969ff49 100644 --- a/config.json +++ b/config.json @@ -95,6 +95,14 @@ "prerequisites": [], "difficulty": 2 }, + { + "slug": "complex-numbers", + "name": "Complex Numbers", + "uuid": "b7934691-ac3e-4aee-acd2-5e01536c43e8", + "practices": [], + "prerequisites": [], + "difficulty": 2 + }, { "slug": "darts", "name": "Darts", diff --git a/exercises/practice/complex-numbers/.docs/instructions.md b/exercises/practice/complex-numbers/.docs/instructions.md new file mode 100644 index 0000000..50b19ae --- /dev/null +++ b/exercises/practice/complex-numbers/.docs/instructions.md @@ -0,0 +1,29 @@ +# 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. diff --git a/exercises/practice/complex-numbers/.meta/Example.roc b/exercises/practice/complex-numbers/.meta/Example.roc new file mode 100644 index 0000000..f383b90 --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/Example.roc @@ -0,0 +1,53 @@ +module [real, imaginary, add, sub, mul, div, conjugate, abs, exp] + +Complex : { re : F64, im : F64 } + +real : Complex -> F64 +real = \z -> z.re + +imaginary : Complex -> F64 +imaginary = \z -> z.im + +add : Complex, Complex -> Complex +add = \z1, z2 -> { + re: z1.re + z2.re, + im: z1.im + z2.im, +} + +sub : Complex, Complex -> Complex +sub = \z1, z2 -> { + re: z1.re - z2.re, + im: z1.im - z2.im, +} + +mul : Complex, Complex -> Complex +mul = \z1, z2 -> { + re: z1.re * z2.re - z1.im * z2.im, + im: z1.re * z2.im + z1.im * z2.re, +} + +div : Complex, Complex -> Complex +div = \z1, z2 -> + denominator = z2.re * z2.re + z2.im * z2.im + { + re: (z1.re * z2.re + z1.im * z2.im) / denominator, + im: (z1.im * z2.re - z1.re * z2.im) / denominator, + } + +conjugate : Complex -> Complex +conjugate = \z -> { + re: z.re, + im: -z.im, +} + +abs : Complex -> F64 +abs = \z -> + z.re * z.re + z.im * z.im |> Num.sqrt + +exp : Complex -> Complex +exp = \z -> + factor = Num.e |> Num.pow z.re + { + re: factor * Num.cos z.im, + im: factor * Num.sin z.im, + } diff --git a/exercises/practice/complex-numbers/.meta/config.json b/exercises/practice/complex-numbers/.meta/config.json new file mode 100644 index 0000000..4270326 --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "ageron" + ], + "files": { + "solution": [ + "ComplexNumbers.roc" + ], + "test": [ + "complex-numbers-test.roc" + ], + "example": [ + ".meta/Example.roc" + ] + }, + "blurb": "Implement complex numbers.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Complex_number" +} diff --git a/exercises/practice/complex-numbers/.meta/plugins.py b/exercises/practice/complex-numbers/.meta/plugins.py new file mode 100644 index 0000000..394b212 --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/plugins.py @@ -0,0 +1,25 @@ +float_map = { + "e": "Num.e", + "ln(2)": "Num.log 2f64", + "ln(2)/2": "Num.log 2f64 / 2", + "pi": "Num.pi", + "pi/4": "Num.pi / 4", +} + + +def to_roc(value): + if isinstance(value, str): + return float_map[value] + else: + return f"{value}" + + +def to_complex_number(value): + if isinstance(value, list): + assert len(value) == 2 + re = to_roc(value[0]) + im = to_roc(value[1]) + else: + re = to_roc(value) + im = 0 + return f"{{ re: {re}, im: {im} }}" diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2 new file mode 100644 index 0000000..aa15243 --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/template.j2 @@ -0,0 +1,51 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} +{{ macros.header() }} + +import {{ exercise | to_pascal }} exposing [real, imaginary, add, sub, mul, div, conjugate, abs, exp] + +isApproxEq = \z1, z2 -> + z1.re |> Num.isApproxEq z2.re {} && z1.im |> Num.isApproxEq z2.im {} + +{% for supercase in cases %} +### +### {{ supercase["description"] }} +### + +{% for case in supercase["cases"] -%} +{%- if case["cases"] %} +## {{ case["description"] }} +{%- set subcases = case["cases"] %} +{%- else %} +{%- set subcases = [case] %} +{%- endif %} + +{% for subcase in subcases -%} +# {{ subcase["description"] }} +{%- if subcase["input"]["z"] %} +expect + z = {{ plugins.to_complex_number(subcase["input"]["z"]) }} + result = {{ subcase["property"] }} z + {%- if subcase["expected"] is iterable and subcase["expected"] is not string %} + expected = {{ plugins.to_complex_number(subcase["expected"]) }} + result |> isApproxEq expected + {%- else %} + result |> Num.isApproxEq {{ subcase["expected"] | to_roc }} {} + {%- endif %} + +{%- elif subcase["input"]["z1"] %} +expect + z1 = {{ plugins.to_complex_number(subcase["input"]["z1"]) }} + z2 = {{ plugins.to_complex_number(subcase["input"]["z2"]) }} + result = z1 |> {{ subcase["property"] }} z2 + expected = {{ plugins.to_complex_number(subcase["expected"]) }} + result |> isApproxEq expected +{%- else %} +# This test case is not implemented: perhaps you can implement it? +{%- endif %} + +{% endfor %} + +{% endfor %} +{% endfor %} + diff --git a/exercises/practice/complex-numbers/.meta/tests.toml b/exercises/practice/complex-numbers/.meta/tests.toml new file mode 100644 index 0000000..dffb1f2 --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/tests.toml @@ -0,0 +1,130 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[9f98e133-eb7f-45b0-9676-cce001cd6f7a] +description = "Real part -> Real part of a purely real number" + +[07988e20-f287-4bb7-90cf-b32c4bffe0f3] +description = "Real part -> Real part of a purely imaginary number" + +[4a370e86-939e-43de-a895-a00ca32da60a] +description = "Real part -> Real part of a number with real and imaginary part" + +[9b3fddef-4c12-4a99-b8f8-e3a42c7ccef6] +description = "Imaginary part -> Imaginary part of a purely real number" + +[a8dafedd-535a-4ed3-8a39-fda103a2b01e] +description = "Imaginary part -> Imaginary part of a purely imaginary number" + +[0f998f19-69ee-4c64-80ef-01b086feab80] +description = "Imaginary part -> Imaginary part of a number with real and imaginary part" + +[a39b7fd6-6527-492f-8c34-609d2c913879] +description = "Imaginary unit" + +[9a2c8de9-f068-4f6f-b41c-82232cc6c33e] +description = "Arithmetic -> Addition -> Add purely real numbers" + +[657c55e1-b14b-4ba7-bd5c-19db22b7d659] +description = "Arithmetic -> Addition -> Add purely imaginary numbers" + +[4e1395f5-572b-4ce8-bfa9-9a63056888da] +description = "Arithmetic -> Addition -> Add numbers with real and imaginary part" + +[1155dc45-e4f7-44b8-af34-a91aa431475d] +description = "Arithmetic -> Subtraction -> Subtract purely real numbers" + +[f95e9da8-acd5-4da4-ac7c-c861b02f774b] +description = "Arithmetic -> Subtraction -> Subtract purely imaginary numbers" + +[f876feb1-f9d1-4d34-b067-b599a8746400] +description = "Arithmetic -> Subtraction -> Subtract numbers with real and imaginary part" + +[8a0366c0-9e16-431f-9fd7-40ac46ff4ec4] +description = "Arithmetic -> Multiplication -> Multiply purely real numbers" + +[e560ed2b-0b80-4b4f-90f2-63cefc911aaf] +description = "Arithmetic -> Multiplication -> Multiply purely imaginary numbers" + +[4d1d10f0-f8d4-48a0-b1d0-f284ada567e6] +description = "Arithmetic -> Multiplication -> Multiply numbers with real and imaginary part" + +[b0571ddb-9045-412b-9c15-cd1d816d36c1] +description = "Arithmetic -> Division -> Divide purely real numbers" + +[5bb4c7e4-9934-4237-93cc-5780764fdbdd] +description = "Arithmetic -> Division -> Divide purely imaginary numbers" + +[c4e7fef5-64ac-4537-91c2-c6529707701f] +description = "Arithmetic -> Division -> Divide numbers with real and imaginary part" + +[c56a7332-aad2-4437-83a0-b3580ecee843] +description = "Absolute value -> Absolute value of a positive purely real number" + +[cf88d7d3-ee74-4f4e-8a88-a1b0090ecb0c] +description = "Absolute value -> Absolute value of a negative purely real number" + +[bbe26568-86c1-4bb4-ba7a-da5697e2b994] +description = "Absolute value -> Absolute value of a purely imaginary number with positive imaginary part" + +[3b48233d-468e-4276-9f59-70f4ca1f26f3] +description = "Absolute value -> Absolute value of a purely imaginary number with negative imaginary part" + +[fe400a9f-aa22-4b49-af92-51e0f5a2a6d3] +description = "Absolute value -> Absolute value of a number with real and imaginary part" + +[fb2d0792-e55a-4484-9443-df1eddfc84a2] +description = "Complex conjugate -> Conjugate a purely real number" + +[e37fe7ac-a968-4694-a460-66cb605f8691] +description = "Complex conjugate -> Conjugate a purely imaginary number" + +[f7704498-d0be-4192-aaf5-a1f3a7f43e68] +description = "Complex conjugate -> Conjugate a number with real and imaginary part" + +[6d96d4c6-2edb-445b-94a2-7de6d4caaf60] +description = "Complex exponential function -> Euler's identity/formula" + +[2d2c05a0-4038-4427-a24d-72f6624aa45f] +description = "Complex exponential function -> Exponential of 0" + +[ed87f1bd-b187-45d6-8ece-7e331232c809] +description = "Complex exponential function -> Exponential of a purely real number" + +[08eedacc-5a95-44fc-8789-1547b27a8702] +description = "Complex exponential function -> Exponential of a number with real and imaginary part" + +[d2de4375-7537-479a-aa0e-d474f4f09859] +description = "Complex exponential function -> Exponential resulting in a number with real and imaginary part" + +[06d793bf-73bd-4b02-b015-3030b2c952ec] +description = "Operations between real numbers and complex numbers -> Add real number to complex number" + +[d77dbbdf-b8df-43f6-a58d-3acb96765328] +description = "Operations between real numbers and complex numbers -> Add complex number to real number" + +[20432c8e-8960-4c40-ba83-c9d910ff0a0f] +description = "Operations between real numbers and complex numbers -> Subtract real number from complex number" + +[b4b38c85-e1bf-437d-b04d-49bba6e55000] +description = "Operations between real numbers and complex numbers -> Subtract complex number from real number" + +[dabe1c8c-b8f4-44dd-879d-37d77c4d06bd] +description = "Operations between real numbers and complex numbers -> Multiply complex number by real number" + +[6c81b8c8-9851-46f0-9de5-d96d314c3a28] +description = "Operations between real numbers and complex numbers -> Multiply real number by complex number" + +[8a400f75-710e-4d0c-bcb4-5e5a00c78aa0] +description = "Operations between real numbers and complex numbers -> Divide complex number by real number" + +[9a867d1b-d736-4c41-a41e-90bd148e9d5e] +description = "Operations between real numbers and complex numbers -> Divide real number by complex number" diff --git a/exercises/practice/complex-numbers/ComplexNumbers.roc b/exercises/practice/complex-numbers/ComplexNumbers.roc new file mode 100644 index 0000000..0e51f3e --- /dev/null +++ b/exercises/practice/complex-numbers/ComplexNumbers.roc @@ -0,0 +1,39 @@ +module [real, imaginary, add, sub, mul, div, conjugate, abs, exp] + +Complex : { re : F64, im : F64 } + +real : Complex -> F64 +real = \z -> + crash "Please implement the 'real' function" + +imaginary : Complex -> F64 +imaginary = \z -> + crash "Please implement the 'imaginary' function" + +add : Complex, Complex -> Complex +add = \z1, z2 -> + crash "Please implement the 'add' function" + +sub : Complex, Complex -> Complex +sub = \z1, z2 -> + crash "Please implement the 'sub' function" + +mul : Complex, Complex -> Complex +mul = \z1, z2 -> + crash "Please implement the 'mul' function" + +div : Complex, Complex -> Complex +div = \z1, z2 -> + crash "Please implement the 'div' function" + +conjugate : Complex -> Complex +conjugate = \z -> + crash "Please implement the 'conjugate' function" + +abs : Complex -> F64 +abs = \z -> + crash "Please implement the 'abs' function" + +exp : Complex -> Complex +exp = \z -> + crash "Please implement the 'exp' function" diff --git a/exercises/practice/complex-numbers/complex-numbers-test.roc b/exercises/practice/complex-numbers/complex-numbers-test.roc new file mode 100644 index 0000000..ad948d2 --- /dev/null +++ b/exercises/practice/complex-numbers/complex-numbers-test.roc @@ -0,0 +1,337 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json +# File last updated on 2024-09-20 +app [main] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", +} + +main = + Task.ok {} + +import ComplexNumbers exposing [real, imaginary, add, sub, mul, div, conjugate, abs, exp] + +isApproxEq = \z1, z2 -> + z1.re |> Num.isApproxEq z2.re {} && z1.im |> Num.isApproxEq z2.im {} + +### +### Real part +### + +# Real part of a purely real number +expect + z = { re: 1, im: 0 } + result = real z + result |> Num.isApproxEq 1 {} + +# Real part of a purely imaginary number +expect + z = { re: 0, im: 1 } + result = real z + result |> Num.isApproxEq 0 {} + +# Real part of a number with real and imaginary part +expect + z = { re: 1, im: 2 } + result = real z + result |> Num.isApproxEq 1 {} + +### +### Imaginary part +### + +# Imaginary part of a purely real number +expect + z = { re: 1, im: 0 } + result = imaginary z + result |> Num.isApproxEq 0 {} + +# Imaginary part of a purely imaginary number +expect + z = { re: 0, im: 1 } + result = imaginary z + result |> Num.isApproxEq 1 {} + +# Imaginary part of a number with real and imaginary part +expect + z = { re: 1, im: 2 } + result = imaginary z + result |> Num.isApproxEq 2 {} + +### +### Imaginary unit +### + +### +### Arithmetic +### + +## Addition + +# Add purely real numbers +expect + z1 = { re: 1, im: 0 } + z2 = { re: 2, im: 0 } + result = z1 |> add z2 + expected = { re: 3, im: 0 } + result |> isApproxEq expected + +# Add purely imaginary numbers +expect + z1 = { re: 0, im: 1 } + z2 = { re: 0, im: 2 } + result = z1 |> add z2 + expected = { re: 0, im: 3 } + result |> isApproxEq expected + +# Add numbers with real and imaginary part +expect + z1 = { re: 1, im: 2 } + z2 = { re: 3, im: 4 } + result = z1 |> add z2 + expected = { re: 4, im: 6 } + result |> isApproxEq expected + +## Subtraction + +# Subtract purely real numbers +expect + z1 = { re: 1, im: 0 } + z2 = { re: 2, im: 0 } + result = z1 |> sub z2 + expected = { re: -1, im: 0 } + result |> isApproxEq expected + +# Subtract purely imaginary numbers +expect + z1 = { re: 0, im: 1 } + z2 = { re: 0, im: 2 } + result = z1 |> sub z2 + expected = { re: 0, im: -1 } + result |> isApproxEq expected + +# Subtract numbers with real and imaginary part +expect + z1 = { re: 1, im: 2 } + z2 = { re: 3, im: 4 } + result = z1 |> sub z2 + expected = { re: -2, im: -2 } + result |> isApproxEq expected + +## Multiplication + +# Multiply purely real numbers +expect + z1 = { re: 1, im: 0 } + z2 = { re: 2, im: 0 } + result = z1 |> mul z2 + expected = { re: 2, im: 0 } + result |> isApproxEq expected + +# Multiply purely imaginary numbers +expect + z1 = { re: 0, im: 1 } + z2 = { re: 0, im: 2 } + result = z1 |> mul z2 + expected = { re: -2, im: 0 } + result |> isApproxEq expected + +# Multiply numbers with real and imaginary part +expect + z1 = { re: 1, im: 2 } + z2 = { re: 3, im: 4 } + result = z1 |> mul z2 + expected = { re: -5, im: 10 } + result |> isApproxEq expected + +## Division + +# Divide purely real numbers +expect + z1 = { re: 1, im: 0 } + z2 = { re: 2, im: 0 } + result = z1 |> div z2 + expected = { re: 0.5, im: 0 } + result |> isApproxEq expected + +# Divide purely imaginary numbers +expect + z1 = { re: 0, im: 1 } + z2 = { re: 0, im: 2 } + result = z1 |> div z2 + expected = { re: 0.5, im: 0 } + result |> isApproxEq expected + +# Divide numbers with real and imaginary part +expect + z1 = { re: 1, im: 2 } + z2 = { re: 3, im: 4 } + result = z1 |> div z2 + expected = { re: 0.44, im: 0.08 } + result |> isApproxEq expected + +### +### Absolute value +### + +# Absolute value of a positive purely real number +expect + z = { re: 5, im: 0 } + result = abs z + result |> Num.isApproxEq 5 {} + +# Absolute value of a negative purely real number +expect + z = { re: -5, im: 0 } + result = abs z + result |> Num.isApproxEq 5 {} + +# Absolute value of a purely imaginary number with positive imaginary part +expect + z = { re: 0, im: 5 } + result = abs z + result |> Num.isApproxEq 5 {} + +# Absolute value of a purely imaginary number with negative imaginary part +expect + z = { re: 0, im: -5 } + result = abs z + result |> Num.isApproxEq 5 {} + +# Absolute value of a number with real and imaginary part +expect + z = { re: 3, im: 4 } + result = abs z + result |> Num.isApproxEq 5 {} + +### +### Complex conjugate +### + +# Conjugate a purely real number +expect + z = { re: 5, im: 0 } + result = conjugate z + expected = { re: 5, im: 0 } + result |> isApproxEq expected + +# Conjugate a purely imaginary number +expect + z = { re: 0, im: 5 } + result = conjugate z + expected = { re: 0, im: -5 } + result |> isApproxEq expected + +# Conjugate a number with real and imaginary part +expect + z = { re: 1, im: 1 } + result = conjugate z + expected = { re: 1, im: -1 } + result |> isApproxEq expected + +### +### Complex exponential function +### + +# Euler's identity/formula +expect + z = { re: 0, im: Num.pi } + result = exp z + expected = { re: -1, im: 0 } + result |> isApproxEq expected + +# Exponential of 0 +expect + z = { re: 0, im: 0 } + result = exp z + expected = { re: 1, im: 0 } + result |> isApproxEq expected + +# Exponential of a purely real number +expect + z = { re: 1, im: 0 } + result = exp z + expected = { re: Num.e, im: 0 } + result |> isApproxEq expected + +# Exponential of a number with real and imaginary part +expect + z = { re: Num.log 2f64, im: Num.pi } + result = exp z + expected = { re: -2, im: 0 } + result |> isApproxEq expected + +# Exponential resulting in a number with real and imaginary part +expect + z = { re: Num.log 2f64 / 2, im: Num.pi / 4 } + result = exp z + expected = { re: 1, im: 1 } + result |> isApproxEq expected + +### +### Operations between real numbers and complex numbers +### + +# Add real number to complex number +expect + z1 = { re: 1, im: 2 } + z2 = { re: 5, im: 0 } + result = z1 |> add z2 + expected = { re: 6, im: 2 } + result |> isApproxEq expected + +# Add complex number to real number +expect + z1 = { re: 5, im: 0 } + z2 = { re: 1, im: 2 } + result = z1 |> add z2 + expected = { re: 6, im: 2 } + result |> isApproxEq expected + +# Subtract real number from complex number +expect + z1 = { re: 5, im: 7 } + z2 = { re: 4, im: 0 } + result = z1 |> sub z2 + expected = { re: 1, im: 7 } + result |> isApproxEq expected + +# Subtract complex number from real number +expect + z1 = { re: 4, im: 0 } + z2 = { re: 5, im: 7 } + result = z1 |> sub z2 + expected = { re: -1, im: -7 } + result |> isApproxEq expected + +# Multiply complex number by real number +expect + z1 = { re: 2, im: 5 } + z2 = { re: 5, im: 0 } + result = z1 |> mul z2 + expected = { re: 10, im: 25 } + result |> isApproxEq expected + +# Multiply real number by complex number +expect + z1 = { re: 5, im: 0 } + z2 = { re: 2, im: 5 } + result = z1 |> mul z2 + expected = { re: 10, im: 25 } + result |> isApproxEq expected + +# Divide complex number by real number +expect + z1 = { re: 10, im: 100 } + z2 = { re: 10, im: 0 } + result = z1 |> div z2 + expected = { re: 1, im: 10 } + result |> isApproxEq expected + +# Divide real number by complex number +expect + z1 = { re: 5, im: 0 } + z2 = { re: 1, im: 1 } + result = z1 |> div z2 + expected = { re: 2.5, im: -2.5 } + result |> isApproxEq expected +