Skip to content

Commit 0d9b514

Browse files
Add practice exercise piecing-it-together (#1576)
* Add practice exercise `piecing-it-together` * practive integers and floats Co-authored-by: Angelika Cathor <angelikatyborska@fastmail.com> * fix doc * add prerequisites * integers already practiced 10 times * add nils to struct types * do not nest module * Fix module name in mix.exs --------- Co-authored-by: Angelika Cathor <angelikatyborska@fastmail.com> Co-authored-by: Angelika Cathor <angelikacathor@fastmail.com>
1 parent 7cce0e8 commit 0d9b514

12 files changed

Lines changed: 506 additions & 0 deletions

File tree

config.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,6 +2675,31 @@
26752675
],
26762676
"difficulty": 7
26772677
},
2678+
{
2679+
"slug": "piecing-it-together",
2680+
"name": "Piecing It Together",
2681+
"uuid": "e20ce41a-ff3f-46e4-9fbb-8810883c488f",
2682+
"practices": [
2683+
"structs",
2684+
"floating-point-numbers"
2685+
],
2686+
"prerequisites": [
2687+
"structs",
2688+
"errors",
2689+
"multiple-clause-functions",
2690+
"pattern-matching",
2691+
"guards",
2692+
"if",
2693+
"case",
2694+
"integers",
2695+
"floating-point-numbers",
2696+
"maps",
2697+
"enum",
2698+
"keyword-lists",
2699+
"erlang-libraries"
2700+
],
2701+
"difficulty": 7
2702+
},
26782703
{
26792704
"slug": "queen-attack",
26802705
"name": "Queen Attack",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Instructions
2+
3+
Given partial information about a jigsaw puzzle, add the missing pieces.
4+
5+
If the information is insufficient to complete the details, or if given parts are in contradiction, the user should be notified.
6+
7+
The full information about the jigsaw puzzle contains the following parts:
8+
9+
- `pieces`: Total number of pieces
10+
- `border`: Number of border pieces
11+
- `inside`: Number of inside (non-border) pieces
12+
- `rows`: Number of rows
13+
- `columns`: Number of columns
14+
- `aspectRatio`: Aspect ratio of columns to rows
15+
- `format`: Puzzle format, which can be `portrait`, `square`, or `landscape`
16+
17+
For this exercise, you may assume square pieces, so that the format can be derived from the aspect ratio:
18+
19+
- If the aspect ratio is less than 1, it's `portrait`
20+
- If it is equal to 1, it's `square`
21+
- If it is greater than 1, it's `landscape`
22+
23+
## Three examples
24+
25+
### Portrait
26+
27+
A portrait jigsaw puzzle with 6 pieces, all of which are border pieces and none are inside pieces. It has 3 rows and 2 columns. The aspect ratio is 1.5 (3/2).
28+
29+
![A 2 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-2x3.svg)
30+
31+
### Square
32+
33+
A square jigsaw puzzle with 9 pieces, all of which are border pieces except for the one in the center, which is an inside piece. It has 3 rows and 3 columns. The aspect ratio is 1 (3/3).
34+
35+
![A 3 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-3x3.svg)
36+
37+
### Landscape
38+
39+
A landscape jigsaw puzzle with 12 pieces, 10 of which are border pieces and 2 are inside pieces. It has 3 rows and 4 columns. The aspect ratio is 1.333333... (4/3).
40+
41+
![A 4 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-4x3.svg)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Introduction
2+
3+
Your best friend has started collecting jigsaw puzzles and wants to build a detailed catalog of their collection — recording information such as the total number of pieces, the number of rows and columns, the number of border and inside pieces, aspect ratio, and puzzle format.
4+
Even with your powers combined, it takes multiple hours to solve a single jigsaw puzzle with one thousand pieces — and then you still need to count the rows and columns and calculate the remaining numbers manually.
5+
The even larger puzzles with thousands of pieces look quite daunting now.
6+
"There has to be a better way!" you exclaim and sit down in front of your computer to solve the problem.
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+
piecing_it_together-*.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/jigsaw_puzzle.ex"
8+
],
9+
"test": [
10+
"test/jigsaw_puzzle_test.exs"
11+
],
12+
"example": [
13+
".meta/example.ex"
14+
]
15+
},
16+
"blurb": "Fill in missing jigsaw puzzle details from partial data",
17+
"source": "atk just started another 1000-pieces jigsaw puzzle when this idea hit him",
18+
"source_url": "https://github.com/exercism/problem-specifications/pull/2554"
19+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
defmodule JigsawPuzzle do
2+
@doc """
3+
Fill in missing jigsaw puzzle details from partial data
4+
"""
5+
6+
@type format() :: :landscape | :portrait | :square
7+
@type t() :: %__MODULE__{
8+
pieces: pos_integer() | nil,
9+
rows: pos_integer() | nil,
10+
columns: pos_integer() | nil,
11+
format: format() | nil,
12+
aspect_ratio: float() | nil,
13+
border: pos_integer() | nil,
14+
inside: pos_integer() | nil
15+
}
16+
17+
defstruct [:pieces, :rows, :columns, :format, :aspect_ratio, :border, :inside]
18+
19+
@spec data(jigsaw_puzzle :: JigsawPuzzle.t()) ::
20+
{:ok, JigsawPuzzle.t()} | {:error, String.t()}
21+
def data(%JigsawPuzzle{} = jigsaw_puzzle) do
22+
jigsaw_puzzle
23+
|> Map.from_struct()
24+
|> Enum.filter(fn {_, value} -> value end)
25+
|> Enum.sort_by(fn {key, _} -> key end)
26+
|> complete_data()
27+
end
28+
29+
defp complete_data(aspect_ratio: aspect_ratio, pieces: pieces) do
30+
columns = round(:math.sqrt(aspect_ratio * pieces))
31+
rows = Integer.floor_div(pieces, columns)
32+
border = 2 * (rows + columns) - 4
33+
inside = pieces - border
34+
35+
{:ok,
36+
%JigsawPuzzle{
37+
pieces: pieces,
38+
rows: rows,
39+
columns: columns,
40+
format: aspect_ratio_to_format(aspect_ratio),
41+
aspect_ratio: aspect_ratio,
42+
border: border,
43+
inside: inside
44+
}}
45+
end
46+
47+
defp complete_data(format: :square, rows: rows) do
48+
columns = rows
49+
pieces = rows * columns
50+
border = 2 * (rows + columns) - 4
51+
inside = pieces - border
52+
53+
{:ok,
54+
%JigsawPuzzle{
55+
pieces: pieces,
56+
rows: rows,
57+
columns: columns,
58+
format: :square,
59+
aspect_ratio: 1.0,
60+
border: border,
61+
inside: inside
62+
}}
63+
end
64+
65+
defp complete_data(format: _, rows: _) do
66+
{:error, "Insufficient data"}
67+
end
68+
69+
defp complete_data(aspect_ratio: 1.0, inside: inside) do
70+
columns = 2 + round(:math.sqrt(inside))
71+
rows = columns
72+
pieces = rows * columns
73+
border = pieces - inside
74+
75+
{:ok,
76+
%JigsawPuzzle{
77+
pieces: pieces,
78+
rows: rows,
79+
columns: columns,
80+
format: :square,
81+
aspect_ratio: 1.0,
82+
border: border,
83+
inside: inside
84+
}}
85+
end
86+
87+
defp complete_data(aspect_ratio: _, inside: _) do
88+
{:error, "Insufficient data"}
89+
end
90+
91+
defp complete_data(aspect_ratio: aspect_ratio, rows: rows) do
92+
columns = round(rows * aspect_ratio)
93+
pieces = rows * columns
94+
border = 2 * (rows + columns) - 4
95+
inside = pieces - border
96+
97+
{:ok,
98+
%JigsawPuzzle{
99+
pieces: pieces,
100+
rows: rows,
101+
columns: columns,
102+
format: aspect_ratio_to_format(aspect_ratio),
103+
aspect_ratio: aspect_ratio,
104+
border: border,
105+
inside: inside
106+
}}
107+
end
108+
109+
defp complete_data(border: border, format: :square, pieces: pieces) do
110+
complete_data(aspect_ratio: 1.0, inside: pieces - border)
111+
end
112+
113+
defp complete_data(border: border, format: format, pieces: pieces) do
114+
center = 1 + border / 4
115+
diff = :math.sqrt(center ** 2 - pieces)
116+
117+
{rows, columns} =
118+
case format do
119+
:landscape -> {round(center - diff), round(center + diff)}
120+
:portrait -> {round(center + diff), round(center - diff)}
121+
end
122+
123+
aspect_ratio = columns / rows
124+
inside = pieces - border
125+
126+
{:ok,
127+
%JigsawPuzzle{
128+
pieces: pieces,
129+
rows: rows,
130+
columns: columns,
131+
format: format,
132+
aspect_ratio: aspect_ratio,
133+
border: border,
134+
inside: inside
135+
}}
136+
end
137+
138+
defp complete_data(columns: columns, format: format, rows: rows) do
139+
aspect_ratio = columns / rows
140+
141+
if format == aspect_ratio_to_format(aspect_ratio) do
142+
pieces = rows * columns
143+
border = 2 * (rows + columns) - 4
144+
inside = rows * columns - border
145+
146+
{:ok,
147+
%JigsawPuzzle{
148+
pieces: pieces,
149+
rows: rows,
150+
columns: columns,
151+
format: format,
152+
aspect_ratio: aspect_ratio,
153+
border: border,
154+
inside: inside
155+
}}
156+
else
157+
{:error, "Contradictory data"}
158+
end
159+
end
160+
161+
defp complete_data(_) do
162+
{:error, "Insufficient data"}
163+
end
164+
165+
defp aspect_ratio_to_format(aspect_ratio) do
166+
case aspect_ratio do
167+
aspect_ratio when aspect_ratio < 1 -> :portrait
168+
aspect_ratio when aspect_ratio > 1 -> :landscape
169+
_ -> :square
170+
end
171+
end
172+
end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
[ad626f23-09a2-4f5f-ba22-eec0671fa2a9]
13+
description = "1000 pieces puzzle with 1.6 aspect ratio"
14+
15+
[3e0c5919-3561-42f5-b9ed-26d70c20214e]
16+
description = "square puzzle with 32 rows"
17+
18+
[1126f160-b094-4dc2-bf37-13e36e394867]
19+
description = "400 pieces square puzzle with only inside pieces and aspect ratio"
20+
21+
[a9743178-5642-4cc0-8fdb-00d6b031c3a0]
22+
description = "1500 pieces landscape puzzle with 30 rows and 1.6 aspect ratio"
23+
24+
[f6378369-989c-497f-a6e2-f30b1fa76cba]
25+
description = "300 pieces portrait puzzle with 70 border pieces"
26+
27+
[f53f82ba-5663-4c7e-9e86-57fdbb3e53d2]
28+
description = "puzzle with insufficient data"
29+
30+
[a3d5c31a-cc74-44bf-b4fc-9e4d65f1ac1a]
31+
description = "puzzle with contradictory data"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
defmodule JigsawPuzzle do
2+
@doc """
3+
Fill in missing jigsaw puzzle details from partial data
4+
"""
5+
6+
@type format() :: :landscape | :portrait | :square
7+
@type t() :: %__MODULE__{
8+
pieces: pos_integer() | nil,
9+
rows: pos_integer() | nil,
10+
columns: pos_integer() | nil,
11+
format: format() | nil,
12+
aspect_ratio: float() | nil,
13+
border: pos_integer() | nil,
14+
inside: pos_integer() | nil
15+
}
16+
17+
defstruct [:pieces, :rows, :columns, :format, :aspect_ratio, :border, :inside]
18+
19+
@spec data(jigsaw_puzzle :: JigsawPuzzle.t()) ::
20+
{:ok, JigsawPuzzle.t()} | {:error, String.t()}
21+
def data(jigsaw_puzzle) do
22+
end
23+
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
defmodule JigsawPuzzle.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :piecing_it_together,
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)