diff --git a/config.json b/config.json index 0e4818b75..587d2182a 100644 --- a/config.json +++ b/config.json @@ -189,6 +189,18 @@ ], "status": "wip" }, + { + "slug": "strange-stopwatch", + "name": "Strange Stopwatch", + "uuid": "6428590f-2bf3-4ca3-b4e9-dea11aceea83", + "concepts": [ + "complex-numbers" + ], + "prerequisites": [ + "numbers", "strings", "function-composition" + ], + "status": "wip" + }, { "slug": "old-annalyns-infiltration", "name": "Old Annalyn's Infiltration", diff --git a/exercises/concept/strange-stopwatch/.docs/hints.md b/exercises/concept/strange-stopwatch/.docs/hints.md new file mode 100644 index 000000000..058e0e7ec --- /dev/null +++ b/exercises/concept/strange-stopwatch/.docs/hints.md @@ -0,0 +1,36 @@ +# Hints + +Using complex numbers for rotations in 2D can leave things much cleaner and numerically more precise. + +## 1. Define a 2D vector rotation function + +- [Euler's formula][euler] is your friend here. +- The inbuilt [`complex(x, y)`][complex] method is more efficient than assigning `x + im*y`. +- There are methods for retrieving the [real][real] part and [imaginary][imaginary] part of a complex number, or both! + +## 2. Define a function to find the angle of the stopwatch's hand + +- The inbulit [`angle`][angle] method can be used to quickly find an argument from a complex number. +- You may want to use your `rotation` function with `angle` because you will need a rotation to redefine the zero point, since `angle` uses the convention of having zero on the negative x-axis. +- A translation would also be needed when using `angle` since the convention used has angles between -π and π. + +## 3. Define a function to tell the time on the stopwatch + +- This is a good opportunity to use your `timearg` function. +- There is an inbuilt method [rad2deg][rad2deg] to convert from radians to degrees. +- Minutes are the [`abs`][abs] value of the vector. + +## 4. Define a function to set a timer + +- Consider using the [polar form][euler] of a complex number (e.g. `m*ℯ^(iθ)`). +- There is an inbuilt method [deg2rad][deg2rad] to convert from degrees to radians. +- A rotation may be needed to redefine the zero point. + +[euler]: https://docs.julialang.org/en/v1/base/math/#Base.cis +[complex]: https://docs.julialang.org/en/v1/base/numbers/#Base.complex-Tuple{Complex} +[real]: https://docs.julialang.org/en/v1/base/math/#Base.real +[imaginary]: https://docs.julialang.org/en/v1/base/math/#Base.imag +[angle]: https://docs.julialang.org/en/v1/base/math/#Base.angle +[abs]: https://docs.julialang.org/en/v1/base/math/#Base.abs +[rad2deg]: https://docs.julialang.org/en/v1/base/math/#Base.Math.rad2deg +[deg2rad]: https://docs.julialang.org/en/v1/base/math/#Base.Math.deg2rad diff --git a/exercises/concept/strange-stopwatch/.docs/instructions.md b/exercises/concept/strange-stopwatch/.docs/instructions.md new file mode 100644 index 000000000..f4cba32b4 --- /dev/null +++ b/exercises/concept/strange-stopwatch/.docs/instructions.md @@ -0,0 +1,69 @@ +# Instructions + +You've been given a strange stopwatch which has no markings on it but takes 60 seconds to go around once. +What's stranger is that the hand grows by one unit with each passing minute. + +To read the time on stopwatch you can place it on some graph paper and read off the x-y coordinates. +Then we will have to convert these coordinates into minutes and seconds. + +On the other hand, we may want to use it as a timer, so we need to be able to put a mark on the paper as a reference time. +Here we will have to convert the minutes and seconds into the x-y coordinates. + +These operations can be done through trigonometric functions and/or rotation matrices, but they can be made simpler (and more fun, I assure you) with the use of complex numbers. +This ease, which can make rotations quite straightforward, results from Euler's elegant formula, `ℯ^(iθ) = cos(θ) + isin(θ) = x + iy`, where `i = √-1` is the imaginary unit. +For example, the complex number `z = x + iy`, can be rotated about the origin with a simple multiplication `z * ℯ^(-iθ)`. +Here the `x` and `y` are just the coordnates on a real 2D plane. +**Caveat:** A *clockwise* rotation takes a negative exponent, `ℯ^(-iθ) = cos(θ) - isin(θ)`, and a *counterclockwise* rotation takes a positive one, `ℯ^(iθ) = cos(θ) + isin(θ)`. + +## 1. Define a 2D vector rotation function + +Implement the `rotate` function which takes an x coordinate, a y coordinate and an angle θ (in radians). +The function should rotate the point about the origin by the given angle θ and return the new coordinates as a tuple. +While our stopwatch hand will only have integer lengths, this function should rotate a vector of any length. + +```julia-repl +julia> rotate(0, 1, π) +(0, -1) + +julia> rotate(1, 1, π) +(-1, -1) +``` + +## 2. Define a function to find the angle of the stopwatch's hand + +Implement the function `timearg` which takes the x-y coordinates the hand and returns the angle in radians. +Since we are dealing with a clock, the zero point will be on the y-axis (i.e. imaginary axis). + + +```julia-repl +julia> timearg(0, 1) +0 + +julia> timearg(1, 0) +1.5707963267948966 # equal to π/2 +``` + +## 3. Define a function to tell the time on the stopwatch + +Implement a function `readtime` which takes the x-y coordinates of the hand and returns the time as `"Mm Ss"`. + + +```julia-repl +julia> readtime(1, 0) +"1m 15.0s" + +julia> readtime(√2, √2) +"2m 7.5s" +``` + +## 4. Define a function to set a timer + +Implement a function `getcoords` which takes a time, in minutes and seconds, and outputs the x-y coordinates of the tip of the stopwatch's hand at that time. + +```julia-repl +julia> getcoords(1, 0) +(0, 1) + +julia> getcoords(2, 15) +(2, 0) +``` \ No newline at end of file diff --git a/exercises/concept/strange-stopwatch/.docs/introduction.md b/exercises/concept/strange-stopwatch/.docs/introduction.md new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/concept/strange-stopwatch/.meta/config.json b/exercises/concept/strange-stopwatch/.meta/config.json new file mode 100644 index 000000000..dc0b20124 --- /dev/null +++ b/exercises/concept/strange-stopwatch/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "depial" + ], + "files": { + "solution": [ + "strange-stopwatch.jl" + ], + "test": [ + "runtests.jl" + ], + "exemplar": [ + ".meta/exemplar.jl" + ] + }, + "blurb": "Learn about simple rotations of 2D vectors using complex numbers" +} diff --git a/exercises/concept/strange-stopwatch/.meta/design.md b/exercises/concept/strange-stopwatch/.meta/design.md new file mode 100644 index 000000000..85a56fade --- /dev/null +++ b/exercises/concept/strange-stopwatch/.meta/design.md @@ -0,0 +1,26 @@ +# Design + +## Goal + +The goal of this exercise is to introduce the student to the predefined type of complex numbers. + +## Learning objectives + +- Understand the predefined type of complex numbers and their construction. +- Become familiar with a basic subset of functions, such as `complex`, `abs`, `angle`, `reim`, etc. + +## Out of scope + +- Modular Arithmetic + +## Concepts + +The Concepts this exercise unlocks are: + +- `complex-numbers` + +## Prerequisites + +- `numbers` +- `strings` +- `function-composition` diff --git a/exercises/concept/strange-stopwatch/.meta/exemplar.jl b/exercises/concept/strange-stopwatch/.meta/exemplar.jl new file mode 100644 index 000000000..d69e43ec9 --- /dev/null +++ b/exercises/concept/strange-stopwatch/.meta/exemplar.jl @@ -0,0 +1,4 @@ +rotate(x, y, θ) = reim(complex(x, y)cispi(θ/π)) +timearg(x, y) = π - angle(complex(rotate(x, y, π/2)...)) +readtime(x, y) = "$(floor(Int, abs(complex(x, y))))m $(rad2deg(timearg(x, y))/6)s" +getcoords(m, s) = rotate(reim(m * cispi(-deg2rad(6s)/π))..., π/2) diff --git a/exercises/concept/strange-stopwatch/runtests.jl b/exercises/concept/strange-stopwatch/runtests.jl new file mode 100644 index 000000000..232cca9a3 --- /dev/null +++ b/exercises/concept/strange-stopwatch/runtests.jl @@ -0,0 +1,61 @@ +using Test + +include("strange-stopwatch.jl") + +@testset verbose = true "tests" begin + @testset "rotations" begin + x, y = rotate(1, 0, π/2) + @test isapprox(x, 0; atol=1e-2) && isapprox(y, 1; atol=1e-2) + + x, y = rotate(1, 0, -π/2) + @test isapprox(x, 0; atol=1e-2) && isapprox(y, -1; atol=1e-2) + + x, y = rotate(1, 1, π) + @test isapprox(x, -1; atol=1e-2) && isapprox(y, -1; atol=1e-2) + + x, y = rotate(2, 2, -π/3) + @test isapprox(x, 2.73205; atol=1e-2) && isapprox(y, -0.73205; atol=1e-2) + end + + @testset "time argument" begin + @test isapprox(timearg(0, 1), 0; atol=1e-2) + + @test isapprox(timearg(2, 2), π/4; atol=1e-2) + + @test isapprox(timearg(0, -7), π; atol=1e-2) + + @test isapprox(timearg(-4, 0), 3π/2; atol=1e-2) + + @test isapprox(timearg(1, √3), π/6; atol=1e-2) + end + + @testset "read time" begin + minsec(strtime) = parse.(Float64, match(r"(\d+\.?\d*)m (\d+\.?\d*)s", strtime).captures) + + m, s = minsec(readtime(1, 0)) + @test isapprox(m, 1.0; atol=1e-9) && isapprox(s, 15.0; atol=1e-2) + + m, s = minsec(readtime(-4, 0)) + @test isapprox(m, 4.0; atol=1e-9) && isapprox(s, 45.0; atol=1e-2) + + m, s = minsec(readtime(0, -3)) + @test isapprox(m, 3.0; atol=1e-9) && isapprox(s, 30.0; atol=1e-2) + + m, s = minsec(readtime(√2, -√2)) + @test isapprox(m, 2.0; atol=1e-9) && isapprox(s, 22.5; atol=1e-2) + end + + @testset "find timer coordinates" begin + x, y = getcoords(1, 0) + @test isapprox(x, 0; atol=1e-2) && isapprox(y, 1; atol=1e-2) + + x, y = getcoords(2, 15) + @test isapprox(x, 2; atol=1e-2) && isapprox(y, 0; atol=1e-2) + + x, y = getcoords(3, 45) + @test isapprox(x, -3; atol=1e-2) && isapprox(y, 0; atol=1e-2) + + x, y = getcoords(4, 37.5) + @test isapprox(x, -2.8284; atol=1e-2) && isapprox(y, -2.8284; atol=1e-2) + end +end diff --git a/exercises/concept/strange-stopwatch/strange-stopwatch.jl b/exercises/concept/strange-stopwatch/strange-stopwatch.jl new file mode 100644 index 000000000..b02857c3d --- /dev/null +++ b/exercises/concept/strange-stopwatch/strange-stopwatch.jl @@ -0,0 +1,15 @@ +function rotate(x, y, θ) + +end + +function timearg(z) + +end + +function readtime((a, b)) + +end + +function getcoords(m, s) + +end