Skip to content

Commit f92b24d

Browse files
authored
Add practice exercise prism (#1617)
* Add practice exercise `prism` * include latest docs and metadata updates
1 parent 5eb78e9 commit f92b24d

12 files changed

Lines changed: 631 additions & 0 deletions

File tree

config.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2503,6 +2503,31 @@
25032503
],
25042504
"difficulty": 6
25052505
},
2506+
{
2507+
"slug": "prism",
2508+
"name": "Prism",
2509+
"uuid": "a2d25eb2-06bb-4e50-8dff-c29582b8e1c6",
2510+
"practices": [
2511+
"erlang-libraries",
2512+
"floating-point-numbers"
2513+
],
2514+
"prerequisites": [
2515+
"lists",
2516+
"enum",
2517+
"maps",
2518+
"pipe-operator",
2519+
"recursion",
2520+
"tail-call-recursion",
2521+
"pattern-matching",
2522+
"anonymous-functions",
2523+
"booleans",
2524+
"erlang-libraries",
2525+
"floating-point-numbers",
2526+
"module-attributes-as-constants",
2527+
"nil"
2528+
],
2529+
"difficulty": 6
2530+
},
25062531
{
25072532
"slug": "rail-fence-cipher",
25082533
"name": "Rail Fence Cipher",
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Instructions
2+
3+
Before activating the laser array, you must predict the exact order in which crystals will be hit, identified by their sample IDs.
4+
5+
## Example Test Case
6+
7+
Consider this crystal array configuration:
8+
9+
```json
10+
{
11+
"start": { "x": 0, "y": 0, "angle": 0 },
12+
"prisms": [
13+
{ "id": 1, "x": 10, "y": 10, "angle": -90 },
14+
{ "id": 2, "x": 10, "y": 0, "angle": 90 },
15+
{ "id": 3, "x": 30, "y": 10, "angle": 45 },
16+
{ "id": 4, "x": 20, "y": 0, "angle": 0 }
17+
]
18+
}
19+
```
20+
21+
## What's Happening
22+
23+
The laser starts at the origin `(0, 0)` and fires horizontally to the right at angle 0°.
24+
Here's the step-by-step beam path:
25+
26+
**Step 1**: The beam travels along the x-axis (y = 0) and first encounters **Crystal #2** at position `(10, 0)`.
27+
This crystal has a refraction angle of 90°, which means it bends the beam perpendicular to its current path.
28+
The beam, originally traveling at 0°, is now redirected to 90° (straight up).
29+
30+
**Step 2**: The beam now travels vertically upward from position `(10, 0)` and strikes **Crystal #1** at position `(10, 10)`.
31+
This crystal has a refraction angle of -90°, bending the beam by -90° relative to its current direction.
32+
The beam was traveling at 90°, so after refraction it's now at 0° (90° + (-90°) = 0°), traveling horizontally to the right again.
33+
34+
**Step 3**: From position `(10, 10)`, the beam travels horizontally and encounters **Crystal #3** at position `(30, 10)`.
35+
This crystal refracts the beam by 45°, changing its direction to 45°.
36+
The beam continues into empty space beyond the array.
37+
38+
!["A graph showing the path of a laser beam refracted through three prisms."](https://assets.exercism.org/images/exercises/prism/laser_path-light.svg)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Introduction
2+
3+
You're a researcher at **PRISM** (Precariously Redirected Illumination Safety Management), working with a precision laser calibration system that tests experimental crystal prisms.
4+
These crystals are being developed for next-generation optical computers, and each one has unique refractive properties based on its molecular structure.
5+
The lab's laser system can damage crystals if they receive unexpected illumination, so precise path prediction is critical.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Used by "mix format"
2+
[
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where third-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# Ignore .fetch files in case you like to edit your project deps locally.
14+
/.fetch
15+
16+
# If the VM crashes, it generates a dump, let's ignore it too.
17+
erl_crash.dump
18+
19+
# Also ignore archive artifacts (built via "mix archive.build").
20+
*.ez
21+
22+
# Ignore package tarball (built via "mix hex.build").
23+
prism-*.tar
24+
25+
# Temporary files, for example, from tests.
26+
/tmp/
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"jiegillet"
4+
],
5+
"files": {
6+
"solution": [
7+
"lib/prism.ex"
8+
],
9+
"test": [
10+
"test/prism_test.exs"
11+
],
12+
"example": [
13+
".meta/example.ex"
14+
]
15+
},
16+
"blurb": "Calculate the path of a laser through refractive prisms.",
17+
"source": "FraSanga",
18+
"source_url": "https://github.com/exercism/problem-specifications/pull/2625"
19+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
defmodule Prism do
2+
@doc """
3+
Finds the sequence of prisms that the laser will hit.
4+
"""
5+
6+
@type start :: %{angle: number(), x: number(), y: number()}
7+
@type prism :: %{id: integer(), angle: number(), x: number(), y: number()}
8+
9+
@spec find_sequence(prisms :: [prism()], start :: start()) :: [integer()]
10+
def find_sequence(prisms, start) do
11+
find_next_prism(start, prisms, [])
12+
end
13+
14+
@precision 0.01
15+
defp find_next_prism(start, prisms, sequence) do
16+
next_prism =
17+
prisms
18+
|> Enum.filter(fn %{x: x, y: y} ->
19+
different_prism? = x != start.x or y != start.y
20+
21+
angle_to_prism = :math.atan2(y - start.y, x - start.x) / :math.pi() * 180
22+
angle_difference = abs(:math.fmod(angle_to_prism - start.angle, 360))
23+
24+
pointing_to_prism? =
25+
angle_difference < @precision or 360 - angle_difference < @precision
26+
27+
different_prism? and pointing_to_prism?
28+
end)
29+
|> Enum.min_by(
30+
fn %{x: x, y: y} -> (x - start.x) ** 2 + (y - start.y) ** 2 end,
31+
&<=/2,
32+
fn -> nil end
33+
)
34+
35+
case next_prism do
36+
nil ->
37+
Enum.reverse(sequence)
38+
39+
%{id: id, angle: angle, x: x, y: y} ->
40+
next_start = %{angle: start.angle + angle, x: x, y: y}
41+
find_next_prism(next_start, prisms, [id | sequence])
42+
end
43+
end
44+
end
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[ec65d3b3-f7bf-4015-8156-0609c141c4c4]
13+
description = "zero prisms"
14+
15+
[ec0ca17c-0c5f-44fb-89ba-b76395bdaf1c]
16+
description = "one prism one hit"
17+
18+
[0db955f2-0a27-4c82-ba67-197bd6202069]
19+
description = "one prism zero hits"
20+
21+
[8d92485b-ebc0-4ee9-9b88-cdddb16b52da]
22+
description = "going up zero hits"
23+
24+
[78295b3c-7438-492d-8010-9c63f5c223d7]
25+
description = "going down zero hits"
26+
27+
[acc723ea-597b-4a50-8d1b-b980fe867d4c]
28+
description = "going left zero hits"
29+
30+
[3f19b9df-9eaa-4f18-a2db-76132f466d17]
31+
description = "negative angle"
32+
33+
[96dacffb-d821-4cdf-aed8-f152ce063195]
34+
description = "large angle"
35+
36+
[513a7caa-957f-4c5d-9820-076842de113c]
37+
description = "upward refraction two hits"
38+
39+
[d452b7c7-9761-4ea9-81a9-2de1d73eb9ef]
40+
description = "downward refraction two hits"
41+
42+
[be1a2167-bf4c-4834-acc9-e4d68e1a0203]
43+
description = "same prism twice"
44+
45+
[df5a60dd-7c7d-4937-ac4f-c832dae79e2e]
46+
description = "simple path"
47+
48+
[8d9a3cc8-e846-4a3b-a137-4bfc4aa70bd1]
49+
description = "multiple prisms floating point precision"
50+
51+
[e077fc91-4e4a-46b3-a0f5-0ba00321da56]
52+
description = "complex path with multiple prisms floating point precision"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
defmodule Prism do
2+
@doc """
3+
Finds the sequence of prisms that the laser will hit.
4+
"""
5+
6+
@type start :: %{angle: number(), x: number(), y: number()}
7+
@type prism :: %{id: integer(), angle: number(), x: number(), y: number()}
8+
9+
@spec find_sequence(prisms :: [prism()], start :: start()) :: [integer()]
10+
def find_sequence(prisms, start) do
11+
end
12+
end

exercises/practice/prism/mix.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
defmodule Prism.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :prism,
7+
version: "0.1.0",
8+
start_permanent: Mix.env() == :prod,
9+
deps: deps()
10+
]
11+
end
12+
13+
# Run "mix help compile.app" to learn about applications.
14+
def application do
15+
[
16+
extra_applications: [:logger]
17+
]
18+
end
19+
20+
# Run "mix help deps" to learn about dependencies.
21+
defp deps do
22+
[
23+
# {:dep_from_hexpm, "~> 0.3.0"},
24+
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
25+
]
26+
end
27+
end

0 commit comments

Comments
 (0)