summaryrefslogtreecommitdiff
path: root/2023/go/internal/day_07
diff options
context:
space:
mode:
authorDJ O'Leary <dijitol@proton.me>2025-11-29 20:23:02 +0100
committerDJ O'Leary <dijitol@proton.me>2025-11-29 20:23:02 +0100
commit4c3a50166bf0f78ce89459c352cc1c6536a8b96d (patch)
tree02e4b66a1435680ef1e92bf82576747b27f16447 /2023/go/internal/day_07
parentbe2f8528b3829f8263507da301812d852297c168 (diff)
chore: make language folders snake_case
Diffstat (limited to '2023/go/internal/day_07')
-rw-r--r--2023/go/internal/day_07/part_01/part_01.go273
-rw-r--r--2023/go/internal/day_07/part_01/part_01_test.go432
2 files changed, 705 insertions, 0 deletions
diff --git a/2023/go/internal/day_07/part_01/part_01.go b/2023/go/internal/day_07/part_01/part_01.go
new file mode 100644
index 0000000..a485dfd
--- /dev/null
+++ b/2023/go/internal/day_07/part_01/part_01.go
@@ -0,0 +1,273 @@
+package part_01
+
+import (
+ "os"
+ "strconv"
+ "strings"
+)
+
+const USE_SAMPLE = false
+
+type handType int
+
+const (
+ HIGH_CARD handType = iota
+ PAIR
+ TWO_PAIR
+ THREE_OF_A_KIND
+ FULL_HOUSE
+ FOUR_OF_A_KIND
+ FIVE_OF_A_KIND
+)
+
+func (h handType) String() string {
+ return [...]string{"HIGH_CARD", "PAIR", "TWO_PAIR", "THREE_OF_A_KIND", "FULL_HOUSE", "FOUR_OF_A_KIND", "FIVE_OF_A_KIND"}[h]
+}
+
+type cardType int
+
+const (
+ TWO cardType = iota
+ THREE
+ FOUR
+ FIVE
+ SIX
+ SEVEN
+ EIGHT
+ NINE
+ TEN
+ JACK
+ QUEEN
+ KING
+ ACE
+)
+
+func (c cardType) String() string {
+ return [...]string{"TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "TEN", "JACK", "QUEEN", "KING", "ACE"}[c]
+}
+
+type hand struct {
+ strength handType
+ cards []cardType
+ bet int
+}
+
+/**
+ * Tried:
+ * - 252489894 (too high)
+ * - 229176554 (too low)
+ * - 200253108 (too low)
+ */
+func Solve() {
+ var filePath string
+ if USE_SAMPLE {
+ filePath = "../_input/day_07_part_01_sample.txt"
+ } else {
+ filePath = "../_input/day_07.txt"
+ }
+ contents, err := os.ReadFile(filePath)
+ if err != nil {
+ panic(err)
+ }
+
+ lines := strings.Split(string(contents), "\r\n")
+ var hands []hand
+ for _, line := range lines {
+ if line == "" {
+ continue
+ }
+
+ hands = append(hands, parseLine(line))
+ }
+
+ mappedByStrength := mapByStrength(hands)
+
+ sortedByHighest := make(map[handType][]hand)
+ for _, strength := range []handType{HIGH_CARD, PAIR, TWO_PAIR, THREE_OF_A_KIND, FULL_HOUSE, FOUR_OF_A_KIND, FIVE_OF_A_KIND} {
+ if _, ok := mappedByStrength[strength]; !ok {
+ continue
+ }
+
+ toBeSorted := mappedByStrength[strength]
+ sortedByHighest[strength] = sortByHighest(toBeSorted)
+ }
+
+ flattened := flatten(sortedByHighest)
+
+ answer := calculateWinnings(flattened)
+
+ println("Answer: ", answer)
+}
+
+func parseLine(line string) hand {
+ parts := strings.Split(line, " ")
+
+ unparsedCards := strings.Split(parts[0], "")
+ cards := parseCards(unparsedCards)
+ strength := calculateStrength(cards)
+
+ bet, err := strconv.Atoi(parts[1])
+ if err != nil {
+ panic(err)
+ }
+
+ return hand{
+ strength,
+ cards,
+ bet,
+ }
+}
+
+func parseCards(unparsedCards []string) []cardType {
+ var cards []cardType
+ for _, card := range unparsedCards {
+ switch card {
+ case "2":
+ cards = append(cards, TWO)
+ case "3":
+ cards = append(cards, THREE)
+ case "4":
+ cards = append(cards, FOUR)
+ case "5":
+ cards = append(cards, FIVE)
+ case "6":
+ cards = append(cards, SIX)
+ case "7":
+ cards = append(cards, SEVEN)
+ case "8":
+ cards = append(cards, EIGHT)
+ case "9":
+ cards = append(cards, NINE)
+ case "T":
+ cards = append(cards, TEN)
+ case "J":
+ cards = append(cards, JACK)
+ case "Q":
+ cards = append(cards, QUEEN)
+ case "K":
+ cards = append(cards, KING)
+ case "A":
+ cards = append(cards, ACE)
+ }
+ }
+ return cards
+}
+
+func calculateStrength(cards []cardType) handType {
+ strength := map[cardType]int{}
+ for _, card := range cards {
+ if _, ok := strength[card]; !ok {
+ strength[card] = 0
+ }
+ strength[card]++
+ }
+
+ var pairs int
+ var hasThreeOfAKind bool
+ for _, count := range strength {
+ if count == 5 {
+ return FIVE_OF_A_KIND
+ }
+
+ if count == 4 {
+ return FOUR_OF_A_KIND
+ }
+
+ if count == 3 {
+ hasThreeOfAKind = true
+ }
+
+ if count == 2 {
+ pairs++
+ }
+ }
+
+ if hasThreeOfAKind && pairs == 1 {
+ return FULL_HOUSE
+ }
+
+ if hasThreeOfAKind {
+ return THREE_OF_A_KIND
+ }
+
+ if pairs == 2 {
+ return TWO_PAIR
+ }
+
+ if pairs == 1 {
+ return PAIR
+ }
+
+ return HIGH_CARD
+}
+
+func mapByStrength(hands []hand) map[handType][]hand {
+
+ handsByStrength := make(map[handType][]hand)
+ for _, hand := range hands {
+ handsByStrength[hand.strength] = append(handsByStrength[hand.strength], hand)
+ }
+
+ return handsByStrength
+}
+
+// low to high
+func sortByHighest(hands []hand) []hand {
+ var partition = func(arr []hand, low, high int) ([]hand, int) {
+ pivot := arr[high]
+ index := low
+
+ for i := low; i < high; i++ {
+ // Always 5 cards
+ for j := 0; j < 5; j++ {
+ if arr[i].cards[j] < pivot.cards[j] {
+ arr[i], arr[index] = arr[index], arr[i]
+ index++
+ break
+ }
+
+ if arr[i].cards[j] != pivot.cards[j] {
+ break
+ }
+ }
+ }
+
+ arr[high], arr[index] = arr[index], arr[high]
+
+ return arr, index
+ }
+
+ var quickSort func(arr []hand, low, high int) []hand
+ quickSort = func(arr []hand, low, high int) []hand {
+ if low < high {
+ arr, pivot := partition(arr, low, high)
+
+ quickSort(arr, low, pivot-1)
+ quickSort(arr, pivot+1, high)
+ }
+
+ return arr
+ }
+
+ hands = quickSort(hands, 0, len(hands)-1)
+
+ return hands
+}
+
+func flatten(hands map[handType][]hand) []hand {
+ var flat []hand
+ for _, handType := range []handType{HIGH_CARD, PAIR, TWO_PAIR, THREE_OF_A_KIND, FULL_HOUSE, FOUR_OF_A_KIND, FIVE_OF_A_KIND} {
+ flat = append(flat, hands[handType]...)
+ }
+ return flat
+}
+
+func calculateWinnings(hands []hand) int {
+ var winnings int
+ for i, hand := range hands {
+ earned := hand.bet * (i + 1)
+ winnings += earned
+ println("hand ", i, ": { strength: ", hand.strength.String(), ", cards: [", hand.cards[0], hand.cards[1], hand.cards[2], hand.cards[3], hand.cards[4], "], earned: ", earned, " }")
+ }
+ return winnings
+}
diff --git a/2023/go/internal/day_07/part_01/part_01_test.go b/2023/go/internal/day_07/part_01/part_01_test.go
new file mode 100644
index 0000000..6ceabe8
--- /dev/null
+++ b/2023/go/internal/day_07/part_01/part_01_test.go
@@ -0,0 +1,432 @@
+package part_01
+
+import "testing"
+
+func TestParseLine(t *testing.T) {
+ tests := map[string]struct {
+ input string
+ expected hand
+ }{
+ "sample_1": {
+ input: "32T3K 765",
+ expected: hand{
+ strength: PAIR,
+ cards: []cardType{THREE, TWO, TEN, THREE, KING},
+ bet: 765,
+ },
+ },
+ "sample_2": {
+ input: "T55J5 684",
+ expected: hand{
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE},
+ bet: 684,
+ },
+ },
+ "sample_3": {
+ input: "KK677 28",
+ expected: hand{
+ strength: TWO_PAIR,
+ cards: []cardType{KING, KING, SIX, SEVEN, SEVEN},
+ bet: 28,
+ },
+ },
+ "sample_4": {
+ input: "KTJJT 220",
+ expected: hand{
+ strength: TWO_PAIR,
+ cards: []cardType{KING, TEN, JACK, JACK, TEN},
+ bet: 220,
+ },
+ },
+ "sample_5": {
+ input: "QQQJA 483",
+ expected: hand{
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE},
+ bet: 483,
+ },
+ },
+ "full_house": {
+ input: "QQQAA 999",
+ expected: hand{
+ strength: FULL_HOUSE,
+ cards: []cardType{QUEEN, QUEEN, QUEEN, ACE, ACE},
+ bet: 999,
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ actual := parseLine(test.input)
+
+ if actual.strength != test.expected.strength {
+ t.Errorf("Strength: expected %v, got %v", test.expected.strength, actual.strength)
+ }
+
+ if len(actual.cards) != len(test.expected.cards) {
+ t.Errorf("Cards: expected %v, got %v", test.expected.cards, actual.cards)
+ }
+
+ for i, card := range actual.cards {
+ if card != test.expected.cards[i] {
+ t.Errorf("Card Order: expected %v, got %v", test.expected.cards, actual.cards)
+ }
+ }
+
+ if actual.bet != test.expected.bet {
+ t.Errorf("Bet: expected %v, got %v", test.expected.bet, actual.bet)
+ }
+ })
+ }
+}
+
+func TestMapByStrength(t *testing.T) {
+ tests := map[string]struct {
+ input []hand
+ expected map[handType][]hand
+ }{
+ "sample": {
+ input: []hand{
+ {
+ strength: PAIR,
+ cards: []cardType{THREE, TWO, TEN, THREE, KING},
+ bet: 765,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE},
+ bet: 684,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, KING, SIX, SEVEN, SEVEN},
+ bet: 28,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, TEN, JACK, JACK, TEN},
+ bet: 220,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE},
+ bet: 483,
+ },
+ },
+ expected: map[handType][]hand{
+ PAIR: {
+ {
+ strength: PAIR,
+ cards: []cardType{THREE, TWO, TEN, THREE, KING},
+ bet: 765,
+ },
+ },
+ TWO_PAIR: {
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, KING, SIX, SEVEN, SEVEN},
+ bet: 28,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, TEN, JACK, JACK, TEN},
+ bet: 220,
+ },
+ },
+ THREE_OF_A_KIND: {
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE},
+ bet: 684,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE},
+ bet: 483,
+ },
+ },
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ actual := mapByStrength(test.input)
+
+ for strength, hands := range actual {
+ expected := test.expected[strength]
+
+ if len(hands) != len(expected) {
+ t.Errorf("Length: expected %v, got %v", len(expected), len(hands))
+ }
+
+ for i, hand := range hands {
+ if hand.strength != expected[i].strength {
+ t.Errorf("Strength: expected %v, got %v", expected[i].strength, hand.strength)
+ }
+
+ if len(hand.cards) != len(expected[i].cards) {
+ t.Errorf("Cards: expected %v, got %v", expected[i].cards, hand.cards)
+ }
+
+ for j, card := range hand.cards {
+ if card != expected[i].cards[j] {
+ t.Errorf("Card Order: expected %v, got %v", expected[i].cards, hand.cards)
+ }
+ }
+
+ if hand.bet != expected[i].bet {
+ t.Errorf("Bet: expected %v, got %v", expected[i].bet, hand.bet)
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestSortByHighest(t *testing.T) {
+ tests := map[string]struct {
+ input []hand
+ expected []hand
+ }{
+ "sample": {
+ input: []hand{
+ {
+ strength: PAIR,
+ cards: []cardType{THREE, TWO, TEN, THREE, KING},
+ bet: 765,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE},
+ bet: 684,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, KING, SIX, SEVEN, SEVEN},
+ bet: 28,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, TEN, JACK, JACK, TEN},
+ bet: 220,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE},
+ bet: 483,
+ },
+ },
+ expected: []hand{
+ {
+ strength: PAIR,
+ cards: []cardType{THREE, TWO, TEN, THREE, KING},
+ bet: 765,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE},
+ bet: 684,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE},
+ bet: 483,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, TEN, JACK, JACK, TEN},
+ bet: 220,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, KING, SIX, SEVEN, SEVEN},
+ bet: 28,
+ },
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ actual := sortByHighest(test.input)
+
+ if len(actual) != len(test.expected) {
+ t.Errorf("Length: expected %v, got %v", len(test.expected), len(actual))
+ }
+
+ for i, hand := range actual {
+ if hand.strength != test.expected[i].strength {
+ t.Errorf("Strength: expected %v, got %v", test.expected[i].strength, hand.strength)
+ }
+
+ if len(hand.cards) != len(test.expected[i].cards) {
+ t.Errorf("Cards: expected %v, got %v", test.expected[i].cards, hand.cards)
+ }
+
+ for j, card := range hand.cards {
+ if card != test.expected[i].cards[j] {
+ t.Errorf("Card Order: expected %v, got %v", test.expected[i].cards, hand.cards)
+ }
+ }
+
+ if hand.bet != test.expected[i].bet {
+ t.Errorf("Bet: expected %v, got %v", test.expected[i].bet, hand.bet)
+ }
+ }
+ })
+ }
+}
+
+func TestFlatten(t *testing.T) {
+ tests := map[string]struct {
+ input map[handType][]hand
+ expected []hand
+ }{
+ "sample": {
+ // Order jumbled to ensure sorting works
+ input: map[handType][]hand{
+ TWO_PAIR: {
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, TEN, JACK, JACK, TEN},
+ bet: 220,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, KING, SIX, SEVEN, SEVEN},
+ bet: 28,
+ },
+ },
+ PAIR: {
+ {
+ strength: PAIR,
+ cards: []cardType{THREE, TWO, TEN, THREE, KING},
+ bet: 765,
+ },
+ },
+ THREE_OF_A_KIND: {
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE},
+ bet: 684,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE},
+ bet: 483,
+ },
+ },
+ },
+ expected: []hand{
+ {
+ strength: PAIR,
+ cards: []cardType{THREE, TWO, TEN, THREE, KING},
+ bet: 765,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, TEN, JACK, JACK, TEN},
+ bet: 220,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, KING, SIX, SEVEN, SEVEN},
+ bet: 28,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE},
+ bet: 684,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE},
+ bet: 483,
+ },
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ actual := flatten(test.input)
+
+ if len(actual) != len(test.expected) {
+ t.Errorf("Length: expected %v, got %v", len(test.expected), len(actual))
+ }
+
+ for i, hand := range actual {
+ expected := test.expected[i]
+
+ if hand.strength != expected.strength {
+ t.Errorf("Strength: expected %v, got %v", expected.strength, hand.strength)
+ }
+
+ if len(hand.cards) != len(expected.cards) {
+ t.Errorf("Cards: expected %v, got %v", expected.cards, hand.cards)
+ }
+
+ for i, card := range hand.cards {
+ if card != expected.cards[i] {
+ t.Errorf("Card Order: expected %v, got %v", expected.cards, hand.cards)
+ }
+ }
+
+ if hand.bet != expected.bet {
+ t.Errorf("Bet: expected %v, got %v", expected.bet, hand.bet)
+ }
+ }
+ })
+ }
+}
+
+func TestCalculateWinnings(t *testing.T) {
+ tests := map[string]struct {
+ input []hand
+ expected int
+ }{
+ "sample": {
+ input: []hand{
+ {
+ strength: PAIR,
+ cards: []cardType{THREE, TWO, TEN, THREE, KING},
+ bet: 765,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, TEN, JACK, JACK, TEN},
+ bet: 220,
+ },
+ {
+ strength: TWO_PAIR,
+ cards: []cardType{KING, KING, SIX, SEVEN, SEVEN},
+ bet: 28,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE},
+ bet: 684,
+ },
+ {
+ strength: THREE_OF_A_KIND,
+ cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE},
+ bet: 483,
+ },
+ },
+ expected: 6440,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ actual := calculateWinnings(test.input)
+
+ if actual != test.expected {
+ t.Errorf("Winnings: expected %v, got %v", test.expected, actual)
+ }
+ })
+ }
+}