Skip to content

Commit 8fd4678

Browse files
Sync two-bucket (#1289)
1 parent eadaadf commit 8fd4678

File tree

5 files changed

+152
-112
lines changed

5 files changed

+152
-112
lines changed
Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
# Instructions
22

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.
3+
Given two buckets of different size and which bucket to fill first, determine how many actions are required to measure an exact number of liters by strategically transferring fluid between the buckets.
44

5-
Since this mathematical problem is fairly subject to interpretation / individual approach, the tests have been written specifically to expect one overarching solution.
5+
There are some rules that your solution must follow:
66

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!
7+
- You can only do one action at a time.
8+
- There are only 3 possible actions:
9+
1. Pouring one bucket into the other bucket until either:
10+
a) the first bucket is empty
11+
b) the second bucket is full
12+
2. Emptying a bucket and doing nothing to the other.
13+
3. Filling a bucket and doing nothing to the other.
14+
- After an action, you may not arrive at a state where the starting bucket is empty and the other bucket is full.
815

916
Your program will take as input:
1017

@@ -15,19 +22,25 @@ Your program will take as input:
1522

1623
Your program should determine:
1724

18-
- the total number of "moves" it should take to reach the desired number of liters, including the first fill
19-
- which bucket should end up with the desired number of liters (let's say this is bucket A) - either bucket one or bucket two
20-
- how many liters are left in the other bucket (bucket B)
25+
- the total number of actions it should take to reach the desired number of liters, including the first fill of the starting bucket
26+
- which bucket should end up with the desired number of liters - either bucket one or bucket two
27+
- how many liters are left in the other bucket
2128

22-
Note: any time a change is made to either or both buckets counts as one (1) move.
29+
Note: any time a change is made to either or both buckets counts as one (1) action.
2330

2431
Example:
25-
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.
32+
Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters.
33+
Let's say at a given step, bucket one is holding 7 liters and bucket two is holding 8 liters (7,8).
34+
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 action.
35+
Instead, if you had poured from bucket one into bucket two until bucket two was full, resulting in 4 liters in bucket one and 11 liters in bucket two (4,11), that would also only count as one action.
2636

27-
To conclude, the only valid moves are:
37+
Another Example:
38+
Bucket one can hold 3 liters, and bucket two can hold up to 5 liters.
39+
You are told you must start with bucket one.
40+
So your first action is to fill bucket one.
41+
You choose to empty bucket one for your second action.
42+
For your third action, you may not fill bucket two, because this violates the third rule -- you may not end up in a state after any action where the starting bucket is empty and the other bucket is full.
2843

29-
- pouring from either bucket to another
30-
- emptying either bucket and doing nothing to the other
31-
- filling either bucket and doing nothing to the other
44+
Written with <3 at [Fullstack Academy][fullstack] by Lindsay Levine.
3245

33-
Written with <3 at [Fullstack Academy](http://www.fullstackacademy.com/) by Lindsay Levine.
46+
[fullstack]: https://www.fullstackacademy.com/

exercises/practice/two-bucket/.meta/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
},
1818
"blurb": "Given two buckets of different size, demonstrate how to measure an exact number of liters.",
1919
"source": "Water Pouring Problem",
20-
"source_url": "http://demonstrations.wolfram.com/WaterPouringProblem/"
20+
"source_url": "https://demonstrations.wolfram.com/WaterPouringProblem/"
2121
}
Lines changed: 103 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,117 @@
1-
type Bucket = 'one' | 'two'
1+
class Bucket {
2+
public name: string
3+
public readonly size: number
4+
public amount: number
25

3-
export class TwoBucket {
4-
private readonly starter: Bucket
5-
private readonly x: number
6-
private readonly y: number
7-
private readonly z: number
8-
9-
public goalBucket!: Bucket
10-
public otherBucket!: number
11-
12-
constructor(x: number, y: number, z: number, starter: Bucket) {
13-
this.starter = starter
14-
this.x = x
15-
this.y = y
16-
this.z = z
6+
constructor(name: string, size: number) {
7+
this.name = name
8+
this.size = size
9+
this.amount = 0
1710
}
1811

19-
private reachedGoal(measurements: number[]): boolean {
20-
if (measurements[0] === this.z || measurements[1] === this.z) {
21-
if (measurements[0] === this.z) {
22-
this.goalBucket = 'one'
23-
this.otherBucket = measurements[1]
24-
} else {
25-
this.goalBucket = 'two'
26-
this.otherBucket = measurements[0]
27-
}
28-
return true
29-
}
30-
return false
12+
// accessors
13+
public get available(): number {
14+
return this.size - this.amount
15+
}
16+
public get isFull(): boolean {
17+
return this.amount === this.size
18+
}
19+
public get isEmpty(): boolean {
20+
return this.amount === 0
3121
}
3222

33-
private bigFirst(
34-
measurements: number[],
35-
moveCount: number,
36-
prBool: boolean
37-
): number {
38-
let j = measurements[0],
39-
k = measurements[1]
40-
while (!this.reachedGoal(measurements)) {
41-
if (k > this.x && j === 0 && moveCount === 0) {
42-
j = this.x
43-
k = this.y - j
44-
} else if (j === this.x) {
45-
j = 0
46-
} else if ((k > this.x && j !== 0) || (k > this.x && prBool)) {
47-
k -= this.x - j
48-
j = this.x
49-
} else if (k > this.x || j === 0) {
50-
j = k
51-
k -= j
52-
} else if (k === 0) {
53-
k = this.y
54-
}
55-
measurements = [j, k]
56-
moveCount++
57-
prBool = !prBool
23+
public fill(): void {
24+
this.amount = this.size
25+
}
26+
public empty(): void {
27+
this.amount = 0
28+
}
29+
30+
public pourInto(other: Bucket): void {
31+
const quantity = Math.min(this.amount, other.available)
32+
this.amount -= quantity
33+
other.amount += quantity
34+
}
35+
}
36+
37+
const gcd: (a: number, b: number) => number = (a, b) =>
38+
b === 0 ? a : gcd(b, a % b)
39+
40+
export class TwoBucket {
41+
private readonly buckets: Bucket[]
42+
private readonly goal: number
43+
public goalBucket: string | undefined
44+
public otherBucket: number | undefined
45+
46+
constructor(size1: number, size2: number, goal: number, start: string) {
47+
this.goal = goal
48+
this.buckets = [new Bucket('one', size1), new Bucket('two', size2)]
49+
50+
if (start === 'two') {
51+
this.buckets.reverse()
5852
}
59-
return moveCount
6053
}
6154

62-
private smallFirst(
63-
measurements: number[],
64-
moveCount: number,
65-
prBool: boolean
66-
): number {
67-
let j = measurements[0],
68-
k = measurements[1]
69-
while (!this.reachedGoal(measurements)) {
70-
if (j === this.x && moveCount === 0) {
71-
j = 0
72-
k = this.x
73-
} else if (j === 0) {
74-
j = this.x
75-
} else if (j === this.x && k < this.y) {
76-
const tempK = k
77-
k + j > this.y ? (k = this.y) : (k = tempK + j)
78-
tempK + j > this.y ? (j -= this.y - tempK) : (j = 0)
79-
} else if (k === this.y) {
80-
k = 0
81-
} else if (k === 0 && j < this.x) {
82-
k = j
83-
j = 0
84-
}
85-
measurements = [j, k]
86-
moveCount++
87-
prBool = !prBool
55+
private get first(): Bucket {
56+
return this.buckets[0]
57+
}
58+
private get second(): Bucket {
59+
return this.buckets[1]
60+
}
61+
62+
private validate(): void {
63+
if (this.goal > Math.max(this.first.size, this.second.size)) {
64+
throw new Error('Goal is bigger than the largest bucket.')
65+
}
66+
67+
if (this.goal % gcd(this.first.size, this.second.size) !== 0) {
68+
throw new Error(
69+
'Goal must be a multiple of the GCD of the sizes of the two buckets.'
70+
)
8871
}
89-
return moveCount
9072
}
9173

92-
public moves(): number {
93-
let j = 0
94-
let k = 0 // j will be running val of bucket one, k = running val of bucket two
95-
this.starter === 'one' ? (j = this.x) : (k = this.y)
96-
const measurements = [j, k]
97-
let moveCount = 0
98-
const prBool = true // pour / receive boolean - need to pour or receive every other turn
99-
if (this.starter === 'one') {
100-
moveCount = this.smallFirst(measurements, moveCount, prBool)
101-
} else {
102-
moveCount = this.bigFirst(measurements, moveCount, prBool)
74+
private moves(): number {
75+
this.validate()
76+
77+
this.first.empty()
78+
this.second.empty()
79+
let moves = 0
80+
81+
// fill the start bucket with the first move
82+
this.first.fill()
83+
moves += 1
84+
85+
// optimization: if the other bucket is the right size,
86+
// fill it immediately with the second move
87+
if (this.second.size === this.goal) {
88+
this.second.fill()
89+
moves += 1
90+
}
91+
92+
/* eslint-disable-next-line no-constant-condition */
93+
while (true) {
94+
if (this.first.amount === this.goal) {
95+
this.goalBucket = this.first.name
96+
this.otherBucket = this.second.amount
97+
return moves
98+
}
99+
100+
if (this.second.amount === this.goal) {
101+
this.goalBucket = this.second.name
102+
this.otherBucket = this.first.amount
103+
return moves
104+
}
105+
106+
if (this.first.isEmpty) {
107+
this.first.fill()
108+
} else if (this.second.isFull) {
109+
this.second.empty()
110+
} else {
111+
this.first.pourInto(this.second)
112+
}
113+
114+
moves += 1
103115
}
104-
return moveCount + 1 // accounts for first move made before loop (and moveCount starts at zero before loop)
105116
}
106117
}

exercises/practice/two-bucket/.meta/tests.toml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
# This is an auto-generated file. Regular comments will be removed when this
2-
# file is regenerated. Regenerating will not touch any manually added keys,
3-
# so comments can be added in a "comment" key.
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.
411

512
[a6f2b4ba-065f-4dca-b6f0-e3eee51cb661]
613
description = "Measure using bucket one of size 3 and bucket two of size 5 - start with bucket one"
@@ -19,3 +26,12 @@ description = "Measure one step using bucket one of size 1 and bucket two of siz
1926

2027
[eb329c63-5540-4735-b30b-97f7f4df0f84]
2128
description = "Measure using bucket one of size 2 and bucket two of size 3 - start with bucket one and end with bucket two"
29+
30+
[449be72d-b10a-4f4b-a959-ca741e333b72]
31+
description = "Not possible to reach the goal"
32+
33+
[aac38b7a-77f4-4d62-9b91-8846d533b054]
34+
description = "With the same buckets but a different goal, then it is possible"
35+
36+
[74633132-0ccf-49de-8450-af4ab2e3b299]
37+
description = "Goal larger than both buckets is impossible"

exercises/practice/two-bucket/two-bucket.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe('TwoBucket', () => {
5959
})
6060

6161
describe('Measure using bucket one of size 2 and bucket two of size 3', () => {
62-
it.skip('start with bucket one and end with bucket two', () => {
62+
xit('start with bucket one and end with bucket two', () => {
6363
const twoBucket = new TwoBucket(2, 3, 3, 'one')
6464
expect(twoBucket.moves()).toEqual(2)
6565
expect(twoBucket.goalBucket).toEqual('two')
@@ -72,7 +72,7 @@ describe('TwoBucket', () => {
7272
const buckTwo = 15
7373
const starterBuck = 'one'
7474

75-
it.skip('Not possible to reach the goal', () => {
75+
xit('Not possible to reach the goal', () => {
7676
const goal = 5
7777
const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck)
7878
expect(() => twoBucket.moves()).toThrow()
@@ -88,7 +88,7 @@ describe('TwoBucket', () => {
8888
})
8989

9090
describe('Goal larger than both buckets', () => {
91-
it.skip('Is impossible', () => {
91+
xit('Is impossible', () => {
9292
const twoBucket = new TwoBucket(5, 7, 8, 'one')
9393
expect(() => twoBucket.moves()).toThrow()
9494
})

0 commit comments

Comments
 (0)