-
-
Notifications
You must be signed in to change notification settings - Fork 185
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
website/blog/2024-12-24-advent-of-pbt-day-24/AdventOfTheDay.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import adventBuggy from './buggy.mjs'; | ||
import { buildAdventOfTheDay } from '../2024-12-01-advent-of-pbt-day-1/AdventOfTheDayBuilder'; | ||
|
||
const { AdventPlaygroundOfTheDay, FormOfTheDay } = buildAdventOfTheDay({ | ||
day: 24, | ||
buildBuggyAdvent: adventBuggy, | ||
referenceAdvent: () => true, | ||
buggyAdventSurcharged: (...args: Parameters<ReturnType<typeof adventBuggy>>) => { | ||
const expected = distributeCoins(...args); | ||
const out = adventBuggy()(...args); | ||
const [availableCoins, amountsToBePaid] = args; | ||
if (out === null) { | ||
return expected === null ? true : 'not supposed to find anything'; | ||
} | ||
for (let index = 0; index !== amountsToBePaid.length; ++index) { | ||
if (out[index].reduce((acc, v) => acc + v, 0) !== amountsToBePaid[index]) { | ||
return 'bad amount'; | ||
} | ||
} | ||
const coins = [...availableCoins]; | ||
for (const coinsForPayslip of out) { | ||
for (const coinValue of coinsForPayslip) { | ||
const index = coins.indexOf(coinValue); | ||
if (index === -1) { | ||
return 'no such coin'; | ||
} | ||
coins.splice(index, 1); | ||
} | ||
} | ||
return true; | ||
}, | ||
parser, | ||
placeholderForm: '7,5,8?\n1,2,2,4,5,7,10', | ||
functionName: 'distributeCoins', | ||
signature: 'distributeCoins(availableCoins: Coin[], amountsToBePaid: number[]): Coin[][] | null;', | ||
signatureExtras: ['type Coin = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;'], | ||
}); | ||
|
||
export { AdventPlaygroundOfTheDay, FormOfTheDay }; | ||
|
||
// Reference implementation | ||
|
||
function distributeCoins(availableCoins, payslips) { | ||
const coins = [...availableCoins].sort((a, b) => b - a); | ||
|
||
function helper(targets, indexInTarget, nextCoins = coins) { | ||
if (indexInTarget >= targets.length) { | ||
return []; | ||
} | ||
if (targets[indexInTarget] === 0) { | ||
const withCurrent = helper(targets, indexInTarget + 1); | ||
if (withCurrent === null) { | ||
return null; | ||
} | ||
return [[], ...withCurrent]; | ||
} | ||
if (targets[indexInTarget] < 0 || nextCoins.length === 0) { | ||
return null; | ||
} | ||
const subNextCoins = nextCoins.slice(1); | ||
const newTargets = targets.slice(); | ||
newTargets[indexInTarget] -= nextCoins[0]; | ||
const withCurrent = helper(newTargets, indexInTarget, subNextCoins); | ||
if (withCurrent !== null) { | ||
return [[nextCoins[0], ...withCurrent[0]], ...withCurrent.slice(1)]; | ||
} | ||
const withoutCurrent = helper(targets, indexInTarget, subNextCoins); | ||
return withoutCurrent; | ||
} | ||
return helper(payslips, 0); | ||
} | ||
|
||
// Inputs parser | ||
|
||
function parser(answer: string): unknown[] | undefined { | ||
const lines = answer.split('\n'); | ||
if (lines.length !== 2) { | ||
throw new Error( | ||
'Expected to receive two lines one for the amounts (payslips) to be paid, another one for the coins', | ||
); | ||
} | ||
if (lines[0].at(-1) !== '?') { | ||
throw new Error(`First line must end by ?, got: ${lines[0]}.`); | ||
} | ||
const payslips: number[] = | ||
lines[0] === '?' | ||
? [] | ||
: lines[0] | ||
.slice(0, -1) | ||
.split(',') | ||
.map((v) => { | ||
const amount = Number(v); | ||
if (!Number.isInteger(amount) || amount < 0 || amount > 2 ** 31 - 1) { | ||
throw new Error( | ||
`Invalid payslip value received, must be a positive integer value below 2**31-1, got: ${v}.`, | ||
); | ||
} | ||
return amount; | ||
}); | ||
const coins: number[] = | ||
lines[1] === '' | ||
? [] | ||
: lines[1].split(',').map((v) => { | ||
const n = Number(v); | ||
if (!Number.isInteger(n) || n < 1 || n > 10) { | ||
throw new Error(`Invalid coin value received, got: ${v}.`); | ||
} | ||
return n; | ||
}); | ||
return [coins, payslips]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// @ts-check | ||
|
||
export default function advent() { | ||
/** @typedef {1|2|3|4|5|6|7|8|9|10} Coin */ | ||
|
||
/** | ||
* @param {Coin[]} availableCoins | ||
* @param {number[]} amountsToBePaid | ||
* @returns {Coin[][] | null} | ||
*/ | ||
return function distributeCoins(availableCoins, amountsToBePaid) { | ||
function payslipContentFor(availableCoins, amountToBePaid) { | ||
const coins = [...availableCoins].sort((a, b) => b - a); | ||
function helper(target, index) { | ||
if (target === 0) { | ||
return []; | ||
} | ||
if (target < 0 || index >= coins.length) { | ||
return null; | ||
} | ||
const withCurrent = helper(target - coins[index], index + 1); | ||
if (withCurrent !== null) { | ||
return [coins[index], ...withCurrent]; | ||
} | ||
const withoutCurrent = helper(target, index + 1); | ||
return withoutCurrent; | ||
} | ||
return helper(amountToBePaid, 0); | ||
} | ||
|
||
const remainingCoins = [...availableCoins]; | ||
const coinsForPayslips = []; | ||
const orderedAmountsToBePaid = amountsToBePaid | ||
.map((amount, index) => ({ amount, index })) | ||
.sort((a, b) => a.amount - b.amount); | ||
for (const { index, amount } of orderedAmountsToBePaid) { | ||
const dedicatedCoins = payslipContentFor(remainingCoins, amount); | ||
if (dedicatedCoins === null) { | ||
return null; | ||
} | ||
for (const coin of dedicatedCoins) { | ||
remainingCoins.splice(remainingCoins.indexOf(coin), 1); | ||
} | ||
coinsForPayslips[index] = dedicatedCoins; | ||
} | ||
return coinsForPayslips; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
--- | ||
title: Advent of PBT 2024 · Day 24 | ||
authors: [dubzzz] | ||
tags: [advent-of-pbt, advent-of-pbt-2024] | ||
image: ./social.png | ||
--- | ||
|
||
import {AdventPlaygroundOfTheDay,FormOfTheDay} from './AdventOfTheDay'; | ||
import BlueskyComments from '../2024-12-01-advent-of-pbt-day-1/BlueskyComments'; | ||
|
||
Christmas is at risk! In their rush to meet tight deadlines, Santa’s elves accidentally introduced bugs into critical algorithms. If these issues aren’t discovered in time, Christmas could be delayed for everyone worldwide! | ||
|
||
Your mission is to troubleshoot these black-box algorithms using the power of fast-check. | ||
|
||
The clock is ticking! Emma just reached out with a new challenge: Santa’s coin distribution strategy for multiple elves might leave some unpaid. Can you identify any flaws in the algorithm and ensure every elf gets their fair share? 🎄✨ | ||
|
||
<!--truncate--> | ||
|
||
## Money Day: The Revenge | ||
|
||
Emma’s algorithm was a big hit! The elves were thrilled to learn about it. But they noticed something troubling: while her algorithm works wonders for a single elf’s payslip, it doesn’t account for the big picture. | ||
|
||
Here’s the problem: | ||
|
||
> Santa has to pay all the elves at once, and the coins he has available are limited. While there might be multiple ways to pay one elf, some of those choices can make it impossible to pay another elf later. | ||
So elves created a more sophisticated algorithm that handles multiple payslips simultaneously. The algorithm specification is the following: | ||
|
||
> **Input:** | ||
> | ||
> - coins: A list of available coins (e.g., `[1, 2, 2, 4, 5, 7, 10]`). | ||
> - payslips: A list of amounts Santa must pay to each elf (e.g., `[7, 5, 8]`). | ||
> | ||
> **Output:** | ||
> | ||
> An array of arrays, where each inner array contains the coins used to fulfill a payslip (e.g., `[[7], [5], [2, 2, 4]]`). | ||
> Return null if it’s impossible to fulfill all payslips with the given coins. | ||
> | ||
> When returned the array should be in the same ordered as the received payslips. | ||
## Hands On | ||
|
||
Emma just implemented this new algorithm, but she’s worried it might not work perfectly for all edge cases. She’s asking for your help to test it thoroughly using property-based testing. | ||
|
||
Can you uncover any bugs and help Emma ensure that every elf gets paid fairly and efficiently this year? | ||
|
||
Remember: Elf morale for next year is on the line. 🎄✨ | ||
|
||
<AdventPlaygroundOfTheDay /> | ||
|
||
## Your answer | ||
|
||
<FormOfTheDay /> | ||
|
||
## Comments | ||
|
||
<BlueskyComments url="" /> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.