Skip to content

Commit 99c27cb

Browse files
committed
Add practice exercise piecing-it-together
1 parent 860d527 commit 99c27cb

12 files changed

Lines changed: 505 additions & 0 deletions

File tree

config.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,6 +2675,25 @@
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+
],
2685+
"prerequisites": [
2686+
"structs",
2687+
"errors",
2688+
"multiple-clause-functions",
2689+
"pattern-matching",
2690+
"guards",
2691+
"if",
2692+
"case",
2693+
"floating-point-numbers"
2694+
],
2695+
"difficulty": 7
2696+
},
26782697
{
26792698
"slug": "queen-attack",
26802699
"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/piecing_it_together.ex"
8+
],
9+
"test": [
10+
"test/piecing_it_together_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: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
defmodule PiecingItTogether do
2+
@doc """
3+
TODO: add function description and replace types in @spec
4+
"""
5+
6+
defmodule JigsawPuzzle do
7+
@type format() :: :landscape | :portrait | :square
8+
@type t() :: %__MODULE__{
9+
pieces: pos_integer(),
10+
rows: pos_integer(),
11+
columns: pos_integer(),
12+
format: format(),
13+
aspect_ratio: float(),
14+
border: pos_integer(),
15+
inside: pos_integer()
16+
}
17+
18+
defstruct [:pieces, :rows, :columns, :format, :aspect_ratio, :border, :inside]
19+
end
20+
21+
@spec jigsaw_data(jigsaw_puzzle :: JigsawPuzzle.t()) ::
22+
{:ok, JigsawPuzzle.t()} | {:error, String.t()}
23+
def jigsaw_data(%JigsawPuzzle{} = jigsaw_puzzle) do
24+
jigsaw_puzzle
25+
|> Map.from_struct()
26+
|> Enum.filter(fn {_, value} -> value end)
27+
|> Enum.sort_by(fn {key, _} -> key end)
28+
|> complete_jigsaw_data()
29+
end
30+
31+
defp complete_jigsaw_data(aspect_ratio: aspect_ratio, pieces: pieces) do
32+
columns = round(:math.sqrt(aspect_ratio * pieces))
33+
rows = Integer.floor_div(pieces, columns)
34+
border = 2 * (rows + columns) - 4
35+
inside = pieces - border
36+
37+
{:ok,
38+
%JigsawPuzzle{
39+
pieces: pieces,
40+
rows: rows,
41+
columns: columns,
42+
format: aspect_ratio_to_format(aspect_ratio),
43+
aspect_ratio: aspect_ratio,
44+
border: border,
45+
inside: inside
46+
}}
47+
end
48+
49+
defp complete_jigsaw_data(format: :square, rows: rows) do
50+
columns = rows
51+
pieces = rows * columns
52+
border = 2 * (rows + columns) - 4
53+
inside = pieces - border
54+
55+
{:ok,
56+
%JigsawPuzzle{
57+
pieces: pieces,
58+
rows: rows,
59+
columns: columns,
60+
format: :square,
61+
aspect_ratio: 1.0,
62+
border: border,
63+
inside: inside
64+
}}
65+
end
66+
67+
defp complete_jigsaw_data(format: _, rows: _) do
68+
{:error, "Insufficient data"}
69+
end
70+
71+
defp complete_jigsaw_data(aspect_ratio: 1.0, inside: inside) do
72+
columns = 2 + round(:math.sqrt(inside))
73+
rows = columns
74+
pieces = rows * columns
75+
border = pieces - inside
76+
77+
{:ok,
78+
%JigsawPuzzle{
79+
pieces: pieces,
80+
rows: rows,
81+
columns: columns,
82+
format: :square,
83+
aspect_ratio: 1.0,
84+
border: border,
85+
inside: inside
86+
}}
87+
end
88+
89+
defp complete_jigsaw_data(aspect_ratio: _, inside: _) do
90+
{:error, "Insufficient data"}
91+
end
92+
93+
defp complete_jigsaw_data(aspect_ratio: aspect_ratio, rows: rows) do
94+
columns = round(rows * aspect_ratio)
95+
pieces = rows * columns
96+
border = 2 * (rows + columns) - 4
97+
inside = pieces - border
98+
99+
{:ok,
100+
%JigsawPuzzle{
101+
pieces: pieces,
102+
rows: rows,
103+
columns: columns,
104+
format: aspect_ratio_to_format(aspect_ratio),
105+
aspect_ratio: aspect_ratio,
106+
border: border,
107+
inside: inside
108+
}}
109+
end
110+
111+
defp complete_jigsaw_data(border: border, format: :square, pieces: pieces) do
112+
complete_jigsaw_data(aspect_ratio: 1.0, inside: pieces - border)
113+
end
114+
115+
defp complete_jigsaw_data(border: border, format: format, pieces: pieces) do
116+
center = 1 + border / 4
117+
diff = :math.sqrt(center ** 2 - pieces)
118+
119+
{rows, columns} =
120+
case format do
121+
:landscape -> {round(center - diff), round(center + diff)}
122+
:portrait -> {round(center + diff), round(center - diff)}
123+
end
124+
125+
aspect_ratio = columns / rows
126+
inside = pieces - border
127+
128+
{:ok,
129+
%JigsawPuzzle{
130+
pieces: pieces,
131+
rows: rows,
132+
columns: columns,
133+
format: format,
134+
aspect_ratio: aspect_ratio,
135+
border: border,
136+
inside: inside
137+
}}
138+
end
139+
140+
defp complete_jigsaw_data(columns: columns, format: format, rows: rows) do
141+
aspect_ratio = columns / rows
142+
143+
if format == aspect_ratio_to_format(aspect_ratio) do
144+
pieces = rows * columns
145+
border = 2 * (rows + columns) - 4
146+
inside = rows * columns - border
147+
148+
{:ok,
149+
%JigsawPuzzle{
150+
pieces: pieces,
151+
rows: rows,
152+
columns: columns,
153+
format: format,
154+
aspect_ratio: aspect_ratio,
155+
border: border,
156+
inside: inside
157+
}}
158+
else
159+
{:error, "Contradictory data"}
160+
end
161+
end
162+
163+
defp complete_jigsaw_data(_) do
164+
{:error, "Insufficient data"}
165+
end
166+
167+
defp aspect_ratio_to_format(aspect_ratio) do
168+
case aspect_ratio do
169+
aspect_ratio when aspect_ratio < 1 -> :portrait
170+
aspect_ratio when aspect_ratio > 1 -> :landscape
171+
_ -> :square
172+
end
173+
end
174+
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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule PiecingItTogether do
2+
@doc """
3+
Fill in missing jigsaw puzzle details from partial data
4+
"""
5+
6+
defmodule JigsawPuzzle do
7+
@type format() :: :landscape | :portrait | :square
8+
@type t() :: %__MODULE__{
9+
pieces: pos_integer(),
10+
rows: pos_integer(),
11+
columns: pos_integer(),
12+
format: format(),
13+
aspect_ratio: float(),
14+
border: pos_integer(),
15+
inside: pos_integer()
16+
}
17+
18+
defstruct [:pieces, :rows, :columns, :format, :aspect_ratio, :border, :inside]
19+
end
20+
21+
@spec jigsaw_data(jigsaw_puzzle :: JigsawPuzzle.t()) ::
22+
{:ok, JigsawPuzzle.t()} | {:error, String.t()}
23+
def jigsaw_data(jigsaw_puzzle) do
24+
end
25+
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
defmodule PiecingItTogether.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)