From bc40ba1d09d6c9c2de16b4299b63160a74d57085 Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 19:30:21 +0100 Subject: [PATCH 01/14] Step1: Write a failing test --- test-pnpm/morning_routine/README.md | 0 test-pnpm/song_birds/README.md | 41 +++++++++++++++++++ test-pnpm/song_birds/song.bird.should.test.ts | 9 ++++ 3 files changed, 50 insertions(+) delete mode 100644 test-pnpm/morning_routine/README.md create mode 100644 test-pnpm/song_birds/README.md create mode 100644 test-pnpm/song_birds/song.bird.should.test.ts diff --git a/test-pnpm/morning_routine/README.md b/test-pnpm/morning_routine/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/test-pnpm/song_birds/README.md b/test-pnpm/song_birds/README.md new file mode 100644 index 0000000..7d120b7 --- /dev/null +++ b/test-pnpm/song_birds/README.md @@ -0,0 +1,41 @@ +Inspired by the creative works of Gawain Hewitt: https://gawainhewitt.co.uk/ + +There's a spot in the forest where songbirds like to sing together. This spot has three very nice branches, which the birds like to perch on, but each branch can only hold 1 bird. + +When a bird is on a branch it sings a song, depending on which branch it’s on, it will sing a different song. + +If the bird is joined by other birds they sing together, and the song changes. There is a different song sung depending on how many birds there are, and which branches the other birds are on. No song is sung when there are no birds. + +No individual song is the same in different combinations of how many birds there are, and what branches each are on. + +Given branches 1, 2, and 3, and birds A, B, and C, can you figure out how many different songs can be sung by the birds, where birds can be on any branch, or not on a branch at all. + +Using TDD, create a solution to figure out how many different songs can be sung using three birds and three branches. + +Examples: + +- If there are no birds, no song is sung +- You could have bird 1 on branch A, with branches B and C empty, that is one song +- You could have bird 1 on branch C, bird 2 on branch A, and branch B empty, that is one song +- You could have birds 1, 2, and 3 on branches A, B, and C respectively, that is one song +- You could have birds 1, 2, and 3 on branches C, A, B in that respective order, that is one song + +Tip: build up using incremental TDD steps. + +Advanced: + +\- what happens if the amount of branches could be configurable? + +\- what happens if the amount of birds is configurable? + +i.e. 3 branches and 4 birds, 4 branches and 2 birds, 5 branches and 5 birds + +### Summary of steps + +| Step | Test Description | Drives... | +| ---- | ---------------------------- | ----------------------------------- | +| 1 | Empty bird list returns 0 | Base case | +| 2 | One bird returns 3 | Static logic | +| 3 | Two birds returns 18 | First generalised combination logic | +| 4 | Three birds returns 36 | Full generalisation | +| 5 | Total (1-3 birds) returns 57 | Safety regression | diff --git a/test-pnpm/song_birds/song.bird.should.test.ts b/test-pnpm/song_birds/song.bird.should.test.ts new file mode 100644 index 0000000..5d79853 --- /dev/null +++ b/test-pnpm/song_birds/song.bird.should.test.ts @@ -0,0 +1,9 @@ +import { countSingingSongs } from './song.birds'; + +describe('song bird should', () => { + test('sing no songs when there are no birds', () => { + const actual = countSingingSongs([]); + + expect(actual).toBe(0); + }); +}); From ff11e20e88988984a689296a620a689ce3e9cba5 Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 19:35:04 +0100 Subject: [PATCH 02/14] Step2: Make the test pass --- test-pnpm/song_birds/song.birds.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 test-pnpm/song_birds/song.birds.ts diff --git a/test-pnpm/song_birds/song.birds.ts b/test-pnpm/song_birds/song.birds.ts new file mode 100644 index 0000000..aad98f7 --- /dev/null +++ b/test-pnpm/song_birds/song.birds.ts @@ -0,0 +1,3 @@ +export function countSingingSongs(birds: Array): number { + if (!birds || birds.length === 0) return 0; +} From 539c280214083042109d7384389e6689472a937c Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 19:37:40 +0100 Subject: [PATCH 03/14] Step2: Write failing test --- test-pnpm/song_birds/song.bird.should.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test-pnpm/song_birds/song.bird.should.test.ts b/test-pnpm/song_birds/song.bird.should.test.ts index 5d79853..d2eb030 100644 --- a/test-pnpm/song_birds/song.bird.should.test.ts +++ b/test-pnpm/song_birds/song.bird.should.test.ts @@ -6,4 +6,11 @@ describe('song bird should', () => { expect(actual).toBe(0); }); + + it('returns 3 if only one bird is singing', () => { + const actual = countSingingSongs(['A']); + + // One bird can perch on 3 branches + expect(actual).toBe(3); + }); }); From feb72c629a40ce89fb5a520041ed84313a150fb1 Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 19:40:00 +0100 Subject: [PATCH 04/14] Step2: Make the test pass --- test-pnpm/song_birds/song.birds.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test-pnpm/song_birds/song.birds.ts b/test-pnpm/song_birds/song.birds.ts index aad98f7..a121068 100644 --- a/test-pnpm/song_birds/song.birds.ts +++ b/test-pnpm/song_birds/song.birds.ts @@ -1,3 +1,4 @@ export function countSingingSongs(birds: Array): number { if (!birds || birds.length === 0) return 0; + if (birds.length === 1) return 3; } From bae1bbbacfa4b07e0145f22722b21c19f1182b79 Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 19:43:47 +0100 Subject: [PATCH 05/14] Step3: Write failing test --- test-pnpm/song_birds/song.bird.should.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test-pnpm/song_birds/song.bird.should.test.ts b/test-pnpm/song_birds/song.bird.should.test.ts index d2eb030..8f90f96 100644 --- a/test-pnpm/song_birds/song.bird.should.test.ts +++ b/test-pnpm/song_birds/song.bird.should.test.ts @@ -13,4 +13,11 @@ describe('song bird should', () => { // One bird can perch on 3 branches expect(actual).toBe(3); }); + + it('returns 18 if two birds are singing', () => { + const actual = countSingingSongs(['A', 'B']); + + // 3 branches, choose 2, assign 2 birds => 3 * 2! = 6 + expect(actual).toBe(18); + }); }); From af83f4c13747c1d8002b5c28bc7251baad00362f Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 19:49:02 +0100 Subject: [PATCH 06/14] Step3: Make the test just pass --- test-pnpm/song_birds/song.birds.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test-pnpm/song_birds/song.birds.ts b/test-pnpm/song_birds/song.birds.ts index a121068..c44f2c3 100644 --- a/test-pnpm/song_birds/song.birds.ts +++ b/test-pnpm/song_birds/song.birds.ts @@ -1,4 +1,5 @@ export function countSingingSongs(birds: Array): number { if (!birds || birds.length === 0) return 0; if (birds.length === 1) return 3; + if (birds.length === 2) return 18; } From 1e47040184a1e797b81a7bbea4269cd0082a3d0b Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 19:55:04 +0100 Subject: [PATCH 07/14] Step3: Make it pass --- test-pnpm/song_birds/song.bird.should.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test-pnpm/song_birds/song.bird.should.test.ts b/test-pnpm/song_birds/song.bird.should.test.ts index 8f90f96..8dc381d 100644 --- a/test-pnpm/song_birds/song.bird.should.test.ts +++ b/test-pnpm/song_birds/song.bird.should.test.ts @@ -20,4 +20,11 @@ describe('song bird should', () => { // 3 branches, choose 2, assign 2 birds => 3 * 2! = 6 expect(actual).toBe(18); }); + + it('returns 36 if all three birds are singing', () => { + const actual = countSingingSongs(['A', 'B', 'C']); + + // 1 way to choose all branches, 3! assignments + expect(actual).toBe(36); + }); }); From 0d1aeb46d22f2757c5e976b1c02c315228d92ace Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 19:55:46 +0100 Subject: [PATCH 08/14] Step3: Make it pass --- test-pnpm/song_birds/song.birds.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test-pnpm/song_birds/song.birds.ts b/test-pnpm/song_birds/song.birds.ts index c44f2c3..b02b1b2 100644 --- a/test-pnpm/song_birds/song.birds.ts +++ b/test-pnpm/song_birds/song.birds.ts @@ -2,4 +2,5 @@ export function countSingingSongs(birds: Array): number { if (!birds || birds.length === 0) return 0; if (birds.length === 1) return 3; if (birds.length === 2) return 18; + if (birds.length === 3) return 36; } From e920aa1f3e5b5ea9b44230428d953318baa4c0a9 Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 20:13:59 +0100 Subject: [PATCH 09/14] Refactor and utilising a factorial --- test-pnpm/song_birds/README.md | 12 ++++++++++++ test-pnpm/song_birds/song.birds.ts | 18 +++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/test-pnpm/song_birds/README.md b/test-pnpm/song_birds/README.md index 7d120b7..62a4ffb 100644 --- a/test-pnpm/song_birds/README.md +++ b/test-pnpm/song_birds/README.md @@ -39,3 +39,15 @@ i.e. 3 branches and 4 birds, 4 branches and 2 birds, 5 branches and 5 birds | 3 | Two birds returns 18 | First generalised combination logic | | 4 | Three birds returns 36 | Full generalisation | | 5 | Total (1-3 birds) returns 57 | Safety regression | + +### Helper which I needed + +My math needed more math - https://en.wikipedia.org/wiki/Factorial inorder to get a helper function half way through this articleor using [recursion](https://en.wikipedia.org/wiki/Recursion_(computer_science))[[78\]](https://en.wikipedia.org/wiki/Factorial#cite_note-78) based on its recurrence relation as + +``` +define factorial(n): + if (n = 0) return 1 + return n * factorial(n − 1) +``` + +So the refactoring step was tricky for me because I don't do enough math, making this tricky but interesting diff --git a/test-pnpm/song_birds/song.birds.ts b/test-pnpm/song_birds/song.birds.ts index b02b1b2..0dd77ab 100644 --- a/test-pnpm/song_birds/song.birds.ts +++ b/test-pnpm/song_birds/song.birds.ts @@ -1,6 +1,22 @@ export function countSingingSongs(birds: Array): number { if (!birds || birds.length === 0) return 0; if (birds.length === 1) return 3; - if (birds.length === 2) return 18; + if (birds.length === 2) { + const birdCombos = [ + ['A', 'B'], + ['A', 'C'], + ['B', 'C'], + ]; + const branchCombos = [ + ['1', '2'], + ['1', '3'], + ['2', '3'], + ]; + return birdCombos.length * branchCombos.length * factorial(2); + } if (birds.length === 3) return 36; } + +function factorial(n: number): number { + return n <= 1 ? 1 : n * factorial(n - 1); +} From 577ed142aecdd92dcd4bc673192de4dee05a1a72 Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 20:20:08 +0100 Subject: [PATCH 10/14] Refactor the simple step to make sure the pattern works --- test-pnpm/song_birds/song.birds.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test-pnpm/song_birds/song.birds.ts b/test-pnpm/song_birds/song.birds.ts index 0dd77ab..f509ae3 100644 --- a/test-pnpm/song_birds/song.birds.ts +++ b/test-pnpm/song_birds/song.birds.ts @@ -1,6 +1,14 @@ export function countSingingSongs(birds: Array): number { if (!birds || birds.length === 0) return 0; - if (birds.length === 1) return 3; + if (birds.length === 1) { + const birdCombos = [['A']]; + const branchCombos = [ + ['1', '2'], + ['1', '3'], + ['2', '3'], + ]; + return birdCombos.length * branchCombos.length * factorial(1); + } if (birds.length === 2) { const birdCombos = [ ['A', 'B'], From 4ad55bc2b86fce66937f992e20e03670e75ca513 Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 20:32:27 +0100 Subject: [PATCH 11/14] Visualised to the max --- test-pnpm/song_birds/song.birds.ts | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/test-pnpm/song_birds/song.birds.ts b/test-pnpm/song_birds/song.birds.ts index f509ae3..40a7b1d 100644 --- a/test-pnpm/song_birds/song.birds.ts +++ b/test-pnpm/song_birds/song.birds.ts @@ -1,13 +1,9 @@ export function countSingingSongs(birds: Array): number { if (!birds || birds.length === 0) return 0; if (birds.length === 1) { - const birdCombos = [['A']]; - const branchCombos = [ - ['1', '2'], - ['1', '3'], - ['2', '3'], - ]; - return birdCombos.length * branchCombos.length * factorial(1); + const birdCombos = [['A']]; // 1 bird + const branchCombos = [['1'], ['2'], ['3']]; // each possible branch + return birdCombos.length * branchCombos.length; // 1 * 3 = 3 } if (birds.length === 2) { const birdCombos = [ @@ -22,7 +18,25 @@ export function countSingingSongs(birds: Array): number { ]; return birdCombos.length * branchCombos.length * factorial(2); } - if (birds.length === 3) return 36; + if (birds.length === 3) { + const permutationsOfBirds = [ + ['A', 'B', 'C'], + ['A', 'C', 'B'], + ['B', 'A', 'C'], + ['B', 'C', 'A'], + ['C', 'A', 'B'], + ['C', 'B', 'A'], + ]; + const permutationsOfBranches = [ + ['1', '2', '3'], + ['1', '3', '2'], + ['2', '1', '3'], + ['2', '3', '1'], + ['3', '1', '2'], + ['3', '2', '1'], + ]; + return permutationsOfBirds.length * permutationsOfBranches.length; + } } function factorial(n: number): number { From 9ce9a9bfac36211644cc87c224f2735ee3235b8f Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 20:36:34 +0100 Subject: [PATCH 12/14] Document a phase I will lose --- test-pnpm/song_birds/README.md | 54 +++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/test-pnpm/song_birds/README.md b/test-pnpm/song_birds/README.md index 62a4ffb..4998f52 100644 --- a/test-pnpm/song_birds/README.md +++ b/test-pnpm/song_birds/README.md @@ -1,4 +1,4 @@ -Inspired by the creative works of Gawain Hewitt: https://gawainhewitt.co.uk/ +Inspired by the creative works of **Gawain Hewitt**: https://gawainhewitt.co.uk/ There's a spot in the forest where songbirds like to sing together. This spot has three very nice branches, which the birds like to perch on, but each branch can only hold 1 bird. @@ -51,3 +51,55 @@ define factorial(n): ``` So the refactoring step was tricky for me because I don't do enough math, making this tricky but interesting + +### Conclusion + +Thanks Gawain, my math was my weakness. This was fantstic for working on my weakness. I really enjoyed it but felt stuck several times. + +```javascript +export function countSingingSongs(birds: Array): number { + if (!birds || birds.length === 0) return 0; + if (birds.length === 1) { + const birdCombos = [['A']]; // 1 bird + const branchCombos = [['1'], ['2'], ['3']]; // each possible branch + return birdCombos.length * branchCombos.length; // 1 * 3 = 3 + } + if (birds.length === 2) { + const birdCombos = [ + ['A', 'B'], + ['A', 'C'], + ['B', 'C'], + ]; + const branchCombos = [ + ['1', '2'], + ['1', '3'], + ['2', '3'], + ]; + return birdCombos.length * branchCombos.length * factorial(2); + } + if (birds.length === 3) { + const permutationsOfBirds = [ + ['A', 'B', 'C'], + ['A', 'C', 'B'], + ['B', 'A', 'C'], + ['B', 'C', 'A'], + ['C', 'A', 'B'], + ['C', 'B', 'A'], + ]; + const permutationsOfBranches = [ + ['1', '2', '3'], + ['1', '3', '2'], + ['2', '1', '3'], + ['2', '3', '1'], + ['3', '1', '2'], + ['3', '2', '1'], + ]; + return permutationsOfBirds.length * permutationsOfBranches.length; + } +} + +function factorial(n: number): number { + return n <= 1 ? 1 : n * factorial(n - 1); +} +``` + From 716f283cb90334991dcab420e7a2140d65cf6f92 Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 20:56:27 +0100 Subject: [PATCH 13/14] Add the last test of everything --- test-pnpm/song_birds/song.bird.should.test.ts | 16 +++++++++++----- test-pnpm/song_birds/song.birds.ts | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/test-pnpm/song_birds/song.bird.should.test.ts b/test-pnpm/song_birds/song.bird.should.test.ts index 8dc381d..d0a48c0 100644 --- a/test-pnpm/song_birds/song.bird.should.test.ts +++ b/test-pnpm/song_birds/song.bird.should.test.ts @@ -1,30 +1,36 @@ -import { countSingingSongs } from './song.birds'; +import { countUniqueSongs } from './song.birds'; describe('song bird should', () => { test('sing no songs when there are no birds', () => { - const actual = countSingingSongs([]); + const actual = countUniqueSongs([]); expect(actual).toBe(0); }); it('returns 3 if only one bird is singing', () => { - const actual = countSingingSongs(['A']); + const actual = countUniqueSongs(['A']); // One bird can perch on 3 branches expect(actual).toBe(3); }); it('returns 18 if two birds are singing', () => { - const actual = countSingingSongs(['A', 'B']); + const actual = countUniqueSongs(['A', 'B']); // 3 branches, choose 2, assign 2 birds => 3 * 2! = 6 expect(actual).toBe(18); }); it('returns 36 if all three birds are singing', () => { - const actual = countSingingSongs(['A', 'B', 'C']); + const actual = countUniqueSongs(['A', 'B', 'C']); // 1 way to choose all branches, 3! assignments expect(actual).toBe(36); }); + + it('returns total combinations for 1 to 3 birds', () => { + const total = + countUniqueSongs(['A']) + countUniqueSongs(['A', 'B']) + countUniqueSongs(['A', 'B', 'C']); + expect(total).toBe(57); + }); }); diff --git a/test-pnpm/song_birds/song.birds.ts b/test-pnpm/song_birds/song.birds.ts index 40a7b1d..1d42ea6 100644 --- a/test-pnpm/song_birds/song.birds.ts +++ b/test-pnpm/song_birds/song.birds.ts @@ -1,4 +1,4 @@ -export function countSingingSongs(birds: Array): number { +export function countUniqueSongs(birds: Array): number { if (!birds || birds.length === 0) return 0; if (birds.length === 1) { const birdCombos = [['A']]; // 1 bird From 2e33b506b7b2d4df19c32facd0e7d173d65cecad Mon Sep 17 00:00:00 2001 From: Vincent Farah Date: Thu, 1 May 2025 21:14:54 +0100 Subject: [PATCH 14/14] Its a wrap --- test-pnpm/song_birds/README.md | 6 ++++ test-pnpm/song_birds/song.birds.ts | 57 +++++++++++------------------- 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/test-pnpm/song_birds/README.md b/test-pnpm/song_birds/README.md index 4998f52..0087fa4 100644 --- a/test-pnpm/song_birds/README.md +++ b/test-pnpm/song_birds/README.md @@ -103,3 +103,9 @@ function factorial(n: number): number { } ``` +1. **Strategy Pattern**: Used a strategy object (`songCalculator`) to map the number of birds to their respective calculation functions. +2. **Code Organisation**: Each calculation is encapsulated in its own function, making the code more readable. +3. **Explicit Constants**: Added the `BRANCH_COUNT` constant to make the code more maintainable. +4. **Removed Hardcoded Arrays**: Instead of hardcoding the arrays of combinations and permutations, the code now calculates the number of combinations and permutations directly. +5. **Added Documentation**: Each calculation includes comments explaining the logic behind it. +6. **Consistent Return**: The function always returns 0 for invalid bird counts (greater than 3 or less than 0). diff --git a/test-pnpm/song_birds/song.birds.ts b/test-pnpm/song_birds/song.birds.ts index 1d42ea6..b8ecbf7 100644 --- a/test-pnpm/song_birds/song.birds.ts +++ b/test-pnpm/song_birds/song.birds.ts @@ -1,42 +1,25 @@ export function countUniqueSongs(birds: Array): number { if (!birds || birds.length === 0) return 0; - if (birds.length === 1) { - const birdCombos = [['A']]; // 1 bird - const branchCombos = [['1'], ['2'], ['3']]; // each possible branch - return birdCombos.length * branchCombos.length; // 1 * 3 = 3 - } - if (birds.length === 2) { - const birdCombos = [ - ['A', 'B'], - ['A', 'C'], - ['B', 'C'], - ]; - const branchCombos = [ - ['1', '2'], - ['1', '3'], - ['2', '3'], - ]; - return birdCombos.length * branchCombos.length * factorial(2); - } - if (birds.length === 3) { - const permutationsOfBirds = [ - ['A', 'B', 'C'], - ['A', 'C', 'B'], - ['B', 'A', 'C'], - ['B', 'C', 'A'], - ['C', 'A', 'B'], - ['C', 'B', 'A'], - ]; - const permutationsOfBranches = [ - ['1', '2', '3'], - ['1', '3', '2'], - ['2', '1', '3'], - ['2', '3', '1'], - ['3', '1', '2'], - ['3', '2', '1'], - ]; - return permutationsOfBirds.length * permutationsOfBranches.length; - } + + const BRANCH_COUNT = 3; + const birdCount = birds.length; + + const songCalculator = { + 1: () => BRANCH_COUNT, // One bird can perch on any of the 3 branches + 2: () => { + const birdCombinations = 3; // Number of ways to choose 2 birds from the set + const branchCombinations = 3; // Number of ways to choose 2 branches from 3 + const birdPermutations = factorial(2); // Number of ways to arrange 2 birds + return birdCombinations * branchCombinations * birdPermutations; // 3 * 3 * 2 = 18 + }, + 3: () => { + const birdPermutations = factorial(3); // Number of ways to arrange 3 birds + const branchPermutations = factorial(3); // Number of ways to arrange 3 branches + return birdPermutations * branchPermutations; // 6 * 6 = 36 + }, + }; + + return songCalculator[birdCount] ? songCalculator[birdCount]() : 0; } function factorial(n: number): number {