Add day 7 part 1

master
Nick Krichevsky 2023-12-07 01:43:31 -05:00
parent 89112a526f
commit 440c89b9b2
1 changed files with 221 additions and 0 deletions

221
day7/main.go Normal file
View File

@ -0,0 +1,221 @@
package main
import (
"cmp"
"errors"
"fmt"
"io"
"os"
"slices"
"strconv"
"strings"
)
type Card int
const (
Two Card = iota + 2
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Jack
Queen
King
Ace
)
type HandKind int
const (
UnknownKind HandKind = iota
HighCard
OnePair
TwoPair
ThreeOfAKind
FullHouse
FourOfAKind
FiveOfAKind
)
type Hand []Card
type Player struct {
bid int
hand Hand
}
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},
}
distinctCounts := hand.CountDistinct()
cardCounts := mapValues(distinctCounts)
slices.Sort(cardCounts)
for kind, kindCounts := range handCounts {
if slices.Equal(cardCounts, kindCounts) {
return kind, nil
}
}
return UnknownKind, errors.New("got it")
}
func (hand Hand) CountDistinct() map[Card]int {
buckets := make(map[Card]int)
for _, card := range hand {
buckets[card]++
}
return buckets
}
func main() {
if len(os.Args) != 2 && len(os.Args) != 3 {
fmt.Fprintf(os.Stderr, "Usage: %s inputfile\n", os.Args[0])
os.Exit(1)
}
inputFilename := os.Args[1]
inputFile, err := os.Open(inputFilename)
if err != nil {
panic(fmt.Sprintf("could not open input file: %s", err))
}
defer inputFile.Close()
inputBytes, err := io.ReadAll(inputFile)
if err != nil {
panic(fmt.Sprintf("could not read input file: %s", err))
}
input := strings.TrimSpace(string(inputBytes))
if err != nil {
panic(fmt.Sprintf("failed to parse input: %s", err))
}
inputLines := strings.Split(input, "\n")
players, err := parsePlayers(inputLines)
if err != nil {
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))
}
func part1(players []Player) int {
sortedPlayers := slices.Clone(players)
slices.SortFunc(sortedPlayers, func(a, b Player) int {
aKind, err := a.hand.Kind()
if err != nil {
panic(fmt.Errorf("invalid hand %+v: %w", a, err))
}
bKind, err := b.hand.Kind()
if err != nil {
panic(fmt.Errorf("invalid hand %+v: %w", b, err))
}
compareHands := cmp.Compare(aKind, bKind)
if compareHands == 0 {
return slices.Compare(a.hand, b.hand)
} else {
return compareHands
}
})
winnings := 0
for i, player := range sortedPlayers {
winnings += (i + 1) * player.bid
}
return winnings
}
func parsePlayers(inputLines []string) ([]Player, error) {
players := make([]Player, 0, len(inputLines))
for i, line := range inputLines {
player, err := parsePlayer(line)
if err != nil {
return nil, fmt.Errorf("parse line %d: %w", i, err)
}
players = append(players, player)
}
return players, nil
}
func parsePlayer(inputLine string) (Player, error) {
lineComponents := strings.Split(inputLine, " ")
if len(lineComponents) != 2 {
return Player{}, errors.New("malformed player line")
}
hand, err := parseHand(lineComponents[0])
if err != nil {
return Player{}, fmt.Errorf("parse hand: %w", err)
}
bid, err := strconv.Atoi(lineComponents[1])
if err != nil {
return Player{}, fmt.Errorf("parse bid: %w", err)
}
return Player{bid: bid, hand: hand}, nil
}
func parseHand(handStr string) (Hand, error) {
cardMap := map[byte]Card{
'A': Ace,
'K': King,
'Q': Queen,
'J': Jack,
'T': Ten,
'9': Nine,
'8': Eight,
'7': Seven,
'6': Six,
'5': Five,
'4': Four,
'3': Three,
'2': Two,
}
hand := Hand{}
for _, handChar := range handStr {
card, ok := cardMap[byte(handChar)]
if !ok {
return nil, fmt.Errorf("invalid card character %c", handChar)
}
hand = append(hand, card)
}
return hand, nil
}
func mapValues[T comparable, U any](m map[T]U) []U {
values := make([]U, 0, len(m))
for _, value := range m {
values = append(values, value)
}
return values
}