diff --git a/website/blog/2024-12-24-advent-of-pbt-day-24/AdventOfTheDay.tsx b/website/blog/2024-12-24-advent-of-pbt-day-24/AdventOfTheDay.tsx new file mode 100644 index 00000000000..3d03b8e6620 --- /dev/null +++ b/website/blog/2024-12-24-advent-of-pbt-day-24/AdventOfTheDay.tsx @@ -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>) => { + 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]; +} diff --git a/website/blog/2024-12-24-advent-of-pbt-day-24/buggy.mjs b/website/blog/2024-12-24-advent-of-pbt-day-24/buggy.mjs new file mode 100644 index 00000000000..7ade936b245 --- /dev/null +++ b/website/blog/2024-12-24-advent-of-pbt-day-24/buggy.mjs @@ -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; + }; +} diff --git a/website/blog/2024-12-24-advent-of-pbt-day-24/index.md b/website/blog/2024-12-24-advent-of-pbt-day-24/index.md new file mode 100644 index 00000000000..ef0ab485045 --- /dev/null +++ b/website/blog/2024-12-24-advent-of-pbt-day-24/index.md @@ -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? 🎄✨ + + + +## 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. 🎄✨ + + + +## Your answer + + + +## Comments + + diff --git a/website/blog/2024-12-24-advent-of-pbt-day-24/social.png b/website/blog/2024-12-24-advent-of-pbt-day-24/social.png new file mode 100644 index 00000000000..e2bf71946b3 --- /dev/null +++ b/website/blog/2024-12-24-advent-of-pbt-day-24/social.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b8a4a38f96d76f02bdca723f6537e91292f35e41a608c1d157daeb889a6528c +size 251126