Skip to content

Commit 0c41cbd

Browse files
authored
[#792] New practice exercise two-bucket (#808)
1 parent 7f39df4 commit 0c41cbd

File tree

10 files changed

+328
-0
lines changed

10 files changed

+328
-0
lines changed

config.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2713,6 +2713,26 @@
27132713
"maps"
27142714
],
27152715
"difficulty": 3
2716+
},
2717+
{
2718+
"slug": "two-bucket",
2719+
"name": "Two Bucket",
2720+
"uuid": "07b4679a-3d9f-42bc-ad1e-b9ba272a1c15",
2721+
"prerequisites": [
2722+
"atoms",
2723+
"tuples",
2724+
"integers",
2725+
"lists",
2726+
"pattern-matching",
2727+
"if",
2728+
"cond",
2729+
"case",
2730+
"structs",
2731+
"enum",
2732+
"recursion"
2733+
],
2734+
"practices": [],
2735+
"difficulty": 7
27162736
}
27172737
],
27182738
"foregone": [
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Description
2+
3+
Given two buckets of different size, demonstrate how to measure an exact number of liters by strategically transferring liters of fluid between the buckets.
4+
5+
Since this mathematical problem is fairly subject to interpretation / individual approach, the tests have been written specifically to expect one overarching solution.
6+
7+
To help, the tests provide you with which bucket to fill first. That means, when starting with the larger bucket full, you are NOT allowed at any point to have the smaller bucket full and the larger bucket empty (aka, the opposite starting point); that would defeat the purpose of comparing both approaches!
8+
9+
Your program will take as input:
10+
- the size of bucket one
11+
- the size of bucket two
12+
- the desired number of liters to reach
13+
- which bucket to fill first, either bucket one or bucket two
14+
15+
Your program should determine:
16+
- the total number of "moves" it should take to reach the desired number of liters, including the first fill
17+
- which bucket should end up with the desired number of liters (let's say this is bucket A) - either bucket one or bucket two
18+
- how many liters are left in the other bucket (bucket B)
19+
20+
Note: any time a change is made to either or both buckets counts as one (1) move.
21+
22+
Example:
23+
Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters. Let's say bucket one, at a given step, is holding 7 liters, and bucket two is holding 8 liters (7,8). If you empty bucket one and make no change to bucket two, leaving you with 0 liters and 8 liters respectively (0,8), that counts as one "move". Instead, if you had poured from bucket one into bucket two until bucket two was full, leaving you with 4 liters in bucket one and 11 liters in bucket two (4,11), that would count as only one "move" as well.
24+
25+
To conclude, the only valid moves are:
26+
- pouring from either bucket to another
27+
- emptying either bucket and doing nothing to the other
28+
- filling either bucket and doing nothing to the other
29+
30+
Written with <3 at [Fullstack Academy](http://www.fullstackacademy.com/) by Lindsay Levine.
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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"authors": ["jiegillet"],
3+
"contributors": [
4+
"angelikatyborska",
5+
"neenjaw"
6+
],
7+
"files": {
8+
"example": [
9+
".meta/example.ex"
10+
],
11+
"solution": [
12+
"lib/two_bucket.ex"
13+
],
14+
"test": [
15+
"test/two_bucket_test.exs"
16+
]
17+
},
18+
"blurb": "Given two buckets of different size, demonstrate how to measure an exact number of liters.",
19+
"source": "Water Pouring Problem",
20+
"source_url": "http://demonstrations.wolfram.com/WaterPouringProblem/"
21+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
defmodule TwoBucket do
2+
defstruct [:bucket_one, :bucket_two, :moves]
3+
@type t :: %TwoBucket{bucket_one: integer, bucket_two: integer, moves: integer}
4+
5+
@doc """
6+
Find the quickest way to fill a bucket with some amount of water from two buckets of specific sizes.
7+
"""
8+
@spec measure(
9+
size_one :: integer,
10+
size_two :: integer,
11+
goal :: integer,
12+
start_bucket :: :one | :two
13+
) :: {:ok, TwoBucket.t()} | {:error, :impossible}
14+
def measure(size_one, size_two, goal, start_bucket) do
15+
other_filled = if start_bucket == :one, do: {0, size_two}, else: {size_one, 0}
16+
forbidden_states = MapSet.new([other_filled])
17+
18+
# Partially apply function to avoid passing many arguments
19+
next_moves = &next_moves(&1, size_one, size_two)
20+
21+
pour([{{0, 0}, 0}], forbidden_states, next_moves, goal)
22+
end
23+
24+
def pour([], _, _, _), do: {:error, :impossible}
25+
26+
def pour([{{a, b}, moves} | _], _, _, goal) when a == goal or b == goal,
27+
do: {:ok, %TwoBucket{bucket_one: a, bucket_two: b, moves: moves}}
28+
29+
def pour([{state, moves} | states], forbidden_states, next_moves, goal) do
30+
next =
31+
next_moves.(state)
32+
|> Enum.reject(&MapSet.member?(forbidden_states, &1))
33+
|> Enum.map(&{&1, moves + 1})
34+
35+
pour(states ++ next, MapSet.put(forbidden_states, state), next_moves, goal)
36+
end
37+
38+
def next_moves({fill_one, fill_two}, size_one, size_two) do
39+
[
40+
# Empty a bucket
41+
{0, fill_two},
42+
{fill_one, 0},
43+
# Fill a bucket
44+
{fill_one, size_two},
45+
{size_one, fill_two},
46+
# Pour one into the other
47+
if fill_one < size_two - fill_two do
48+
{0, fill_one + fill_two}
49+
else
50+
{fill_one - (size_two - fill_two), size_two}
51+
end,
52+
if fill_two < size_one - fill_one do
53+
{fill_one + fill_two, 0}
54+
else
55+
{size_one, fill_two - (size_one - fill_one)}
56+
end
57+
]
58+
|> Enum.uniq()
59+
end
60+
end
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
[a6f2b4ba-065f-4dca-b6f0-e3eee51cb661]
12+
description = "Measure using bucket one of size 3 and bucket two of size 5 - start with bucket one"
13+
14+
[6c4ea451-9678-4926-b9b3-68364e066d40]
15+
description = "Measure using bucket one of size 3 and bucket two of size 5 - start with bucket two"
16+
17+
[3389f45e-6a56-46d5-9607-75aa930502ff]
18+
description = "Measure using bucket one of size 7 and bucket two of size 11 - start with bucket one"
19+
20+
[fe0ff9a0-3ea5-4bf7-b17d-6d4243961aa1]
21+
description = "Measure using bucket one of size 7 and bucket two of size 11 - start with bucket two"
22+
23+
[0ee1f57e-da84-44f7-ac91-38b878691602]
24+
description = "Measure one step using bucket one of size 1 and bucket two of size 3 - start with bucket two"
25+
26+
[eb329c63-5540-4735-b30b-97f7f4df0f84]
27+
description = "Measure using bucket one of size 2 and bucket two of size 3 - start with bucket one and end with bucket two"
28+
29+
[449be72d-b10a-4f4b-a959-ca741e333b72]
30+
description = "Not possible to reach the goal"
31+
32+
[aac38b7a-77f4-4d62-9b91-8846d533b054]
33+
description = "With the same buckets but a different goal, then it is possible"
34+
35+
[74633132-0ccf-49de-8450-af4ab2e3b299]
36+
description = "Goal larger than both buckets is impossible"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
defmodule TwoBucket do
2+
defstruct [:bucket_one, :bucket_two, :moves]
3+
@type t :: %TwoBucket{bucket_one: integer, bucket_two: integer, moves: integer}
4+
5+
@doc """
6+
Find the quickest way to fill a bucket with some amount of water from two buckets of specific sizes.
7+
"""
8+
@spec measure(
9+
size_one :: integer,
10+
size_two :: integer,
11+
goal :: integer,
12+
start_bucket :: :one | :two
13+
) :: {:ok, TwoBucket.t()} | {:error, :impossible}
14+
def measure(size_one, size_two, goal, start_bucket) do
15+
end
16+
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
defmodule TwoBucket.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :two_bucket,
7+
version: "0.1.0",
8+
# elixir: "~> 1.8",
9+
start_permanent: Mix.env() == :prod,
10+
deps: deps()
11+
]
12+
end
13+
14+
# Run "mix help compile.app" to learn about applications.
15+
def application do
16+
[
17+
extra_applications: [:logger]
18+
]
19+
end
20+
21+
# Run "mix help deps" to learn about dependencies.
22+
defp deps do
23+
[
24+
# {:dep_from_hexpm, "~> 0.3.0"},
25+
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
26+
]
27+
end
28+
end
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ExUnit.start()
2+
ExUnit.configure(exclude: :pending, trace: true)
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
defmodule TwoBucketTest do
2+
use ExUnit.Case
3+
4+
# @tag :pending
5+
test "Measure using bucket one of size 3 and bucket two of size 5 - start with bucket one" do
6+
bucket_one = 3
7+
bucket_two = 5
8+
goal = 1
9+
start_bucket = :one
10+
output = TwoBucket.measure(bucket_one, bucket_two, goal, start_bucket)
11+
expected = {:ok, %TwoBucket{bucket_one: goal, bucket_two: 5, moves: 4}}
12+
13+
assert output == expected
14+
end
15+
16+
@tag :pending
17+
test "Measure using bucket one of size 3 and bucket two of size 5 - start with bucket two" do
18+
bucket_one = 3
19+
bucket_two = 5
20+
goal = 1
21+
start_bucket = :two
22+
output = TwoBucket.measure(bucket_one, bucket_two, goal, start_bucket)
23+
expected = {:ok, %TwoBucket{bucket_one: 3, bucket_two: goal, moves: 8}}
24+
25+
assert output == expected
26+
end
27+
28+
@tag :pending
29+
test "Measure using bucket one of size 7 and bucket two of size 11 - start with bucket one" do
30+
bucket_one = 7
31+
bucket_two = 11
32+
goal = 2
33+
start_bucket = :one
34+
output = TwoBucket.measure(bucket_one, bucket_two, goal, start_bucket)
35+
expected = {:ok, %TwoBucket{bucket_one: goal, bucket_two: 11, moves: 14}}
36+
37+
assert output == expected
38+
end
39+
40+
@tag :pending
41+
test "Measure using bucket one of size 7 and bucket two of size 11 - start with bucket two" do
42+
bucket_one = 7
43+
bucket_two = 11
44+
goal = 2
45+
start_bucket = :two
46+
output = TwoBucket.measure(bucket_one, bucket_two, goal, start_bucket)
47+
expected = {:ok, %TwoBucket{bucket_one: 7, bucket_two: goal, moves: 18}}
48+
49+
assert output == expected
50+
end
51+
52+
@tag :pending
53+
test "Measure one step using bucket one of size 1 and bucket two of size 3 - start with bucket two" do
54+
bucket_one = 1
55+
bucket_two = 3
56+
goal = 3
57+
start_bucket = :two
58+
output = TwoBucket.measure(bucket_one, bucket_two, goal, start_bucket)
59+
expected = {:ok, %TwoBucket{bucket_one: 0, bucket_two: goal, moves: 1}}
60+
61+
assert output == expected
62+
end
63+
64+
@tag :pending
65+
test "Measure using bucket one of size 2 and bucket two of size 3 - start with bucket one and end with bucket two" do
66+
bucket_one = 2
67+
bucket_two = 3
68+
goal = 3
69+
start_bucket = :one
70+
output = TwoBucket.measure(bucket_one, bucket_two, goal, start_bucket)
71+
expected = {:ok, %TwoBucket{bucket_one: 2, bucket_two: goal, moves: 2}}
72+
73+
assert output == expected
74+
end
75+
76+
@tag :pending
77+
test "Not possible to reach the goal" do
78+
bucket_one = 6
79+
bucket_two = 15
80+
goal = 5
81+
start_bucket = :one
82+
output = TwoBucket.measure(bucket_one, bucket_two, goal, start_bucket)
83+
expected = {:error, :impossible}
84+
85+
assert output == expected
86+
end
87+
88+
@tag :pending
89+
test "With the same buckets but a different goal, then it is possible" do
90+
bucket_one = 6
91+
bucket_two = 15
92+
goal = 9
93+
start_bucket = :one
94+
output = TwoBucket.measure(bucket_one, bucket_two, goal, start_bucket)
95+
expected = {:ok, %TwoBucket{bucket_one: 0, bucket_two: goal, moves: 10}}
96+
97+
assert output == expected
98+
end
99+
100+
@tag :pending
101+
test "Goal larger than both buckets is impossible" do
102+
bucket_one = 5
103+
bucket_two = 7
104+
goal = 8
105+
start_bucket = :one
106+
output = TwoBucket.measure(bucket_one, bucket_two, goal, start_bucket)
107+
expected = {:error, :impossible}
108+
109+
assert output == expected
110+
end
111+
end

0 commit comments

Comments
 (0)