diff --git a/config.json b/config.json index 5f3acd5..7288f89 100644 --- a/config.json +++ b/config.json @@ -153,6 +153,14 @@ "prerequisites": [], "difficulty": 4 }, + { + "uuid": "26f834d8-6182-4f41-aa4f-a376e0c83def", + "slug": "complex-numbers", + "name": "Complex Numbers", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "uuid": "df360057-385e-4166-96e1-ed0629c02ca3", "slug": "two-fer", 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/config.json b/exercises/practice/complex-numbers/.meta/config.json new file mode 100644 index 0000000..ac3f600 --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/config.json @@ -0,0 +1,11 @@ +{ + "authors": ["m-charlton"], + "files": { + "solution": ["complex-numbers.v"], + "test": ["run_test.v"], + "example": [".meta/example.v"] + }, + "blurb": "Implement complex numbers.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Complex_number" +} diff --git a/exercises/practice/complex-numbers/.meta/example.v b/exercises/practice/complex-numbers/.meta/example.v new file mode 100644 index 0000000..f4ecd50 --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/example.v @@ -0,0 +1,116 @@ +module main + +import math { cos, pow, sin } + +struct Complex { + real f64 + imaginary f64 +} + +fn (c Complex) additive(other Complex, op fn (f64, f64) f64) Complex { + return Complex.new(op(c.real, other.real), op(c.imaginary, other.imaginary)) +} + +fn (c Complex) reciprocal() Complex { + denominator := pow(c.real, 2.0) + pow(c.imaginary, 2.0) + return Complex.new(c.real / denominator, -c.imaginary / denominator) +} + +// build a Complex number +pub fn Complex.new(real f64, imaginary f64) Complex { + return Complex{real, imaginary} +} + +pub fn (c Complex) real() f64 { + return c.real +} + +pub fn (c Complex) imaginary() f64 { + return c.imaginary +} + +pub fn (c Complex) conjugate() Complex { + return Complex.new(c.real, -c.imaginary) +} + +pub fn (c Complex) abs() f64 { + return math.hypot(c.real, c.imaginary) +} + +pub fn (c Complex) add(other Complex) Complex { + return c.additive(other, fn (a f64, b f64) f64 { + return a + b + }) +} + +pub fn (c Complex) sub(other Complex) Complex { + return c.additive(other, fn (a f64, b f64) f64 { + return a - b + }) +} + +pub fn (c Complex) exp() Complex { + e_to_creal := math.exp(c.real) + return Complex.new(e_to_creal * cos(c.imaginary), e_to_creal * sin(c.imaginary)) +} + +pub fn (c Complex) mul(other Complex) Complex { + return Complex.new(c.real * other.real - c.imaginary * other.imaginary, + c.imaginary * other.real + c.real * other.imaginary) +} + +pub fn (c Complex) div(other Complex) Complex { + return c.mul(other.reciprocal()) +} + +// add a real number 'r' to complex number 'c' +// c + r +pub fn (c Complex) add_real(r f64) Complex { + return Complex.new(c.real + r, c.imaginary) +} + +// add a complex number 'c' to a real number 'r': +// r + c +pub fn (c Complex) real_add(r f64) Complex { + // addition is commutative. + // r + c == c + r + return c.add_real(r) +} + +// subtract a real number 'r' from a complex number 'c': +// c - r +pub fn (c Complex) sub_real(r f64) Complex { + return c.add_real(-r) +} + +// subtract a complex number 'c' from 'r': +// r - c +pub fn (c Complex) real_sub(r f64) Complex { + return Complex.new(r - c.real, -c.imaginary) +} + +// multiply a complex number 'c' by real number 'r' +// c * r +pub fn (c Complex) mul_by_real(r f64) Complex { + return Complex.new(r * c.real, r * c.imaginary) +} + +// multiply a real number 'r' by a complex number 'c': +// r * c +pub fn (c Complex) real_mul(r f64) Complex { + // multiplication is commutative. + // r * c == c * r + return c.mul_by_real(r) +} + +// divide complex number 'c' by real number 'r' +// c / r +pub fn (c Complex) div_by_real(r f64) Complex { + return Complex.new(c.real / r, c.imaginary / r) +} + +// divide a real number 'r' by a complex number 'c' +// r / c +pub fn (c Complex) real_div(r f64) Complex { + return c.reciprocal().mul_by_real(r) +} 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.v b/exercises/practice/complex-numbers/complex-numbers.v new file mode 100644 index 0000000..2d9016f --- /dev/null +++ b/exercises/practice/complex-numbers/complex-numbers.v @@ -0,0 +1,75 @@ +module main + +struct Complex { +} + +// build a Complex number +pub fn Complex.new(real f64, imaginary f64) Complex { +} + +pub fn (c Complex) real() f64 { +} + +pub fn (c Complex) imaginary() f64 { +} + +pub fn (c Complex) conjugate() Complex { +} + +pub fn (c Complex) abs() f64 { +} + +pub fn (c Complex) add(other Complex) Complex { +} + +pub fn (c Complex) sub(other Complex) Complex { +} + +pub fn (c Complex) exp() Complex { +} + +pub fn (c Complex) mul(other Complex) Complex { +} + +pub fn (c Complex) div(other Complex) Complex { +} + +// add a real number 'r' to complex number 'c' +// c + r +pub fn (c Complex) add_real(r f64) Complex { +} + +// add a complex number 'c' to a real number 'r': +// r + c +pub fn (c Complex) real_add(r f64) Complex { +} + +// subtract a real number 'r' from a complex number 'c': +// c - r +pub fn (c Complex) sub_real(r f64) Complex { +} + +// subtract a complex number 'c' from 'r': +// r - c +pub fn (c Complex) real_sub(r f64) Complex { +} + +// multiply a complex number 'c' by real number 'r' +// c * r +pub fn (c Complex) mul_by_real(r f64) Complex { +} + +// multiply a real number 'r' by a complex number 'c': +// r * c +pub fn (c Complex) real_mul(r f64) Complex { +} + +// divide complex number 'c' by real number 'r' +// c / r +pub fn (c Complex) div_by_real(r f64) Complex { +} + +// divide a real number 'r' by a complex number 'c' +// r / c +pub fn (c Complex) real_div(r f64) Complex { +} diff --git a/exercises/practice/complex-numbers/run_test.v b/exercises/practice/complex-numbers/run_test.v new file mode 100644 index 0000000..9ad1c48 --- /dev/null +++ b/exercises/practice/complex-numbers/run_test.v @@ -0,0 +1,288 @@ +module main + +import math { close } + +// Complex number to a string representation of the form 'a + bi' +// Example '7.0 - 3.1i' +fn to_string(c Complex) string { + imag := c.imaginary() + + sign := match math.signi(imag) { + 1 { '+' } + else { '-' } + } + + return "'${c.real()} ${sign} ${math.abs(imag)}i'" +} + +fn assert_close_enough_complex(actual Complex, expected Complex) { + real_ok := close(actual.real(), expected.real()) + imag_ok := close(actual.imaginary(), expected.imaginary()) + + assert real_ok && imag_ok, 'actual = ${to_string(actual)} expected = ${to_string(expected)}' +} + +fn assert_close_enough_float(actual f64, expected f64) { + assert close(actual, expected), 'actual = ${actual}, expected = ${expected}' +} + +// tests + +fn test_real_part_of_a_purely_real_number() { + z := Complex.new(1.0, 0.0) + + assert z.real() == 1.0 +} + +fn test_real_part_of_a_purely_imaginary_number() { + z := Complex.new(0.0, 1.0) + + assert z.real() == 0.0 +} + +fn test_real_part_of_a_number_with_real_and_imaginary_part() { + z := Complex.new(1.0, 2.0) + + assert z.real() == 1.0 +} + +fn test_imaginary_part_of_a_purely_real_number() { + z := Complex.new(1.0, 0.0) + + assert z.imaginary() == 0.0 +} + +fn test_imaginary_part_of_a_purely_imaginary_number() { + z := Complex.new(0.0, 1.0) + + assert z.imaginary() == 1.0 +} + +fn test_imaginary_part_of_a_number_with_real_and_imaginary_part() { + z := Complex.new(1.0, 2.0) + + assert z.imaginary() == 2.0 +} + +fn test_multiply_imaginary_parts_only() { + z1 := Complex.new(0.0, 1.0) + z2 := Complex.new(0.0, 1.0) + expected := Complex.new(-1.0, 0.0) + + assert_close_enough_complex(z1.mul(z2), expected) +} + +fn test_add_purely_real_numbers() { + z1 := Complex.new(1.0, 0.0) + z2 := Complex.new(2.0, 0.0) + + assert z1.add(z2) == Complex.new(3.0, 0.0) +} + +fn test_add_purely_imaginary_numbers() { + z1 := Complex.new(0.0, 1.0) + z2 := Complex.new(0.0, 2.0) + + assert z1.add(z2) == Complex.new(0.0, 3.0) +} + +fn test_add_numbers_with_real_and_imaginary_part() { + z1 := Complex.new(1.0, 2.0) + z2 := Complex.new(3.0, 4.0) + + assert z1.add(z2) == Complex.new(4.0, 6.0) +} + +fn test_subtract_purely_real_numbers() { + z1 := Complex.new(1.0, 0.0) + z2 := Complex.new(2.0, 0.0) + + assert z1.sub(z2) == Complex.new(-1.0, 0.0) +} + +fn test_subtract_purely_imaginary_numbers() { + z1 := Complex.new(0.0, 1.0) + z2 := Complex.new(0.0, 2.0) + + assert z1.sub(z2) == Complex.new(0.0, -1.0) +} + +fn test_subtract_numbers_with_real_and_imaginary_part() { + z1 := Complex.new(1.0, 2.0) + z2 := Complex.new(3.0, 4.0) + + assert z1.sub(z2) == Complex.new(-2.0, -2.0) +} + +fn test_multiply_purely_real_numbers() { + z1 := Complex.new(1.0, 0.0) + z2 := Complex.new(2.0, 0.0) + + assert z1.mul(z2) == Complex.new(2.0, 0.0) +} + +fn test_multiply_purely_imaginary_numbers() { + z1 := Complex.new(0.0, 1.0) + z2 := Complex.new(0.0, 2.0) + + assert z1.mul(z2) == Complex.new(-2.0, 0.0) +} + +fn test_multiply_numbers_with_real_and_imaginary_part() { + z1 := Complex.new(1.0, 2.0) + z2 := Complex.new(3.0, 4.0) + + assert z1.mul(z2) == Complex.new(-5.0, 10) +} + +fn test_divide_purely_real_numbers() { + z1 := Complex.new(1.0, 0.0) + z2 := Complex.new(2.0, 0.0) + + assert z1.div(z2) == Complex.new(0.5, 0.0) +} + +fn test_divide_purely_imaginary_numbers() { + z1 := Complex.new(0.0, 1.0) + z2 := Complex.new(0.0, 2.0) + + assert z1.div(z2) == Complex.new(0.5, 0.0) +} + +fn test_divide_numbers_with_real_and_imaginary_part() { + z1 := Complex.new(1.0, 2.0) + z2 := Complex.new(3.0, 4.0) + expected := Complex.new(0.44, 0.08) + + assert_close_enough_complex(z1.div(z2), expected) +} + +fn test_absolute_value_of_a_positive_purely_real_number() { + z := Complex.new(5.0, 0.0) + + assert_close_enough_float(z.abs(), 5.0) +} + +fn test_absolute_value_of_a_negative_purely_real_number() { + z := Complex.new(-5.0, 0.0) + + assert_close_enough_float(z.abs(), 5.0) +} + +fn test_absolute_value_of_a_purely_imaginary_number_with_positive_imaginary_part() { + z := Complex.new(0.0, 5.0) + + assert_close_enough_float(z.abs(), 5.0) +} + +fn test_absolute_value_of_a_purely_imaginary_number_with_negative_imaginary_part() { + z := Complex.new(0.0, -5.0) + + assert_close_enough_float(z.abs(), 5.0) +} + +fn test_absolute_value_of_a_number_with_real_and_imaginary_part() { + z := Complex.new(3.0, 4.0) + + assert_close_enough_float(z.abs(), 5.0) +} + +fn test_conjugate_a_purely_real_number() { + z := Complex.new(5.0, 0.0) + + assert z.conjugate() == Complex.new(5.0, 0.0) +} + +fn test_conjugate_a_purely_imaginary_number() { + z := Complex.new(0.0, 5.0) + + assert z.conjugate() == Complex.new(0.0, -5.0) +} + +fn test_conjugate_a_number_with_real_and_imaginary_part() { + z := Complex.new(1.0, 1.0) + + assert z.conjugate() == Complex.new(1.0, -1.0) +} + +fn test_eulers_identity_formula() { + z := Complex.new(0.0, math.pi) + expected := Complex.new(-1.0, 0.0) + + assert_close_enough_complex(z.exp(), expected) +} + +fn test_exponential_of_0() { + z := Complex.new(0.0, 0.0) + + assert z.exp() == Complex.new(1.0, 0.0) +} + +fn test_exponential_of_a_purely_real_number() { + z := Complex.new(1.0, 0.0) + expected := Complex.new(math.e, 0.0) + + assert_close_enough_complex(z.exp(), expected) +} + +fn test_exponential_of_a_number_with_real_and_imaginary_part() { + z := Complex.new(math.log(2.0), math.pi) + expected := Complex.new(-2.0, 0.0) + + assert_close_enough_complex(z.exp(), expected) +} + +fn test_exponential_resulting_in_a_number_with_real_and_imaginary_part() { + z := Complex.new(math.log(2.0) / 2.0, math.pi / 4.0) + expected := Complex.new(1.0, 1.0) + + assert_close_enough_complex(z.exp(), expected) +} + +fn test_add_real_number_to_complex_number() { + z := Complex.new(1.0, 2.0) + + assert z.add_real(5.0) == Complex.new(6.0, 2.0) +} + +fn test_add_complex_number_to_real_number() { + z := Complex.new(1.0, 2.0) + + assert z.real_add(5.0) == Complex.new(6.0, 2.0) +} + +fn test_subtract_real_number_from_complex_number() { + z := Complex.new(5.0, 7.0) + + assert z.sub_real(4.0) == Complex.new(1.0, 7.0) +} + +fn test_subtract_complex_number_from_real_number() { + z := Complex.new(5.0, 7.0) + + assert z.real_sub(4.0) == Complex.new(-1.0, -7.0) +} + +fn test_multiply_complex_number_by_real_number() { + z := Complex.new(2.0, 5.0) + + assert z.mul_by_real(5.0) == Complex.new(10.0, 25.0) +} + +fn test_multiply_real_number_by_complex_number() { + z := Complex.new(2.0, 5.0) + + assert z.real_mul(5.0) == Complex.new(10.0, 25.0) +} + +fn test_divide_complex_number_by_real_number() { + z := Complex.new(10.0, 100.0) + + assert z.div_by_real(10.0) == Complex.new(1.0, 10) +} + +fn test_divide_real_number_by_complex_number() { + z := Complex.new(1.0, 1.0) + + assert z.real_div(5.0) == Complex.new(2.5, -2.5) +}