diff --git a/config.json b/config.json index 007cf86..4ba69bd 100644 --- a/config.json +++ b/config.json @@ -470,6 +470,14 @@ "practices": [], "prerequisites": [], "difficulty": 6 + }, + { + "slug": "complex-numbers", + "name": "Complex Numbers", + "uuid": "29567d07-2cf5-444b-aa82-5cbd6a9820d0", + "practices": [], + "prerequisites": [], + "difficulty": 6 } ] }, diff --git a/exercises/practice/complex-numbers/.busted b/exercises/practice/complex-numbers/.busted new file mode 100644 index 0000000..86b84e7 --- /dev/null +++ b/exercises/practice/complex-numbers/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} diff --git a/exercises/practice/complex-numbers/.docs/instructions.append.md b/exercises/practice/complex-numbers/.docs/instructions.append.md new file mode 100644 index 0000000..22e8a2f --- /dev/null +++ b/exercises/practice/complex-numbers/.docs/instructions.append.md @@ -0,0 +1,17 @@ +# dummy + +## MoonScript-specific instructions + +This is an object-oriented exercise: you're implementing ComplexNumbers as a class. + +The tests will be comparing two complex number instances for equality. +This means your class will have to implement the `__eq` [metamethod][metamethods]. + +The tests also perform arithmetic operations on complex numbers, so there are arithmetic metamethods to implement. + +~~~~exercism/caution +Some of the complex number functions will be doing math with floating point numbers. +Your "equality" will have to deal with numbers being _approximately equal_. +~~~~ + +[metamethods]: https://www.lua.org/manual/5.4/manual.html#2.4 diff --git a/exercises/practice/complex-numbers/.docs/instructions.md b/exercises/practice/complex-numbers/.docs/instructions.md new file mode 100644 index 0000000..2b8a7a4 --- /dev/null +++ b/exercises/practice/complex-numbers/.docs/instructions.md @@ -0,0 +1,100 @@ +# Instructions + +A **complex number** is expressed in the form `z = a + b * i`, where: + +- `a` is the **real part** (a real number), + +- `b` is the **imaginary part** (also a real number), and + +- `i` is the **imaginary unit** satisfying `i^2 = -1`. + +## Operations on Complex Numbers + +### Conjugate + +The conjugate of the complex number `z = a + b * i` is given by: + +```text +zc = a - b * i +``` + +### Absolute Value + +The absolute value (or modulus) of `z` is defined as: + +```text +|z| = sqrt(a^2 + b^2) +``` + +The square of the absolute value is computed as the product of `z` and its conjugate `zc`: + +```text +|z|^2 = z * zc = a^2 + b^2 +``` + +### Addition + +The sum of two complex numbers `z1 = a + b * i` and `z2 = c + d * i` is computed by adding their real and imaginary parts separately: + +```text +z1 + z2 = (a + b * i) + (c + d * i) + = (a + c) + (b + d) * i +``` + +### Subtraction + +The difference of two complex numbers is obtained by subtracting their respective parts: + +```text +z1 - z2 = (a + b * i) - (c + d * i) + = (a - c) + (b - d) * i +``` + +### Multiplication + +The product of two complex numbers is defined as: + +```text +z1 * z2 = (a + b * i) * (c + d * i) + = (a * c - b * d) + (b * c + a * d) * i +``` + +### Reciprocal + +The reciprocal of a non-zero complex number is given by: + +```text +1 / z = 1 / (a + b * i) + = a / (a^2 + b^2) - b / (a^2 + b^2) * i +``` + +### Division + +The division of one complex number by another is given by: + +```text +z1 / z2 = z1 * (1 / z2) + = (a + b * i) / (c + d * i) + = (a * c + b * d) / (c^2 + d^2) + (b * c - a * d) / (c^2 + d^2) * i +``` + +### Exponentiation + +Raising _e_ (the base of the natural logarithm) to a complex exponent can be expressed using Euler's formula: + +```text +e^(a + b * i) = e^a * e^(b * i) + = e^a * (cos(b) + i * sin(b)) +``` + +## Implementation Requirements + +Given that you should not use built-in support for complex numbers, implement the following operations: + +- **addition** of two complex numbers +- **subtraction** of two complex numbers +- **multiplication** of two complex numbers +- **division** of two complex numbers +- **conjugate** of a complex number +- **absolute value** of a complex number +- **exponentiation** of _e_ (the base of the natural logarithm) to a complex number diff --git a/exercises/practice/complex-numbers/.meta/config.json b/exercises/practice/complex-numbers/.meta/config.json new file mode 100644 index 0000000..3c957ac --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "complex_numbers.moon" + ], + "test": [ + "complex_numbers_spec.moon" + ], + "example": [ + ".meta/example.moon" + ] + }, + "blurb": "Implement complex numbers.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Complex_number" +} diff --git a/exercises/practice/complex-numbers/.meta/example.moon b/exercises/practice/complex-numbers/.meta/example.moon new file mode 100644 index 0000000..6d8a453 --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/example.moon @@ -0,0 +1,44 @@ +class ComplexNumber + new: (@r, @i = 0) => + + real: => @r + imaginary: => @i + + conjugate: => @@ @r, -@i + + abs: => math.sqrt(@r^2 + @i^2) + + __add: (other) => + @@ (@r + other.r), (@i + other.i) + + __sub: (other) => + @@ (@r - other.r), (@i - other.i) + + __mul: (other) => + a = @r * other.r - @i * other.i + b = @i * other.r + @r * other.i + @@ a, b + + reciprocal: => + denom = @r^2 + @i^2 + @@ @r / denom, -@i / denom + + __div: (other) => self * other\reciprocal! + + exp: => + factor = math.exp(@r) + @@ factor * math.cos(@i), factor * math.sin(@i) + + __eq: (other) => + approx_equal = (a, b) -> math.abs(a - b) <= 1e-15 + approx_equal(@r, other.r) and approx_equal(@i, other.i) + + +tocomplex = (a) -> if type(a) == 'number' then ComplexNumber a else a + +add = (a, b) -> tocomplex(a) + tocomplex(b) +sub = (a, b) -> tocomplex(a) - tocomplex(b) +mul = (a, b) -> tocomplex(a) * tocomplex(b) +div = (a, b) -> tocomplex(a) / tocomplex(b) + +{ :ComplexNumber, :add, :sub, :mul, :div } diff --git a/exercises/practice/complex-numbers/.meta/spec_generator.moon b/exercises/practice/complex-numbers/.meta/spec_generator.moon new file mode 100644 index 0000000..6ef3d3e --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/spec_generator.moon @@ -0,0 +1,52 @@ +op = add: '+', sub: '-', mul: '*', div: '/' + +cn = (z) -> + switch type(z) + when 'number' then z + when 'table' then "ComplexNumber(#{z[1]}, #{z[2]})" + + +require 'moon.all' +{ + module_imports: {'ComplexNumber', 'add', 'sub', 'mul', 'div'}, + + test_helpers: [[ + pi = math.pi + e = math.exp(1) + ln = math.log +]] + + generate_test: (case, level) -> + local lines + switch case.property + when 'real', 'imaginary', 'abs' + lines = { + "c = #{cn case.input.z}", + "result = c\\#{case.property}!", + "assert.are.equal #{case.expected}, result" + } + when 'add', 'sub', 'mul', 'div' + if type(case.input.z1) == 'number' or type(case.input.z2) == 'number' + lines = { + "result = #{case.property} #{cn case.input.z1}, #{cn case.input.z2}", + "expected = #{cn case.expected}", + "assert.are.equal expected, result" + } + else + lines = { + "c1 = #{cn case.input.z1}", + "c2 = #{cn case.input.z2}", + "result = c1 #{op[case.property]} c2", + "expected = #{cn case.expected}", + "assert.are.equal expected, result" + } + when 'conjugate', 'exp' + lines = { + "c = #{cn case.input.z}", + "result = c\\#{case.property}!", + "expected = #{cn case.expected}", + "assert.are.equal expected, result" + } + + table.concat [indent line, level for line in *lines], '\n' +} 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/complex_numbers.moon b/exercises/practice/complex-numbers/complex_numbers.moon new file mode 100644 index 0000000..0dccf06 --- /dev/null +++ b/exercises/practice/complex-numbers/complex_numbers.moon @@ -0,0 +1,24 @@ +class ComplexNumber + new: (...) => error 'Implement the constructor' + + real: => error 'Implement the real method' + + imaginary: => error 'Implement the imaginary method' + + conjugate: => error 'Implement the conjugate method' + + abs: => error 'Implement the abs method' + + exp: => error 'Implement the exp method' + + __eq: (other) => error 'Implement the __eq metamethod' + + -- Also implement metamethods for the arithmetic operations + +-- For arithmetic operations between real and complex numbers +add = (a, b) -> error 'Implement the add function' +sub = (a, b) -> error 'Implement the sub function' +mul = (a, b) -> error 'Implement the mul function' +div = (a, b) -> error 'Implement the div function' + +{ :ComplexNumber, :add, :sub, :mul, :div } diff --git a/exercises/practice/complex-numbers/complex_numbers_spec.moon b/exercises/practice/complex-numbers/complex_numbers_spec.moon new file mode 100644 index 0000000..733d7db --- /dev/null +++ b/exercises/practice/complex-numbers/complex_numbers_spec.moon @@ -0,0 +1,251 @@ +import ComplexNumber, add, sub, mul, div from require 'complex_numbers' + +describe 'complex-numbers', -> + pi = math.pi + e = math.exp(1) + ln = math.log + + describe 'Real part', -> + it 'Real part of a purely real number', -> + c = ComplexNumber(1, 0) + result = c\real! + assert.are.equal 1, result + + pending 'Real part of a purely imaginary number', -> + c = ComplexNumber(0, 1) + result = c\real! + assert.are.equal 0, result + + pending 'Real part of a number with real and imaginary part', -> + c = ComplexNumber(1, 2) + result = c\real! + assert.are.equal 1, result + + describe 'Imaginary part', -> + pending 'Imaginary part of a purely real number', -> + c = ComplexNumber(1, 0) + result = c\imaginary! + assert.are.equal 0, result + + pending 'Imaginary part of a purely imaginary number', -> + c = ComplexNumber(0, 1) + result = c\imaginary! + assert.are.equal 1, result + + pending 'Imaginary part of a number with real and imaginary part', -> + c = ComplexNumber(1, 2) + result = c\imaginary! + assert.are.equal 2, result + + pending 'Imaginary unit', -> + c1 = ComplexNumber(0, 1) + c2 = ComplexNumber(0, 1) + result = c1 * c2 + expected = ComplexNumber(-1, 0) + assert.are.equal expected, result + + describe 'Arithmetic', -> + describe 'Addition', -> + pending 'Add purely real numbers', -> + c1 = ComplexNumber(1, 0) + c2 = ComplexNumber(2, 0) + result = c1 + c2 + expected = ComplexNumber(3, 0) + assert.are.equal expected, result + + pending 'Add purely imaginary numbers', -> + c1 = ComplexNumber(0, 1) + c2 = ComplexNumber(0, 2) + result = c1 + c2 + expected = ComplexNumber(0, 3) + assert.are.equal expected, result + + pending 'Add numbers with real and imaginary part', -> + c1 = ComplexNumber(1, 2) + c2 = ComplexNumber(3, 4) + result = c1 + c2 + expected = ComplexNumber(4, 6) + assert.are.equal expected, result + + describe 'Subtraction', -> + pending 'Subtract purely real numbers', -> + c1 = ComplexNumber(1, 0) + c2 = ComplexNumber(2, 0) + result = c1 - c2 + expected = ComplexNumber(-1, 0) + assert.are.equal expected, result + + pending 'Subtract purely imaginary numbers', -> + c1 = ComplexNumber(0, 1) + c2 = ComplexNumber(0, 2) + result = c1 - c2 + expected = ComplexNumber(0, -1) + assert.are.equal expected, result + + pending 'Subtract numbers with real and imaginary part', -> + c1 = ComplexNumber(1, 2) + c2 = ComplexNumber(3, 4) + result = c1 - c2 + expected = ComplexNumber(-2, -2) + assert.are.equal expected, result + + describe 'Multiplication', -> + pending 'Multiply purely real numbers', -> + c1 = ComplexNumber(1, 0) + c2 = ComplexNumber(2, 0) + result = c1 * c2 + expected = ComplexNumber(2, 0) + assert.are.equal expected, result + + pending 'Multiply purely imaginary numbers', -> + c1 = ComplexNumber(0, 1) + c2 = ComplexNumber(0, 2) + result = c1 * c2 + expected = ComplexNumber(-2, 0) + assert.are.equal expected, result + + pending 'Multiply numbers with real and imaginary part', -> + c1 = ComplexNumber(1, 2) + c2 = ComplexNumber(3, 4) + result = c1 * c2 + expected = ComplexNumber(-5, 10) + assert.are.equal expected, result + + describe 'Division', -> + pending 'Divide purely real numbers', -> + c1 = ComplexNumber(1, 0) + c2 = ComplexNumber(2, 0) + result = c1 / c2 + expected = ComplexNumber(0.5, 0) + assert.are.equal expected, result + + pending 'Divide purely imaginary numbers', -> + c1 = ComplexNumber(0, 1) + c2 = ComplexNumber(0, 2) + result = c1 / c2 + expected = ComplexNumber(0.5, 0) + assert.are.equal expected, result + + pending 'Divide numbers with real and imaginary part', -> + c1 = ComplexNumber(1, 2) + c2 = ComplexNumber(3, 4) + result = c1 / c2 + expected = ComplexNumber(0.44, 0.08) + assert.are.equal expected, result + + describe 'Absolute value', -> + pending 'Absolute value of a positive purely real number', -> + c = ComplexNumber(5, 0) + result = c\abs! + assert.are.equal 5, result + + pending 'Absolute value of a negative purely real number', -> + c = ComplexNumber(-5, 0) + result = c\abs! + assert.are.equal 5, result + + pending 'Absolute value of a purely imaginary number with positive imaginary part', -> + c = ComplexNumber(0, 5) + result = c\abs! + assert.are.equal 5, result + + pending 'Absolute value of a purely imaginary number with negative imaginary part', -> + c = ComplexNumber(0, -5) + result = c\abs! + assert.are.equal 5, result + + pending 'Absolute value of a number with real and imaginary part', -> + c = ComplexNumber(3, 4) + result = c\abs! + assert.are.equal 5, result + + describe 'Complex conjugate', -> + pending 'Conjugate a purely real number', -> + c = ComplexNumber(5, 0) + result = c\conjugate! + expected = ComplexNumber(5, 0) + assert.are.equal expected, result + + pending 'Conjugate a purely imaginary number', -> + c = ComplexNumber(0, 5) + result = c\conjugate! + expected = ComplexNumber(0, -5) + assert.are.equal expected, result + + pending 'Conjugate a number with real and imaginary part', -> + c = ComplexNumber(1, 1) + result = c\conjugate! + expected = ComplexNumber(1, -1) + assert.are.equal expected, result + + describe 'Complex exponential function', -> + pending "Euler's identity/formula", -> + c = ComplexNumber(0, pi) + result = c\exp! + expected = ComplexNumber(-1, 0) + assert.are.equal expected, result + + pending 'Exponential of 0', -> + c = ComplexNumber(0, 0) + result = c\exp! + expected = ComplexNumber(1, 0) + assert.are.equal expected, result + + pending 'Exponential of a purely real number', -> + c = ComplexNumber(1, 0) + result = c\exp! + expected = ComplexNumber(e, 0) + assert.are.equal expected, result + + pending 'Exponential of a number with real and imaginary part', -> + c = ComplexNumber(ln(2), pi) + result = c\exp! + expected = ComplexNumber(-2, 0) + assert.are.equal expected, result + + pending 'Exponential resulting in a number with real and imaginary part', -> + c = ComplexNumber(ln(2)/2, pi/4) + result = c\exp! + expected = ComplexNumber(1, 1) + assert.are.equal expected, result + + describe 'Operations between real numbers and complex numbers', -> + pending 'Add real number to complex number', -> + result = add ComplexNumber(1, 2), 5 + expected = ComplexNumber(6, 2) + assert.are.equal expected, result + + pending 'Add complex number to real number', -> + result = add 5, ComplexNumber(1, 2) + expected = ComplexNumber(6, 2) + assert.are.equal expected, result + + pending 'Subtract real number from complex number', -> + result = sub ComplexNumber(5, 7), 4 + expected = ComplexNumber(1, 7) + assert.are.equal expected, result + + pending 'Subtract complex number from real number', -> + result = sub 4, ComplexNumber(5, 7) + expected = ComplexNumber(-1, -7) + assert.are.equal expected, result + + pending 'Multiply complex number by real number', -> + result = mul ComplexNumber(2, 5), 5 + expected = ComplexNumber(10, 25) + assert.are.equal expected, result + + pending 'Multiply real number by complex number', -> + result = mul 5, ComplexNumber(2, 5) + expected = ComplexNumber(10, 25) + assert.are.equal expected, result + + pending 'Divide complex number by real number', -> + result = div ComplexNumber(10, 100), 10 + expected = ComplexNumber(1, 10) + assert.are.equal expected, result + + pending 'Divide real number by complex number', -> + result = div 5, ComplexNumber(1, 1) + expected = ComplexNumber(2.5, -2.5) + assert.are.equal expected, result