2023 - Day 7

This commit is contained in:
2024-07-29 00:09:03 +02:00
parent 613492811d
commit 15fabe9d72
6 changed files with 1854 additions and 0 deletions

57
2023/07/PART1.md Normal file
View File

@@ -0,0 +1,57 @@
--- Day 7: Camel Cards ---
Your all-expenses-paid trip turns out to be a one-way, five-minute ride in an airship. (At least it's a cool airship!) It drops you off at the edge of a vast desert and descends back to Island Island.
"Did you bring the parts?"
You turn around to see an Elf completely covered in white clothing, wearing goggles, and riding a large camel.
"Did you bring the parts?" she asks again, louder this time. You aren't sure what parts she's looking for; you're here to figure out why the sand stopped.
"The parts! For the sand, yes! Come with me; I will show you." She beckons you onto the camel.
After riding a bit across the sands of Desert Island, you can see what look like very large rocks covering half of the horizon. The Elf explains that the rocks are all along the part of Desert Island that is directly above Island Island, making it hard to even get there. Normally, they use big machines to move the rocks and filter the sand, but the machines have broken down because Desert Island recently stopped receiving the parts they need to fix the machines.
You've already assumed it'll be your job to figure out why the parts stopped when she asks if you can help. You agree automatically.
Because the journey will take a few days, she offers to teach you the game of Camel Cards. Camel Cards is sort of similar to poker except it's designed to be easier to play while riding a camel.
In Camel Cards, you get a list of hands, and your goal is to order them based on the strength of each hand. A hand consists of five cards labeled one of A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, or 2. The relative strength of each card follows this order, where A is the highest and 2 is the lowest.
Every hand is exactly one type. From strongest to weakest, they are:
- Five of a kind, where all five cards have the same label: AAAAA
- Four of a kind, where four cards have the same label and one card has a different label: AA8AA
- Full house, where three cards have the same label, and the remaining two cards share a different label: 23332
- Three of a kind, where three cards have the same label, and the remaining two cards are each different from any other card in the hand: TTT98
- Two pair, where two cards share one label, two other cards share a second label, and the remaining card has a third label: 23432
- One pair, where two cards share one label, and the other three cards have a different label from the pair and each other: A23A4
- High card, where all cards' labels are distinct: 23456
Hands are primarily ordered based on type; for example, every full house is stronger than any three of a kind.
If two hands have the same type, a second ordering rule takes effect. Start by comparing the first card in each hand. If these cards are different, the hand with the stronger first card is considered stronger. If the first card in each hand have the same label, however, then move on to considering the second card in each hand. If they differ, the hand with the higher second card wins; otherwise, continue with the third card in each hand, then the fourth, then the fifth.
So, 33332 and 2AAAA are both four of a kind hands, but 33332 is stronger because its first card is stronger. Similarly, 77888 and 77788 are both a full house, but 77888 is stronger because its third card is stronger (and both hands have the same first and second card).
To play Camel Cards, you are given a list of hands and their corresponding bid (your puzzle input). For example:
```
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
```
This example shows five hands; each hand is followed by its bid amount. Each hand wins an amount equal to its bid multiplied by its rank, where the weakest hand gets rank 1, the second-weakest hand gets rank 2, and so on up to the strongest hand. Because there are five hands in this example, the strongest hand will have rank 5 and its bid will be multiplied by 5.
So, the first step is to put the hands in order of strength:
- 32T3K is the only one pair and the other hands are all a stronger type, so it gets rank 1.
- KK677 and KTJJT are both two pair. Their first cards both have the same label, but the second card of KK677 is stronger (K vs T), so KTJJT gets rank 2 and KK677 gets rank 3.
- T55J5 and QQQJA are both three of a kind. QQQJA has a stronger first card, so it gets rank 5 and T55J5 gets rank 4.
Now, you can determine the total winnings of this set of hands by adding up the result of multiplying each hand's bid with its rank (765 * 1 + 220 * 2 + 28 * 3 + 684 * 4 + 483 * 5). So the total winnings in this example are 6440.
Find the rank of every hand in your set. What are the total winnings?

36
2023/07/PART2.md Normal file
View File

@@ -0,0 +1,36 @@
--- Part Two ---
To make things a little more interesting, the Elf introduces one additional
rule. Now, J cards are jokers - wildcards that can act like whatever card would
make the hand the strongest type possible.
To balance this, J cards are now the weakest individual cards, weaker even
than 2. The other cards stay in the same order: A, K, Q, T, 9, 8, 7, 6, 5, 4, 3,
2, J.
J cards can pretend to be whatever card is best for the purpose of determining
hand type; for example, QJJQ2 is now considered four of a kind. However, for the
purpose of breaking ties between two hands of the same type, J is always treated
as J, not the card it's pretending to be: JKKK2 is weaker than QQQQ2 because J
is weaker than Q.
Now, the above example goes very differently:
```
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
```
- 32T3K is still the only one pair; it doesn't contain any jokers, so its
strength doesn't increase.
- KK677 is now the only two pair, making it the second-weakest hand.
- T55J5, KTJJT, and QQQJA are now all four of a kind! T55J5 gets rank 3, QQQJA
gets rank 4, and KTJJT gets rank 5.
With the new joker rule, the total winnings in this example are 5905.
Using the new joker rule, find the rank of every hand in your set. What are the
new total winnings?

1000
2023/07/input.txt Normal file

File diff suppressed because it is too large Load Diff

5
2023/07/sample.txt Normal file
View File

@@ -0,0 +1,5 @@
32T3K 765
T55J5 684
KK676 28
KTJJT 220
QQQJA 483

334
2023/07/solution.bak.ts Normal file
View File

@@ -0,0 +1,334 @@
const sample = await Deno.readTextFile("sample.txt");
const input = await Deno.readTextFile("input.txt");
const cardsSolution1 = [
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"T",
"J",
"Q",
"K",
"A",
] as const;
const cardsSolution2 = [
"J",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"T",
"Q",
"K",
"A",
] as const;
type CardListSolution1 = typeof cardsSolution1;
type CardListSolution2 = typeof cardsSolution2;
type CardList = CardListSolution1 | CardListSolution2;
type Card = typeof cardsSolution1[number];
type Cards = string;
type Hand = [
[Card, number],
[Card, number],
[Card, number],
[Card, number],
[Card, number],
];
type GroupedHand = Partial<Record<Card, number>>;
type CardCounts = number[];
const getCardValue = (
cards: CardList,
card: Card,
): number => cards.indexOf(card) + 1;
const groupHand = (hand: Hand): GroupedHand =>
hand.reduce(
(acc, [card]) => ({ ...acc, [card]: (acc[card] || 0) + 1 }),
{} as GroupedHand,
);
const countSameCards = (
groupedHand: GroupedHand,
withJokers = false,
): CardCounts => {
if (!withJokers) return Object.values(groupedHand).sort((a, b) => b - a);
const groupedHandWithoutJokers = { ...groupedHand } satisfies GroupedHand;
delete groupedHandWithoutJokers.J;
return Object.values(groupedHandWithoutJokers).sort((a, b) => b - a);
};
class JokerCounter {
constructor(private jokers: number) {}
getMax() {
const jokers = Math.max(0, this.jokers);
this.jokers = this.jokers - jokers;
return jokers;
}
getDelta(max: number, current: number) {
const jokersNeeded = Math.abs(max - current);
const currentNumberOfJokers = this.jokers;
const hasEnoughJokers = currentNumberOfJokers >= jokersNeeded;
if (!hasEnoughJokers) {
this.jokers = 0;
return currentNumberOfJokers + current;
}
this.jokers = this.jokers - jokersNeeded;
return max;
}
get(count = 1) {
const jokers = Math.max(0, this.jokers - count);
this.jokers = this.jokers - jokers;
return jokers;
}
}
const handTypes = [
function fiveOfAKind(
cardCounts: CardCounts,
groupedHand: GroupedHand,
withJokers = false,
): boolean {
const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0);
return jokers.getDelta(5, cardCounts[0]) >= 5;
},
function fourOfAKind(
cardCounts: CardCounts,
groupedHand: GroupedHand,
withJokers = false,
): boolean {
const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0);
return jokers.getDelta(4, cardCounts[0]) >= 4;
},
function fullHouse(
cardCounts: CardCounts,
groupedHand: GroupedHand,
withJokers = false,
): boolean {
const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0);
return (jokers.getDelta(3, cardCounts[0]) === 3 &&
jokers.getDelta(2, cardCounts[1]) === 2);
},
function threeOfAKind(
cardCounts: CardCounts,
groupedHand: GroupedHand,
withJokers = false,
): boolean {
const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0);
return jokers.getDelta(3, cardCounts[0]) === 3 &&
jokers.getDelta(1, cardCounts[1]) === 1 &&
jokers.getDelta(1, cardCounts[2]) === 1;
},
function twoPair(
cardCounts: CardCounts,
groupedHand: GroupedHand,
withJokers = false,
): boolean {
const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0);
const numberOfPairs = cardCounts.filter((count) => count === 2).length;
if (numberOfPairs === 2) return true;
if (numberOfPairs === 1) return jokers.get(2) === 2;
return jokers.get(2) === 2 && jokers.get(2) === 2;
},
function onePair(
cardCounts: CardCounts,
groupedHand: GroupedHand,
withJokers = false,
): boolean {
const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0);
const hasOnePair = cardCounts.filter((count) => count === 2).length === 1;
if (hasOnePair) return true;
return jokers.get(2) === 2;
},
function highCard(
cardCounts: CardCounts,
groupedHand: GroupedHand,
withJokers = false,
): boolean {
const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0);
const length = cardCounts.length;
return jokers.getDelta(5, length) === 5;
},
];
const handTypeName = [
"fiveOfAKind",
"fourOfAKind",
"fullHouse",
"threeOfAKind",
"twoPair",
"onePair",
"highCard",
] as const;
const compareHands = (
cards: CardList,
) =>
(
[, hand1, , handType1]: [Cards, Hand, number, number],
[, hand2, , handType2]: [Cards, Hand, number, number],
): -1 | 0 | 1 => {
if (handType1 < handType2) {
return -1;
} else if (handType1 > handType2) {
return 1;
} else {
const hand1TotalValue = hand1.reduce(
(s, [card]) => s + getCardValue(cards, card),
0,
);
const hand2TotalValue = hand2.reduce(
(s, [card]) => s + getCardValue(cards, card),
0,
);
console.log(
"hand1",
handType1,
hand1,
hand1TotalValue,
);
console.log(
"hand2",
handType2,
hand2,
hand2TotalValue,
);
return hand1TotalValue > hand2TotalValue ? 1 : -1;
}
// for (let i = 0; i < 5; i += 1) {
// console.log(
// "hand1",
// handType1,
// hand1[i][0],
// getCardValue(cardsSolution1, hand1[i][0]),
// );
// console.log(
// "hand2",
// handType2,
// hand2[i][0],
// getCardValue(cardsSolution1, hand2[i][0]),
// );
// if (hand1[i] > hand2[i]) return 1;
// if (hand1[i] < hand2[i]) return -1;
// }
// return 0;
// }
};
const solvePart1 = (data: string): number => {
return data
.split("\n")
.filter(Boolean)
.map((line) => line.split(" "))
// Convert lines to hands and bids
.map((
[hand, bid],
): [Cards, Hand, number] => {
const [card1, card2, card3, card4, card5] = hand.toUpperCase().split("")
.map((
card,
): [Card, number] => [
card as Card,
getCardValue(cardsSolution1, card as Card),
]);
return [
hand,
[card1, card2, card3, card4, card5],
Number(bid),
];
// Determine hand type per hand
}).map((
[cards, hand, bid],
): [Cards, Hand, number, number] => [
cards,
hand,
bid,
handTypes.length -
(handTypes.findIndex((handType) => {
return handType(countSameCards(groupHand(hand)), groupHand(hand));
}) + 1),
])
// Sort the hands by hand type and card value
.sort(compareHands(cardsSolution1)).map((
result,
index,
): [Cards, Hand, number, number, number, number] => [
...result,
index + 1,
(index + 1) * result[2],
])
.reduce((s, [, , , , , v]) => s + v, 0);
};
console.log("Sample:", solvePart1(sample)); // 6440
console.log("Input", solvePart1(input)); // 251927063
const solvePart2 = (data: string): number => {
const r = data
.split("\n")
.filter(Boolean)
.map((line) => line.split(" "))
// Convert lines to hands and bids
.map((
[hand, bid],
): [Cards, Hand, number] => {
const [card1, card2, card3, card4, card5] = hand.toUpperCase().split("")
.map((
card,
): [Card, number] => [
card as Card,
getCardValue(cardsSolution2, card as Card),
]);
return [
hand,
[card1, card2, card3, card4, card5],
Number(bid),
];
// Determine hand type per hand
}).map((
[cards, hand, bid],
): [Cards, Hand, number, number] => [
cards,
hand,
bid,
handTypes.length -
(handTypes.findIndex((handType) => {
return handType(
countSameCards(groupHand(hand), true),
groupHand(hand),
true,
);
}) + 1),
])
// Sort the hands by hand type and card value
.sort(compareHands(cardsSolution2)).map((
result,
index,
): [Cards, Hand, number, number, number, number] => [
...result,
index + 1,
(index + 1) * result[2],
]);
console.log(
"r",
r,
);
return r.reduce((s, [, , , , , v]) => s + v, 0);
};
console.log("Sample:", solvePart2(sample));
// console.log("Input", solvePart2(input));

422
2023/07/solution.ts Normal file
View File

@@ -0,0 +1,422 @@
const sample = await Deno.readTextFile("sample.txt");
const input = await Deno.readTextFile("input.txt");
abstract class HandType {
abstract get isMatch(): boolean;
abstract get name(): string;
abstract get weight(): number;
}
class FiveOfAKind extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(5);
}
get weight() {
return 1;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "Five of a Kind";
}
}
class FourOfAKind extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(4, 1);
}
get weight() {
return 2;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "Four of a Kind";
}
}
class FullHouse extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(3, 2);
}
get weight() {
return 3;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "Full House";
}
}
class ThreeOfAKind extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(3, 1, 1);
}
get weight() {
return 4;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "Three of a Kind";
}
}
class TwoPair extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(2, 2, 1);
}
get weight() {
return 5;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "Two Pair";
}
}
class OnePair extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(2, 1, 1, 1);
}
get weight() {
return 6;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "One Pair";
}
}
class HighCard extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(1, 1, 1, 1, 1);
}
get weight() {
return 7;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "High Card";
}
}
class Card {
private _label: string;
private _weight: number;
private _isJoker: boolean;
constructor(
label: string,
cardsWeights: Readonly<string[]>,
includeJokers: boolean,
) {
this._label = label;
this._weight = cardsWeights.indexOf(label) + 1;
this._isJoker = includeJokers && label === "J";
}
get label() {
return this._label;
}
get weight() {
return this._weight;
}
get isJoker() {
return this._isJoker;
}
}
class Hand {
private _card1: Card;
private _card2: Card;
private _card3: Card;
private _card4: Card;
private _card5: Card;
private _handTypes: HandType[];
private _handType: HandType | undefined;
private _groupedHand: string[][];
constructor(
public hand: string,
cardsWeights: Readonly<string[]>,
includeJokers: boolean,
) {
this._card1 = new Card(this.hand[0], cardsWeights, includeJokers);
this._card2 = new Card(this.hand[1], cardsWeights, includeJokers);
this._card3 = new Card(this.hand[2], cardsWeights, includeJokers);
this._card4 = new Card(this.hand[3], cardsWeights, includeJokers);
this._card5 = new Card(this.hand[4], cardsWeights, includeJokers);
this._groupedHand = this.cards.reduce((acc, card): string[][] => {
if (acc.flatMap((entry) => entry).includes(card.label)) {
return acc.map((entry): string[] =>
entry[0] === card.label ? [...entry, entry[0]] : entry
);
}
if (includeJokers && card.isJoker) return acc;
return [...acc, [card.label]];
}, [] as string[][]).sort((a, b) => b.length - a.length);
this._handTypes = [
new FiveOfAKind(this),
new FourOfAKind(this),
new FullHouse(this),
new ThreeOfAKind(this),
new TwoPair(this),
new OnePair(this),
new HighCard(this),
];
this._handType = this._handTypes
.filter((handType) => handType.isMatch)
.sort((a, b) => a.weight - b.weight)
.find((handType) => handType.isMatch);
}
get handType() {
return this._handType;
}
get cards() {
return [this._card1, this._card2, this._card3, this._card4, this._card5];
}
get jokerCount() {
return this.cards.filter((card) => card.isJoker).length;
}
get groupedHand() {
return this._groupedHand;
}
doesHaveSameCardCountIncludedJokers(...cardCounts: number[]): boolean {
const sum = cardCounts.reduce((acc, cardCount) => acc + cardCount, 0);
if (sum !== 5) throw new Error("Invalid card counts");
let jokerCount = this.jokerCount;
const groupedHand = this.groupedHand.slice();
for (const cardCount of cardCounts) {
for (const group of groupedHand) {
if (group.length === cardCount) {
groupedHand.splice(groupedHand.indexOf(group), 1);
cardCounts.splice(cardCounts.indexOf(cardCount), 1);
}
}
}
for (const cardCount of cardCounts) {
for (const group of groupedHand) {
if (group.length <= cardCount) {
const d = cardCount - group.length;
if (d <= jokerCount) {
jokerCount -= d;
groupedHand.splice(groupedHand.indexOf(group), 1);
cardCounts.splice(cardCounts.indexOf(cardCount), 1);
}
}
}
}
if (cardCounts.length === groupedHand.flatMap((entry) => entry).length) {
return true;
}
if (cardCounts.length === 0 && groupedHand.length === 0) return true;
if (jokerCount === 0) return false;
const remainingSum = cardCounts.reduce(
(acc, cardCount) => acc + cardCount,
0,
);
if (remainingSum === jokerCount) {
return true;
}
return false;
}
get card1() {
return this._card1;
}
get card2() {
return this._card2;
}
get card3() {
return this._card3;
}
get card4() {
return this._card4;
}
get card5() {
return this._card5;
}
}
class Line {
private _hand: Hand;
private _bid: number;
constructor(
public line: string,
cardsWeights: Readonly<string[]>,
includeJokers: boolean,
) {
this._hand = new Hand(this.line.split(" ")[0], cardsWeights, includeJokers);
this._bid = Number(this.line.split(" ")[1]);
}
get hand() {
return this._hand;
}
get bid() {
return this._bid;
}
}
class Game {
private _lines: Line[];
constructor(
public input: string,
cardsWeights: Readonly<string[]>,
includeJokers: boolean = false,
) {
this._lines = input.split("\n").filter(Boolean).map((line) =>
new Line(line, cardsWeights, includeJokers)
);
}
get lines() {
return this._lines;
}
}
const solvePart1 = (data: string): number => {
const cardsWeights = [
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"T",
"J",
"Q",
"K",
"A",
] as const;
const game = new Game(data, cardsWeights);
return game.lines.sort((a, b) => {
const weightA = a.hand.handType?.weight || 0;
const weightB = b.hand.handType?.weight || 0;
if (weightA > weightB) return -1;
if (weightA < weightB) return 1;
for (let i = 0; i < 5; i += 1) {
const valueA = a.hand.cards[i].weight;
const valueB = b.hand.cards[i].weight;
if (valueA > valueB) return 1;
if (valueA < valueB) return -1;
}
return 0;
})
.reduce((sum, line, index) => {
return sum + ((index + 1) * line.bid);
}, 0);
};
console.log("Sample:", solvePart1(sample)); // 6440
console.log("Input", solvePart1(input)); // 251927063
const solvePart2 = (data: string): number => {
const cardsWeights = [
"J",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"T",
"Q",
"K",
"A",
] as const;
const game = new Game(data, cardsWeights, true);
return game.lines.sort((a, b) => {
const weightA = a.hand.handType?.weight || 0;
const weightB = b.hand.handType?.weight || 0;
if (weightA > weightB) return -1;
if (weightA < weightB) return 1;
for (let i = 0; i < 5; i += 1) {
const valueA = a.hand.cards[i].weight;
const valueB = b.hand.cards[i].weight;
if (valueA > valueB) return 1;
if (valueA < valueB) return -1;
}
return 0;
})
.reduce((sum, line, index) => {
return sum + ((index + 1) * line.bid);
}, 0);
};
console.log("Sample:", solvePart2(sample)); // 5905
console.log("Input", solvePart2(input)); // 255632664