Skip to content

Commit

Permalink
📝 Advent of PBT, Day 24 (#5558)
Browse files Browse the repository at this point in the history
  • Loading branch information
dubzzz authored Dec 24, 2024
1 parent 049833d commit 8a44d16
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 0 deletions.
111 changes: 111 additions & 0 deletions website/blog/2024-12-24-advent-of-pbt-day-24/AdventOfTheDay.tsx
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];
}
48 changes: 48 additions & 0 deletions website/blog/2024-12-24-advent-of-pbt-day-24/buggy.mjs
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;
};
}
57 changes: 57 additions & 0 deletions website/blog/2024-12-24-advent-of-pbt-day-24/index.md
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="" />
3 changes: 3 additions & 0 deletions website/blog/2024-12-24-advent-of-pbt-day-24/social.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 8a44d16

Please sign in to comment.