From a5512c42c3dd2522e19d166730cb42fc561afcbc Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 22 Nov 2022 14:43:48 +0100 Subject: solve complex --- go/complex-numbers/.exercism/config.json | 19 + go/complex-numbers/.exercism/metadata.json | 1 + go/complex-numbers/HELP.md | 40 ++ go/complex-numbers/README.md | 44 +++ go/complex-numbers/cases_test.go | 572 +++++++++++++++++++++++++++++ go/complex-numbers/complex_numbers.go | 72 ++++ go/complex-numbers/complex_numbers_test.go | 134 +++++++ go/complex-numbers/go.mod | 3 + 8 files changed, 885 insertions(+) create mode 100644 go/complex-numbers/.exercism/config.json create mode 100644 go/complex-numbers/.exercism/metadata.json create mode 100644 go/complex-numbers/HELP.md create mode 100644 go/complex-numbers/README.md create mode 100644 go/complex-numbers/cases_test.go create mode 100644 go/complex-numbers/complex_numbers.go create mode 100644 go/complex-numbers/complex_numbers_test.go create mode 100644 go/complex-numbers/go.mod diff --git a/go/complex-numbers/.exercism/config.json b/go/complex-numbers/.exercism/config.json new file mode 100644 index 0000000..e9c806f --- /dev/null +++ b/go/complex-numbers/.exercism/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "eklatzer" + ], + "files": { + "solution": [ + "complex_numbers.go" + ], + "test": [ + "complex_numbers_test.go" + ], + "example": [ + ".meta/example.go" + ] + }, + "blurb": "Implement complex numbers.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Complex_number" +} diff --git a/go/complex-numbers/.exercism/metadata.json b/go/complex-numbers/.exercism/metadata.json new file mode 100644 index 0000000..ab9e31e --- /dev/null +++ b/go/complex-numbers/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"go","exercise":"complex-numbers","id":"0b6d7784e1c847d29450f740f51abaa3","url":"https://exercism.org/tracks/go/exercises/complex-numbers","handle":"dim13","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/go/complex-numbers/HELP.md b/go/complex-numbers/HELP.md new file mode 100644 index 0000000..cc1f090 --- /dev/null +++ b/go/complex-numbers/HELP.md @@ -0,0 +1,40 @@ +# Help + +## Running the tests + +To run the tests run the command `go test` from within the exercise directory. + +If the test suite contains benchmarks, you can run these with the `--bench` and `--benchmem` +flags: + + go test -v --bench . --benchmem + +Keep in mind that each reviewer will run benchmarks on a different machine, with +different specs, so the results from these benchmark tests may vary. + +## Submitting your solution + +You can submit your solution using the `exercism submit complex_numbers.go` 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 [Go track's documentation](https://exercism.org/docs/tracks/go) +- [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. + +To get help if you're having trouble, you can use one of the following resources: + +- [How to Write Go Code](https://golang.org/doc/code.html) +- [Effective Go](https://golang.org/doc/effective_go.html) +- [Go Resources](http://golang.org/help) +- [StackOverflow](http://stackoverflow.com/questions/tagged/go) \ No newline at end of file diff --git a/go/complex-numbers/README.md b/go/complex-numbers/README.md new file mode 100644 index 0000000..ea1288c --- /dev/null +++ b/go/complex-numbers/README.md @@ -0,0 +1,44 @@ +# Complex Numbers + +Welcome to Complex Numbers on Exercism's Go 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. + +## Source + +### Created by + +- @eklatzer + +### Based on + +Wikipedia - https://en.wikipedia.org/wiki/Complex_number \ No newline at end of file diff --git a/go/complex-numbers/cases_test.go b/go/complex-numbers/cases_test.go new file mode 100644 index 0000000..b95890a --- /dev/null +++ b/go/complex-numbers/cases_test.go @@ -0,0 +1,572 @@ +package complexnumbers + +// Source: exercism/problem-specifications +// Commit: 24a7bfa Add exponential resulting in a number with real and imaginary part (#2052) + +type complexNumber struct { + a float64 + b float64 +} + +var realTestCases = []struct { + description string + in complexNumber + want float64 +}{ + { + description: "Real part of a purely real number", + in: complexNumber{ + a: 1.000000, + b: 0.000000, + }, + want: 1.000000, + }, + { + description: "Real part of a purely imaginary number", + in: complexNumber{ + a: 0.000000, + b: 1.000000, + }, + want: 0.000000, + }, + { + description: "Real part of a number with real and imaginary part", + in: complexNumber{ + a: 1.000000, + b: 2.000000, + }, + want: 1.000000, + }, +} + +var imaginaryTestCases = []struct { + description string + in complexNumber + want float64 +}{ + { + description: "Imaginary part of a purely real number", + in: complexNumber{ + a: 1.000000, + b: 0.000000, + }, + want: 0.000000, + }, + { + description: "Imaginary part of a purely imaginary number", + in: complexNumber{ + a: 0.000000, + b: 1.000000, + }, + want: 1.000000, + }, + { + description: "Imaginary part of a number with real and imaginary part", + in: complexNumber{ + a: 1.000000, + b: 2.000000, + }, + want: 2.000000, + }, +} + +var addTestCases = []struct { + description string + n1 complexNumber + n2 complexNumber + want complexNumber +}{ + { + description: "Add purely real numbers", + n1: complexNumber{ + a: 1.000000, + b: 0.000000, + }, + n2: complexNumber{ + a: 2.000000, + b: 0.000000, + }, + want: complexNumber{ + a: 3.000000, + b: 0.000000, + }, + }, + { + description: "Add purely imaginary numbers", + n1: complexNumber{ + a: 0.000000, + b: 1.000000, + }, + n2: complexNumber{ + a: 0.000000, + b: 2.000000, + }, + want: complexNumber{ + a: 0.000000, + b: 3.000000, + }, + }, + { + description: "Add numbers with real and imaginary part", + n1: complexNumber{ + a: 1.000000, + b: 2.000000, + }, + n2: complexNumber{ + a: 3.000000, + b: 4.000000, + }, + want: complexNumber{ + a: 4.000000, + b: 6.000000, + }, + }, + { + description: "Add real number to complex number", + n1: complexNumber{ + a: 1.000000, + b: 2.000000, + }, + n2: complexNumber{ + a: 5.000000, + b: 0.000000, + }, + want: complexNumber{ + a: 6.000000, + b: 2.000000, + }, + }, + { + description: "Add complex number to real number", + n1: complexNumber{ + a: 5.000000, + b: 0.000000, + }, + n2: complexNumber{ + a: 1.000000, + b: 2.000000, + }, + want: complexNumber{ + a: 6.000000, + b: 2.000000, + }, + }, +} + +var subtractTestCases = []struct { + description string + n1 complexNumber + n2 complexNumber + want complexNumber +}{ + { + description: "Subtract purely real numbers", + n1: complexNumber{ + a: 1.000000, + b: 0.000000, + }, + n2: complexNumber{ + a: 2.000000, + b: 0.000000, + }, + want: complexNumber{ + a: -1.000000, + b: 0.000000, + }, + }, + { + description: "Subtract purely imaginary numbers", + n1: complexNumber{ + a: 0.000000, + b: 1.000000, + }, + n2: complexNumber{ + a: 0.000000, + b: 2.000000, + }, + want: complexNumber{ + a: 0.000000, + b: -1.000000, + }, + }, + { + description: "Subtract numbers with real and imaginary part", + n1: complexNumber{ + a: 1.000000, + b: 2.000000, + }, + n2: complexNumber{ + a: 3.000000, + b: 4.000000, + }, + want: complexNumber{ + a: -2.000000, + b: -2.000000, + }, + }, + { + description: "Subtract real number from complex number", + n1: complexNumber{ + a: 5.000000, + b: 7.000000, + }, + n2: complexNumber{ + a: 4.000000, + b: 0.000000, + }, + want: complexNumber{ + a: 1.000000, + b: 7.000000, + }, + }, + { + description: "Subtract complex number from real number", + n1: complexNumber{ + a: 4.000000, + b: 0.000000, + }, + n2: complexNumber{ + a: 5.000000, + b: 7.000000, + }, + want: complexNumber{ + a: -1.000000, + b: -7.000000, + }, + }, +} + +var divideTestCases = []struct { + description string + n1 complexNumber + n2 complexNumber + want complexNumber +}{ + { + description: "Divide purely real numbers", + n1: complexNumber{ + a: 1.000000, + b: 0.000000, + }, + n2: complexNumber{ + a: 2.000000, + b: 0.000000, + }, + want: complexNumber{ + a: 0.500000, + b: 0.000000, + }, + }, + { + description: "Divide purely imaginary numbers", + n1: complexNumber{ + a: 0.000000, + b: 1.000000, + }, + n2: complexNumber{ + a: 0.000000, + b: 2.000000, + }, + want: complexNumber{ + a: 0.500000, + b: 0.000000, + }, + }, + { + description: "Divide numbers with real and imaginary part", + n1: complexNumber{ + a: 1.000000, + b: 2.000000, + }, + n2: complexNumber{ + a: 3.000000, + b: 4.000000, + }, + want: complexNumber{ + a: 0.440000, + b: 0.080000, + }, + }, + { + description: "Divide complex number by real number", + n1: complexNumber{ + a: 10.000000, + b: 100.000000, + }, + n2: complexNumber{ + a: 10.000000, + b: 0.000000, + }, + want: complexNumber{ + a: 1.000000, + b: 10.000000, + }, + }, + { + description: "Divide real number by complex number", + n1: complexNumber{ + a: 5.000000, + b: 0.000000, + }, + n2: complexNumber{ + a: 1.000000, + b: 1.000000, + }, + want: complexNumber{ + a: 2.500000, + b: -2.500000, + }, + }, +} + +var multiplyTestCases = []struct { + description string + n1 complexNumber + n2 *complexNumber // if n2 is nil it is a multiplication with the factor + factor float64 + want complexNumber +}{ + { + description: "Imaginary unit", + n1: complexNumber{ + a: 0.000000, + b: 1.000000, + }, + n2: &complexNumber{ + a: 0.000000, + b: 1.000000, + }, + factor: 0.000000, + want: complexNumber{ + a: -1.000000, + b: 0.000000, + }, + }, + { + description: "Multiply purely real numbers", + n1: complexNumber{ + a: 1.000000, + b: 0.000000, + }, + n2: &complexNumber{ + a: 2.000000, + b: 0.000000, + }, + factor: 0.000000, + want: complexNumber{ + a: 2.000000, + b: 0.000000, + }, + }, + { + description: "Multiply purely imaginary numbers", + n1: complexNumber{ + a: 0.000000, + b: 1.000000, + }, + n2: &complexNumber{ + a: 0.000000, + b: 2.000000, + }, + factor: 0.000000, + want: complexNumber{ + a: -2.000000, + b: 0.000000, + }, + }, + { + description: "Multiply numbers with real and imaginary part", + n1: complexNumber{ + a: 1.000000, + b: 2.000000, + }, + n2: &complexNumber{ + a: 3.000000, + b: 4.000000, + }, + factor: 0.000000, + want: complexNumber{ + a: -5.000000, + b: 10.000000, + }, + }, + { + description: "Multiply complex number by real number", + n1: complexNumber{ + a: 2.000000, + b: 5.000000, + }, + + n2: nil, + + factor: 5.000000, + want: complexNumber{ + a: 10.000000, + b: 25.000000, + }, + }, + { + description: "Multiply real number by complex number", + n1: complexNumber{ + a: 2.000000, + b: 5.000000, + }, + + n2: nil, + + factor: 5.000000, + want: complexNumber{ + a: 10.000000, + b: 25.000000, + }, + }, +} + +var conjugateTestCases = []struct { + description string + in complexNumber + want complexNumber +}{ + { + description: "Conjugate a purely real number", + in: complexNumber{ + a: 5.000000, + b: 0.000000, + }, + want: complexNumber{ + a: 5.000000, + b: 0.000000, + }, + }, + { + description: "Conjugate a purely imaginary number", + in: complexNumber{ + a: 0.000000, + b: 5.000000, + }, + want: complexNumber{ + a: 0.000000, + b: -5.000000, + }, + }, + { + description: "Conjugate a number with real and imaginary part", + in: complexNumber{ + a: 1.000000, + b: 1.000000, + }, + want: complexNumber{ + a: 1.000000, + b: -1.000000, + }, + }, +} + +var absTestCases = []struct { + description string + in complexNumber + want float64 +}{ + { + description: "Absolute value of a positive purely real number", + in: complexNumber{ + a: 5.000000, + b: 0.000000, + }, + want: 5.000000, + }, + { + description: "Absolute value of a negative purely real number", + in: complexNumber{ + a: -5.000000, + b: 0.000000, + }, + want: 5.000000, + }, + { + description: "Absolute value of a purely imaginary number with positive imaginary part", + in: complexNumber{ + a: 0.000000, + b: 5.000000, + }, + want: 5.000000, + }, + { + description: "Absolute value of a purely imaginary number with negative imaginary part", + in: complexNumber{ + a: 0.000000, + b: -5.000000, + }, + want: 5.000000, + }, + { + description: "Absolute value of a number with real and imaginary part", + in: complexNumber{ + a: 3.000000, + b: 4.000000, + }, + want: 5.000000, + }, +} + +var expTestCases = []struct { + description string + in complexNumber + want complexNumber +}{ + { + description: "Euler's identity/formula", + in: complexNumber{ + a: 0.000000, + b: 3.141593, + }, + want: complexNumber{ + a: -1.000000, + b: 0.000000, + }, + }, + { + description: "Exponential of 0", + in: complexNumber{ + a: 0.000000, + b: 0.000000, + }, + want: complexNumber{ + a: 1.000000, + b: 0.000000, + }, + }, + { + description: "Exponential of a purely real number", + in: complexNumber{ + a: 1.000000, + b: 0.000000, + }, + want: complexNumber{ + a: 2.718282, + b: 0.000000, + }, + }, + { + description: "Exponential of a number with real and imaginary part", + in: complexNumber{ + a: 0.693147, + b: 3.141593, + }, + want: complexNumber{ + a: -2.000000, + b: 0.000000, + }, + }, + { + description: "Exponential resulting in a number with real and imaginary part", + in: complexNumber{ + a: 0.346574, + b: 0.785398, + }, + want: complexNumber{ + a: 1.000000, + b: 1.000000, + }, + }, +} diff --git a/go/complex-numbers/complex_numbers.go b/go/complex-numbers/complex_numbers.go new file mode 100644 index 0000000..d06bd3d --- /dev/null +++ b/go/complex-numbers/complex_numbers.go @@ -0,0 +1,72 @@ +package complexnumbers + +import "math" + +// Define the Number type here. +type Number struct { + Re, Im float64 +} + +func (n Number) Real() float64 { + return n.Re +} + +func (n Number) Imaginary() float64 { + return n.Im +} + +func (n1 Number) Add(n2 Number) Number { + return Number{ + Re: n1.Re + n2.Re, + Im: n1.Im + n2.Im, + } +} + +func (n1 Number) Subtract(n2 Number) Number { + return Number{ + Re: n1.Re - n2.Re, + Im: n1.Im - n2.Im, + } +} + +func (n1 Number) Multiply(n2 Number) Number { + return Number{ + Re: n1.Re*n2.Re - n1.Im*n2.Im, + Im: n1.Im*n2.Re + n1.Re*n2.Im, + } +} + +func (n Number) Times(factor float64) Number { + return Number{ + Re: n.Re * factor, + Im: n.Im * factor, + } +} + +func (n1 Number) Divide(n2 Number) Number { + x := n2.Re*n2.Re + n2.Im*n2.Im + return Number{ + Re: (n1.Re*n2.Re + n1.Im*n2.Im) / x, + Im: (n1.Im*n2.Re - n1.Re*n2.Im) / x, + } +} + +func (n Number) Conjugate() Number { + return Number{ + Re: n.Re, + Im: -n.Im, + } +} + +func (n Number) Abs() float64 { + return math.Sqrt(n.Re*n.Re + n.Im*n.Im) +} + +func (n Number) Exp() Number { + r := math.Exp(n.Re) + s, c := math.Sincos(n.Im) + return Number{ + Re: r * c, + Im: r * s, + } +} diff --git a/go/complex-numbers/complex_numbers_test.go b/go/complex-numbers/complex_numbers_test.go new file mode 100644 index 0000000..097f75a --- /dev/null +++ b/go/complex-numbers/complex_numbers_test.go @@ -0,0 +1,134 @@ +package complexnumbers + +import ( + "math" + "testing" +) + +const floatEqualityThreshold = 1e-5 + +func floatingPointEquals(got, want float64) bool { + absoluteDifferenceBelowThreshold := math.Abs(got-want) <= floatEqualityThreshold + relativeDifferenceBelowThreshold := math.Abs(got-want)/(math.Abs(got)+math.Abs(want)) <= floatEqualityThreshold + return absoluteDifferenceBelowThreshold || relativeDifferenceBelowThreshold +} + +func TestNumber_Real(t *testing.T) { + for _, tt := range realTestCases { + t.Run(tt.description, func(t *testing.T) { + n := Number{tt.in.a, tt.in.b} + if got := n.Real(); !floatingPointEquals(got, tt.want) { + t.Errorf("Number%+v.Real() = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestNumber_Imaginary(t *testing.T) { + for _, tt := range imaginaryTestCases { + t.Run(tt.description, func(t *testing.T) { + n := Number{tt.in.a, tt.in.b} + if got := n.Imaginary(); !floatingPointEquals(got, tt.want) { + t.Errorf("Number%+v.Imaginary() = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestNumber_Add(t *testing.T) { + for _, tt := range addTestCases { + t.Run(tt.description, func(t *testing.T) { + n1 := Number{tt.n1.a, tt.n1.b} + n2 := Number{tt.n2.a, tt.n2.b} + if got := n1.Add(n2); !floatingPointEquals(got.Real(), tt.want.a) || !floatingPointEquals(got.Imaginary(), tt.want.b) { + t.Errorf("Number%+v.Add%+v\n got: %+v\nwant: %+v", tt.n1, tt.n2, got, tt.want) + } + }) + } +} + +func TestNumber_Subtract(t *testing.T) { + for _, tt := range subtractTestCases { + t.Run(tt.description, func(t *testing.T) { + n1 := Number{tt.n1.a, tt.n1.b} + n2 := Number{tt.n2.a, tt.n2.b} + if got := n1.Subtract(n2); !floatingPointEquals(got.Real(), tt.want.a) || !floatingPointEquals(got.Imaginary(), tt.want.b) { + t.Errorf("Number%+v.Subtract%+v\n got: %+v\nwant: %+v", tt.n1, tt.n2, got, tt.want) + } + }) + } +} + +func TestNumber_Multiply(t *testing.T) { + for _, tt := range multiplyTestCases { + t.Run(tt.description, func(t *testing.T) { + if tt.n2 == nil { + t.Skip("skipping tests with factor used withNumber.Times()") + } + n1 := Number{tt.n1.a, tt.n1.b} + n2 := Number{tt.n2.a, tt.n2.b} + if got := n1.Multiply(n2); !floatingPointEquals(got.Real(), tt.want.a) || !floatingPointEquals(got.Imaginary(), tt.want.b) { + t.Errorf("Number%+v.Multiply%+v\n got: %+v\nwant: %+v", tt.n1, tt.n2, got, tt.want) + } + }) + } +} + +func TestNumber_Times(t *testing.T) { + for _, tt := range multiplyTestCases { + t.Run(tt.description, func(t *testing.T) { + if tt.n2 != nil { + t.Skip("skipping tests with complex multiplier used withNumber.Multiply()") + } + n := Number{tt.n1.a, tt.n1.b} + if got := n.Times(tt.factor); !floatingPointEquals(got.Real(), tt.want.a) || !floatingPointEquals(got.Imaginary(), tt.want.b) { + t.Errorf("Number%+v.Times(%v)\n got: %+v\nwant: %+v", tt.n1, tt.factor, got, tt.want) + } + }) + } +} + +func TestNumber_Divide(t *testing.T) { + for _, tt := range divideTestCases { + t.Run(tt.description, func(t *testing.T) { + n1 := Number{tt.n1.a, tt.n1.b} + n2 := Number{tt.n2.a, tt.n2.b} + if got := n1.Divide(n2); !floatingPointEquals(got.Real(), tt.want.a) || !floatingPointEquals(got.Imaginary(), tt.want.b) { + t.Errorf("Number%+v.Divide%+v\n got: %+v\nwant: %+v", tt.n1, tt.n2, got, tt.want) + } + }) + } +} + +func TestNumber_Abs(t *testing.T) { + for _, tt := range absTestCases { + t.Run(tt.description, func(t *testing.T) { + n := Number{tt.in.a, tt.in.b} + if got := n.Abs(); !floatingPointEquals(got, tt.want) { + t.Errorf("Number.Abs%+v = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestNumber_Conjugate(t *testing.T) { + for _, tt := range conjugateTestCases { + t.Run(tt.description, func(t *testing.T) { + n := Number{tt.in.a, tt.in.b} + if got := n.Conjugate(); !floatingPointEquals(got.Real(), tt.want.a) || !floatingPointEquals(got.Imaginary(), tt.want.b) { + t.Errorf("Number%+v.Conjugate()\n got: %+v\nwant: %+v", tt.in, got, tt.want) + } + }) + } +} + +func TestNumber_Exp(t *testing.T) { + for _, tt := range expTestCases { + t.Run(tt.description, func(t *testing.T) { + n := Number{tt.in.a, tt.in.b} + if got := n.Exp(); !floatingPointEquals(got.Real(), tt.want.a) || !floatingPointEquals(got.Imaginary(), tt.want.b) { + t.Errorf("Number%+v.Exp()\n got: %+v\nwant: %+v", tt.in, got, tt.want) + } + }) + } +} diff --git a/go/complex-numbers/go.mod b/go/complex-numbers/go.mod new file mode 100644 index 0000000..4a89806 --- /dev/null +++ b/go/complex-numbers/go.mod @@ -0,0 +1,3 @@ +module complex + +go 1.16 -- cgit v1.2.3