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..0087fa4 --- /dev/null +++ b/test-pnpm/song_birds/README.md @@ -0,0 +1,111 @@ +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 | + +### 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 + +### 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); +} +``` + +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.bird.should.test.ts b/test-pnpm/song_birds/song.bird.should.test.ts new file mode 100644 index 0000000..d0a48c0 --- /dev/null +++ b/test-pnpm/song_birds/song.bird.should.test.ts @@ -0,0 +1,36 @@ +import { countUniqueSongs } from './song.birds'; + +describe('song bird should', () => { + test('sing no songs when there are no birds', () => { + const actual = countUniqueSongs([]); + + expect(actual).toBe(0); + }); + + it('returns 3 if only one bird is singing', () => { + 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 = 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 = 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 new file mode 100644 index 0000000..b8ecbf7 --- /dev/null +++ b/test-pnpm/song_birds/song.birds.ts @@ -0,0 +1,27 @@ +export function countUniqueSongs(birds: Array): number { + if (!birds || birds.length === 0) return 0; + + 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 { + return n <= 1 ? 1 : n * factorial(n - 1); +}