diff --git a/day_7/compare.ts b/day_7/compare.ts new file mode 100644 index 0000000..436069a --- /dev/null +++ b/day_7/compare.ts @@ -0,0 +1,27 @@ +export enum RANK { + FIVE_OF_A_KIND, + FOUR_OF_A_KIND, + FULL_HOUSE, + THREE_OF_A_KIND, + TWO_PAIR, + ONE_PAIR, + HIGH_CARD, +} + +export const cmp_by_card = ( + hand1: string, + hand2: string, + strength_order: Map +) => { + for (var i = 0; i < hand1.length; i++) { + const s1 = strength_order.get(hand1[i]); + const s2 = strength_order.get(hand2[i]); + if (undefined === s1 || undefined === s2) { + console.error("ERROR", i, hand1[i], hand2[i]); + return 0; + } + if (s1 > s2) return -1; + if (s1 < s2) return 1; + } + return 0; +}; diff --git a/day_7/parser.ts b/day_7/parser.ts new file mode 100644 index 0000000..be84064 --- /dev/null +++ b/day_7/parser.ts @@ -0,0 +1,15 @@ +import * as fs from "fs"; + +export interface HandBid { + hand: string; + bid: number; +} + +export const read_file = (): HandBid[] => + fs + .readFileSync("input", "ascii") + .split("\n") + .map((line) => { + const [hand, bid] = line.split(" "); + return { hand: hand, bid: Number.parseInt(bid) }; + }); diff --git a/day_7/part_1.ts b/day_7/part_1.ts new file mode 100644 index 0000000..8d0dcee --- /dev/null +++ b/day_7/part_1.ts @@ -0,0 +1,65 @@ +import { RANK, cmp_by_card } from "./compare"; +import { HandBid, read_file } from "./parser"; + +const rank_hand = (hand: string) => { + const cards = hand.split(""); + const card_count = cards.reduce((count, char) => { + const c = count.get(char); + return c ? count.set(char, c + 1) : count.set(char, 1); + }, new Map()); + + const unique_cards = Array.from(card_count.keys()); + + if (unique_cards.length === 1) return RANK.FIVE_OF_A_KIND; + if (unique_cards.length === 4) return RANK.ONE_PAIR; + if (unique_cards.length === 5) return RANK.HIGH_CARD; + if ( + unique_cards.length === 2 && + [1, 4].includes(card_count.get(unique_cards[0]) || 0) + ) + return RANK.FOUR_OF_A_KIND; + if ( + unique_cards.length === 2 && + [2, 3].includes(card_count.get(unique_cards[0]) || 0) + ) + return RANK.FULL_HOUSE; + if ( + unique_cards.length === 3 && + unique_cards.reduce((c, card) => card_count.get(card) === 3 || c, false) + ) + return RANK.THREE_OF_A_KIND; + + return RANK.TWO_PAIR; +}; + +const STRENGTH = new Map( + Object.entries({ + A: 0, + K: 1, + Q: 2, + J: 3, + T: 4, + "9": 5, + "8": 6, + "7": 7, + "6": 8, + "5": 9, + "4": 10, + "3": 11, + "2": 12, + }) +); + +const cmp_hand = (hand_bid1: HandBid, hand_bid2: HandBid) => { + if (rank_hand(hand_bid1.hand) > rank_hand(hand_bid2.hand)) return -1; + if (rank_hand(hand_bid1.hand) < rank_hand(hand_bid2.hand)) return 1; + return cmp_by_card(hand_bid1.hand, hand_bid2.hand, STRENGTH); +}; + +const hand_bids = read_file(); + +console.log( + hand_bids + .sort(cmp_hand) + .reduce((sum, hand_bid, i) => sum + hand_bid.bid * (1 + i), 0) +); diff --git a/day_7/part_2.ts b/day_7/part_2.ts new file mode 100644 index 0000000..0914f30 --- /dev/null +++ b/day_7/part_2.ts @@ -0,0 +1,82 @@ +import { RANK, cmp_by_card } from "./compare"; +import { HandBid, read_file } from "./parser"; + +const rank_hand = (hand: string) => { + const cards = hand.split(""); + const card_count = cards.reduce((count, char) => { + const c = count.get(char); + return c ? count.set(char, c + 1) : count.set(char, 1); + }, new Map(Object.entries({ J: 0 }))); + + // It is always the most optimal choice to greedily substitute J for the card with the most occurrences. + var max_card = "?"; + Array.from(card_count.keys()).forEach((card) => { + if ( + card !== "J" && + (card_count.get(card) || 0) > (card_count.get(max_card) || 0) + ) { + max_card = card; + } + }); + + card_count.set( + max_card, + (card_count.get(max_card) || 0) + (card_count.get("J") || 0) + ); + card_count.delete("J"); + + const unique_cards = Array.from(card_count.keys()).filter( + (card) => card !== "J" + ); + + if (unique_cards.length === 1) return RANK.FIVE_OF_A_KIND; + if (unique_cards.length === 4) return RANK.ONE_PAIR; + if (unique_cards.length === 5) return RANK.HIGH_CARD; + if ( + unique_cards.length === 2 && + [1, 4].includes(card_count.get(unique_cards[0]) || 0) + ) + return RANK.FOUR_OF_A_KIND; + if ( + unique_cards.length === 2 && + [2, 3].includes(card_count.get(unique_cards[0]) || 0) + ) + return RANK.FULL_HOUSE; + if ( + unique_cards.length === 3 && + unique_cards.reduce((c, card) => card_count.get(card) === 3 || c, false) + ) + return RANK.THREE_OF_A_KIND; + + return RANK.TWO_PAIR; +}; + +const STRENGTH = new Map( + Object.entries({ + A: 0, + K: 1, + Q: 2, + T: 3, + "9": 4, + "8": 5, + "7": 6, + "6": 7, + "5": 8, + "4": 9, + "3": 10, + "2": 11, + J: 12, + }) +); + +const cmp_hand = (hand_bid1: HandBid, hand_bid2: HandBid) => { + if (rank_hand(hand_bid1.hand) > rank_hand(hand_bid2.hand)) return -1; + if (rank_hand(hand_bid1.hand) < rank_hand(hand_bid2.hand)) return 1; + return cmp_by_card(hand_bid1.hand, hand_bid2.hand, STRENGTH); +}; + +console.log( + read_file() + .sort(cmp_hand) + .reduce((sum, hand_bid, i) => sum + hand_bid.bid * (1 + i), 0) +);