From b2c9a13c51112c791697f401d03e06484fac6064 Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Fri, 8 Dec 2023 16:50:21 -0500 Subject: [PATCH] Add day 7 part 2 --- day7/main.go | 151 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 132 insertions(+), 19 deletions(-) diff --git a/day7/main.go b/day7/main.go index 503c33c..d296cb6 100644 --- a/day7/main.go +++ b/day7/main.go @@ -14,7 +14,8 @@ import ( type Card int const ( - Two Card = iota + 2 + Joker Card = iota + 1 + Two Three Four Five @@ -49,29 +50,75 @@ type Player struct { hand Hand } +// Kind gets the "Kind" of the hand, which determines its value. func (hand Hand) Kind() (HandKind, error) { - handCounts := map[HandKind][]int{ - FiveOfAKind: {5}, - FourOfAKind: {1, 4}, - FullHouse: {2, 3}, - ThreeOfAKind: {1, 1, 3}, - TwoPair: {1, 2, 2}, - OnePair: {1, 1, 1, 2}, - HighCard: {1, 1, 1, 1, 1}, + type kindMapping struct { + kind HandKind + distinctCounts []int } - distinctCounts := hand.CountDistinct() + // Must iterate in order so we try each kind first + handCounts := []kindMapping{ + { + kind: FiveOfAKind, + distinctCounts: []int{5}, + }, + { + kind: FourOfAKind, + distinctCounts: []int{1, 4}, + }, + { + kind: FullHouse, + distinctCounts: []int{2, 3}, + }, + { + kind: ThreeOfAKind, + distinctCounts: []int{1, 1, 3}, + }, + { + kind: TwoPair, + distinctCounts: []int{1, 2, 2}, + }, + { + kind: OnePair, + distinctCounts: []int{1, 1, 1, 2}, + }, + { + kind: HighCard, + distinctCounts: []int{1, 1, 1, 1, 1}, + }, + } + + handWithoutJokers, numJokers := hand.WithoutCard(Joker) + distinctCounts := handWithoutJokers.CountDistinct() cardCounts := mapValues(distinctCounts) slices.Sort(cardCounts) - for kind, kindCounts := range handCounts { - if slices.Equal(cardCounts, kindCounts) { - return kind, nil + for _, mapping := range handCounts { + if canMakeKindWithCardCombo(cardCounts, mapping.distinctCounts, numJokers) { + return mapping.kind, nil } } - return UnknownKind, errors.New("got it") + return UnknownKind, errors.New("no known kind for hand") } +// WithoutCard will return a copy of the Hand without the given card, toRemove +func (hand Hand) WithoutCard(toRemove Card) (Hand, int) { + newHand := Hand{} + numRemoved := 0 + for _, card := range hand { + if card == toRemove { + numRemoved++ + continue + } + + newHand = append(newHand, card) + } + + return newHand, numRemoved +} + +// CountDistinct returns a count of distinct cards by each card type func (hand Hand) CountDistinct() map[Card]int { buckets := make(map[Card]int) for _, card := range hand { @@ -111,15 +158,22 @@ func main() { panic(fmt.Sprintf("failed to parse races: %s", err)) } - for _, player := range players { - kind, _ := player.hand.Kind() - fmt.Printf("%+v %+v\n", player.hand, kind) - } - fmt.Printf("Part 1: %d\n", part1(players)) + fmt.Printf("Part 2: %d\n", part2(players)) } func part1(players []Player) int { + return findWinnings(players) +} + +func part2(originalPlayers []Player) int { + players := makePart2Players(originalPlayers) + + return findWinnings(players) +} + +// findWinnings finds the winning for each game +func findWinnings(players []Player) int { sortedPlayers := slices.Clone(players) slices.SortFunc(sortedPlayers, func(a, b Player) int { aKind, err := a.hand.Kind() @@ -148,6 +202,65 @@ func part1(players []Player) int { return winnings } +// makePart2Players prepares the players for part 2 by replacing Jacks with Jokers +func makePart2Players(players []Player) []Player { + newPlayers := slices.Clone(players) + for i, player := range newPlayers { + updPlayer := player + updPlayer.hand = makePart2Hand(player.hand) + newPlayers[i] = updPlayer + } + + return newPlayers +} + +// makePart2Hand makes replaces Jacks with Jokers in each hand for part 2 +func makePart2Hand(hand Hand) Hand { + newHand := slices.Clone(hand) + for i, card := range newHand { + if card == Jack { + newHand[i] = Joker + } + } + return newHand +} + +// canMakeKindWithCardCombo checks that if we can make the kind given. This is done by taking a sorted list of +// each of the number of each type of card. For instance, 55766 would be [2, 2, 1] +func canMakeKindWithCardCombo(handDistinctCardCounts []int, kindCardCounts []int, numJokers int) bool { + if numJokers == 0 { + return slices.Equal(kindCardCounts, handDistinctCardCounts) + } else if len(handDistinctCardCounts) == 0 && numJokers == 5 && slices.Equal(kindCardCounts, []int{5}) { + // If we have five jokers, we must act as a five of a kind (most valuable) + return true + } + permutations := permutationsOfJokers(handDistinctCardCounts, numJokers) + + for _, permutation := range permutations { + if slices.Equal(permutation, kindCardCounts) { + return true + } + } + + return false +} + +// Find all the positions where our sets of jokers could be located in in the distinct card count set +func permutationsOfJokers(distinctCardCounts []int, numJokers int) [][]int { + if numJokers == 0 { + return [][]int{distinctCardCounts} + } + + permutations := [][]int{} + for i, count := range distinctCardCounts { + newCounts := slices.Clone(distinctCardCounts) + newCounts[i] = count + 1 + permutations = append(permutations, permutationsOfJokers(newCounts, numJokers-1)...) + } + + return permutations +} + func parsePlayers(inputLines []string) ([]Player, error) { players := make([]Player, 0, len(inputLines)) for i, line := range inputLines {