This commit is contained in:
onyx-and-iris 2023-12-07 23:13:05 +00:00
parent 1c686b53de
commit 4aafad4e8d
10 changed files with 568 additions and 0 deletions

8
day-7/go.mod Normal file
View File

@ -0,0 +1,8 @@
module github.com/onyx-and-iris/aoc2023/day-7
go 1.20
require (
github.com/samber/lo v1.39.0 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
)

4
day-7/go.sum Normal file
View File

@ -0,0 +1,4 @@
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=

100
day-7/hand_test.go Normal file
View File

@ -0,0 +1,100 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetMatches(t *testing.T) {
//t.Skip("skipping test")
h1 := newHand("32T3K", 0)
h2 := newHand("ABC3D", 0)
res1 := map[rune]int{
'3': 2,
'2': 1,
'T': 1,
'K': 1,
}
res2 := map[rune]int{
'A': 1,
'B': 1,
'C': 1,
'3': 1,
'D': 1,
}
t.Run("Should produce equal maps", func(t *testing.T) {
assert.Equal(t, res1, h1.getMatches())
})
t.Run("Should produce equal maps", func(t *testing.T) {
assert.Equal(t, res2, h2.getMatches())
})
}
func TestIsHighCard(t *testing.T) {
//t.Skip("skipping test")
h1 := newHand("ABC12", 0)
t.Run("Should be a high card", func(t *testing.T) {
assert.Equal(t, true, isHighCard(h1))
})
t.Run("Should not be a four of a kind", func(t *testing.T) {
assert.Equal(t, false, isFourOfAKind(h1))
})
}
func TestIsFourOfAKind(t *testing.T) {
//t.Skip("skipping test")
h1 := newHand("UUUU9", 0)
h2 := newHand("UUU99", 0)
t.Run("Should be a four of a kind", func(t *testing.T) {
assert.Equal(t, true, isFourOfAKind(h1))
})
t.Run("Should not be a four of a kind", func(t *testing.T) {
assert.Equal(t, false, isFourOfAKind(h2))
})
}
func TestIsFullHouse(t *testing.T) {
//t.Skip("skipping test")
h1 := newHand("333KK", 0)
h2 := newHand("3338K", 0)
t.Run("Should be a fullhouse", func(t *testing.T) {
assert.Equal(t, true, isFullHouse(h1))
})
t.Run("Should not be a fullhouse", func(t *testing.T) {
assert.Equal(t, false, isFullHouse(h2))
})
}
func TestIsTwoPair(t *testing.T) {
//t.Skip("skipping test")
h1 := newHand("33QQA", 0)
t.Run("Should be a twopair", func(t *testing.T) {
assert.Equal(t, true, isTwoPair(h1))
})
}
func TestIsOnePair(t *testing.T) {
//t.Skip("skipping test")
h1 := newHand("33QKA", 0)
t.Run("Should be a onepair", func(t *testing.T) {
assert.Equal(t, true, isOnePair(h1))
})
}

108
day-7/hands.go Normal file
View File

@ -0,0 +1,108 @@
package main
var hands = []*hand{}
type hand struct {
cards string
bid int
matches map[rune]int
numPairs int
_kind int
}
func newHand(cards string, bid int) *hand {
h := &hand{cards: cards, bid: bid}
setKind(h)
return h
}
func (h *hand) kind() int {
return h._kind
}
func (h *hand) getMatches() map[rune]int {
if h.matches == nil {
h.matches = matches(h.cards)
}
return h.matches
}
func setKind(h *hand) {
if isFiveOfAKind(h) {
h._kind = fiveofakind
} else if isHighCard(h) {
h._kind = highcard
} else if isFourOfAKind(h) {
h._kind = fourofakind
} else if isFullHouse(h) {
h._kind = fullhouse
} else if isThreeOfAKind(h) {
h._kind = threeofakind
} else if isTwoPair(h) {
h._kind = twopair
} else if isOnePair(h) {
h._kind = onepair
}
}
func isFiveOfAKind(h *hand) bool {
return len(h.getMatches()) == 1
}
func isHighCard(h *hand) bool {
return len(h.getMatches()) == 5
}
func isFourOfAKind(h *hand) bool {
if len(h.getMatches()) == 2 {
for _, c := range h.matches {
if c == 4 {
return true
}
}
return false
}
return false
}
func isFullHouse(h *hand) bool {
if len(h.getMatches()) == 2 {
return !isFourOfAKind(h)
}
return false
}
func isThreeOfAKind(h *hand) bool {
if len(h.getMatches()) == 3 {
for _, c := range h.matches {
if c == 3 {
return true
}
}
return false
}
return false
}
func getNumPairs(h *hand) int {
if h.numPairs == 0 {
allowed := []int{1, 2}
for _, n := range h.getMatches() {
if !contains(allowed, n) {
return -1 // there may be a pair but its a fullhouse
}
if n == 2 {
h.numPairs++
}
}
}
return h.numPairs
}
func isTwoPair(h *hand) bool {
return getNumPairs(h) == 2
}
func isOnePair(h *hand) bool {
return !isTwoPair(h) && h.numPairs == 1
}

31
day-7/one.go Normal file
View File

@ -0,0 +1,31 @@
package main
import log "github.com/sirupsen/logrus"
const (
highcard = iota // 0
onepair = iota // 1
twopair = iota // 2
threeofakind = iota // 3
fullhouse = iota // 4
fourofakind = iota // 5
fiveofakind = iota // 6
numKinds = iota
)
// one returns the sum of products of hand values by bids
func one(lines []string) (int, error) {
parselines(lines)
strength := []rune{'A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2'}
log.Debug("strength: ", strength)
sortByKindAndStrength(strength)
sum := 0
for i, hand := range hands {
sum += (i + 1) * hand.bid
}
return sum, nil
}

27
day-7/solution.go Normal file
View File

@ -0,0 +1,27 @@
package main
import (
"fmt"
log "github.com/sirupsen/logrus"
)
func init() {
log.SetLevel(log.InfoLevel)
}
func main() {
lines := readlines()
ans, err := one(lines)
if err != nil {
log.Fatal()
}
fmt.Printf("solution one: %d\n", ans)
ans, err = two(lines)
if err != nil {
log.Fatal()
}
fmt.Printf("solution two: %d\n", ans)
}

74
day-7/two.go Normal file
View File

@ -0,0 +1,74 @@
package main
import log "github.com/sirupsen/logrus"
// promote alters a hands kind based on joker rules
func promote(hand *hand) {
m := matches(hand.cards)
log.Debug(m)
switch hand.kind() {
case fiveofakind:
break
case fourofakind:
hand._kind = fiveofakind
case fullhouse:
if m['J'] == 2 || m['J'] == 3 {
hand._kind = fiveofakind
}
log.Debug(hand.cards, " was promoted to ", hand.kind())
case threeofakind:
if m['J'] == 1 || m['J'] == 3 {
hand._kind = fourofakind
}
log.Debug(hand.cards, " was promoted to ", hand.kind())
case twopair:
if m['J'] == 1 {
hand._kind = fullhouse
} else if m['J'] == 2 {
hand._kind = fourofakind
}
log.Debug(hand.cards, " was promoted to ", hand.kind())
case onepair:
if m['J'] == 1 || m['J'] == 2 {
hand._kind = threeofakind
}
log.Debug(hand.cards, " was promoted to ", hand.kind())
case highcard:
hand._kind = onepair
log.Debug(hand.cards, " was promoted to ", hand.kind())
}
}
// two coreturns the sum of products of hand values by bids
// it uses new joker rules
func two(lines []string) (int, error) {
for _, hand := range hands {
if containsChar(hand.cards, "J") {
promote(hand)
}
}
strength := []rune{'A', 'K', 'Q', 'T', '9', '8', '7', '6', '5', '4', '3', '2', 'J'}
log.Debug("strength: ", strength)
sortByKindAndStrength(strength)
sum := 0
var debugNames = map[int]string{
0: "highcard",
1: "onepair",
2: "twopair",
3: "threeofakind",
4: "fullhouse",
5: "fourofakind",
6: "fiveofakind",
}
for i, hand := range hands {
log.Debug(hand.cards, ": ", hand.bid, " by ", i+1, " || ", debugNames[hand.kind()])
sum += (i + 1) * hand.bid
}
return sum, nil
}

103
day-7/two_test.go Normal file
View File

@ -0,0 +1,103 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPromoteHighCard(t *testing.T) {
//t.Skip("skipping test")
h := newHand("1234J", 0)
promote(h)
t.Run("Should promote to onepair", func(t *testing.T) {
assert.Equal(t, onepair, h.kind())
})
}
func TestPromoteOnePair(t *testing.T) {
//t.Skip("skipping test")
h1 := newHand("1233J", 0)
promote(h1)
h2 := newHand("123JJ", 0)
promote(h2)
t.Run("Should promote to threeofakind", func(t *testing.T) {
assert.Equal(t, threeofakind, h1.kind())
})
t.Run("Should promote to threeofakind", func(t *testing.T) {
assert.Equal(t, threeofakind, h2.kind())
})
}
func TestPromoteTwoPair(t *testing.T) {
//t.Skip("skipping test")
h1 := newHand("1133J", 0)
promote(h1)
h2 := newHand("133JJ", 0)
promote(h2)
t.Run("Should promote to fullhouse", func(t *testing.T) {
assert.Equal(t, fullhouse, h1.kind())
})
t.Run("Should promote to fourofakind", func(t *testing.T) {
assert.Equal(t, fourofakind, h2.kind())
})
}
func TestPromoteThreeOfAKind(t *testing.T) {
//t.Skip("skipping test")
h1 := newHand("1333J", 0)
promote(h1)
h2 := newHand("13JJJ", 0)
promote(h2)
t.Run("Should promote to fourofakind", func(t *testing.T) {
assert.Equal(t, fourofakind, h1.kind())
})
t.Run("Should promote to fourofakind", func(t *testing.T) {
assert.Equal(t, fourofakind, h2.kind())
})
}
func TestPromoteFullHOuse(t *testing.T) {
//t.Skip("skipping test")
h1 := newHand("J333J", 0)
promote(h1)
h2 := newHand("33JJJ", 0)
promote(h2)
t.Run("Should promote to fiveofakind", func(t *testing.T) {
assert.Equal(t, fiveofakind, h1.kind())
})
t.Run("Should promote to fiveofakind", func(t *testing.T) {
assert.Equal(t, fiveofakind, h2.kind())
})
}
func TestPromoteFourOfAKind(t *testing.T) {
//t.Skip("skipping test")
h1 := newHand("JJJJ3", 0)
promote(h1)
h2 := newHand("3333J", 0)
promote(h2)
t.Run("Should promote to fiveofakind", func(t *testing.T) {
assert.Equal(t, fiveofakind, h1.kind())
})
t.Run("Should promote to fiveofakind", func(t *testing.T) {
assert.Equal(t, fiveofakind, h2.kind())
})
}

91
day-7/util.go Normal file
View File

@ -0,0 +1,91 @@
package main
import (
"bufio"
"log"
"os"
"sort"
"strconv"
"strings"
)
// readlines reads lines from stdin.
// Then it returns them as an array of strings
func readlines() []string {
lines := []string{}
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
return lines
}
func parselines(lines []string) {
for _, line := range lines {
splitted := strings.Split(line, " ")
n, _ := strconv.Atoi(splitted[1])
hands = append(hands, newHand(splitted[0], n))
}
}
// matches checks for number of matching cards in hand
func matches(cards string) map[rune]int {
m := map[rune]int{}
for _, r := range cards {
_, ok := m[r]
if !ok {
m[r] = strings.Count(cards, string(r))
}
}
return m
}
// contains returns true if a slice of elements contains a given element
func contains[T comparable](elems []T, v T) bool {
for _, s := range elems {
if v == s {
return true
}
}
return false
}
// indexOf returns the index of an element in a slice
func indexOf(collection []rune, elem rune) int {
for i, x := range collection {
if x == elem {
return i
}
}
return -1
}
func containsChar(s string, c string) bool {
for _, v := range s {
if c == string(v) {
return true
}
}
return false
}
func sortByKindAndStrength(strength []rune) {
sort.Slice(hands, func(i, j int) bool {
if hands[i].kind() == hands[j].kind() {
for k := range hands[i].cards {
if hands[i].cards[k] == hands[j].cards[k] {
continue
}
return indexOf(strength, rune(hands[i].cards[k])) > indexOf(strength, rune(hands[j].cards[k]))
}
}
return hands[i].kind() < hands[j].kind()
})
}

22
day-7/util_test.go Normal file
View File

@ -0,0 +1,22 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMatches(t *testing.T) {
//t.Skip("skipping test")
m1 := matches("32T3K")
m2 := matches("JJU8J")
t.Run("Should produce equal maps", func(t *testing.T) {
assert.Equal(t, map[rune]int{'3': 2, '2': 1, 'T': 1, 'K': 1}, m1)
})
t.Run("Should produce equal maps", func(t *testing.T) {
assert.Equal(t, map[rune]int{'J': 3, 'U': 1, '8': 1}, m2)
})
}