diff --git a/2023/day07/part2.ts b/2023/day07/part2.ts index 5658bc9..467426c 100644 --- a/2023/day07/part2.ts +++ b/2023/day07/part2.ts @@ -1,3 +1,117 @@ +export function answer(input: string): number { + const handsAndBids = input.split("\n").map(parseHand); + + handsAndBids.sort(([handA], [handB]) => { + const [typeA, typeB] = [handType(handA), handType(handB)]; + if (typeA !== typeB) { + return HAND_TYPES.indexOf(typeA) > HAND_TYPES.indexOf(typeB) ? 1 : -1; + } else { + for (let i = 0; i < handA.length; i++) { + if (handA[i] !== handB[i]) { + return CARDS.indexOf(handA[i]) > CARDS.indexOf(handB[i]) ? 1 : -1; + } + } + } + throw `hands are the same!: ${handA} ${handB}`; + }); + + return handsAndBids.reduce( + (sum, [_hand, bid], rank) => sum + bid * (rank + 1), + 0, + ); +} + +const CARDS = "J23456789TQKA"; +const HAND_TYPES = [ + "high card", + "1 pair", + "2 pair", + "3 of a kind", + "full house", + "4 of a kind", + "5 of a kind", +]; + +function parseHand(l: string): [string, number] { + const [hand, bidStr] = l.split(" "); + return [hand, parseInt(bidStr)]; +} + +const cardCounts = (hand: string[]) => ({ + "2": hand.filter((c) => c === "2").length, + "3": hand.filter((c) => c === "3").length, + "4": hand.filter((c) => c === "4").length, + "5": hand.filter((c) => c === "5").length, + "6": hand.filter((c) => c === "6").length, + "7": hand.filter((c) => c === "7").length, + "8": hand.filter((c) => c === "8").length, + "9": hand.filter((c) => c === "9").length, + T: hand.filter((c) => c === "T").length, + J: hand.filter((c) => c === "J").length, + Q: hand.filter((c) => c === "Q").length, + K: hand.filter((c) => c === "K").length, + A: hand.filter((c) => c === "A").length, +}); + +const isFiveOfAKind = (cards: string[]): boolean => new Set(cards).size === 1; + +const isFourOfAKind = (cards: string[]): boolean => { + const uniqueCards = new Set(cards); + return ( + uniqueCards.size >= 2 && + [...uniqueCards].some((c) => cards.filter((cc) => cc === c).length === 4) + ); +}; + +const isThreeOfAKind = (cards: string[]): boolean => { + const uniqueCards = new Set(cards); + return ( + uniqueCards.size >= 2 && + [...uniqueCards].some((c) => cards.filter((cc) => cc === c).length === 3) + ); +}; + +const isFullHouse = (cards: string[]): boolean => { + const uniqueCards = new Set(cards); + return ( + uniqueCards.size === 2 && + Object.values(cardCounts(cards)).filter((c) => c > 1).length === 2 + ); +}; + +const pairs = (cards: string[]): number => + Object.values(cardCounts(cards)).filter((c) => c === 2).length; +const isTwoPair = (cards: string[]): boolean => pairs(cards) === 2; +const isOnePair = (cards: string[]): boolean => pairs(cards) === 1; + +function handType(hand: string): string { + function possibleHandTypes(h: string[], typesSoFar: string[] = []): string[] { + if (h.includes("J")) { + return [ + ...typesSoFar, + ...CARDS.slice(1) + .split("") + .map((c) => + possibleHandTypes(h.join("").replace("J", c).split(""), typesSoFar), + ) + .flat(), + ]; + } + if (isFiveOfAKind(h)) return [...typesSoFar, "5 of a kind"]; + if (isFourOfAKind(h)) return [...typesSoFar, "4 of a kind"]; + if (isFullHouse(h)) return [...typesSoFar, "full house"]; + if (isThreeOfAKind(h)) return [...typesSoFar, "3 of a kind"]; + if (isTwoPair(h)) return [...typesSoFar, "2 pair"]; + if (isOnePair(h)) return [...typesSoFar, "1 pair"]; + return [...typesSoFar, "high card"]; + } + + const possibleTypes = [...new Set(possibleHandTypes(hand.split("")))].sort( + (a, b) => (HAND_TYPES.indexOf(a) > HAND_TYPES.indexOf(b) ? -1 : 1), + ); + return possibleTypes[0]; +} + if (import.meta.main) { const input = ( await Deno.readFile("input").then((bytes) => @@ -6,8 +120,3 @@ if (import.meta.main) { ).trim(); console.log(answer(input)); } - -export function answer(input: string): number { - console.log(input); - return 42; -} diff --git a/2023/day07/puzzle.md b/2023/day07/puzzle.md index 340b21a..c1dcc4c 100644 --- a/2023/day07/puzzle.md +++ b/2023/day07/puzzle.md @@ -58,8 +58,40 @@ Now, you can determine the total winnings of this set of hands by adding up the Find the rank of every hand in your set. *What are the total winnings?* -To begin, [get your puzzle input](7/input). +Your puzzle answer was `251136060`. + +The first half of this puzzle is complete! It provides one gold star: \* + +\--- Part Two --- +---------- + +To make things a little more interesting, the Elf introduces one additional rule. Now, `J` cards are [jokers](https://en.wikipedia.org/wiki/Joker_(playing_card)) - 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?* Answer: -You can also [Shareon [Twitter](https://twitter.com/intent/tweet?text=%22Camel+Cards%22+%2D+Day+7+%2D+Advent+of+Code+2023&url=https%3A%2F%2Fadventofcode%2Ecom%2F2023%2Fday%2F7&related=ericwastl&hashtags=AdventOfCode) [Mastodon](javascript:void(0);)] this puzzle. \ No newline at end of file +Although it hasn't changed, you can still [get your puzzle input](7/input). + +You can also [Shareon [Twitter](https://twitter.com/intent/tweet?text=I%27ve+completed+Part+One+of+%22Camel+Cards%22+%2D+Day+7+%2D+Advent+of+Code+2023&url=https%3A%2F%2Fadventofcode%2Ecom%2F2023%2Fday%2F7&related=ericwastl&hashtags=AdventOfCode) [Mastodon](javascript:void(0);)] this puzzle. \ No newline at end of file diff --git a/2023/day07/test.ts b/2023/day07/test.ts index e2d43ec..a8dac94 100644 --- a/2023/day07/test.ts +++ b/2023/day07/test.ts @@ -13,7 +13,13 @@ Deno.test("part1", () => { assertEquals(p1.answer(examples), 6440); }); -// Deno.test("part2", () => { -// const examples = ["abc", "def"].join("\n"); -// assertEquals(p2.answer(examples), 42); -// }); +Deno.test("part2", () => { + const examples = [ + "32T3K 765", + "T55J5 684", + "KK677 28", + "KTJJT 220", + "QQQJA 483", + ].join("\n"); + assertEquals(p2.answer(examples), 5905); +});