generated from kotlin-hands-on/advent-of-code-kotlin-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay07.kt
185 lines (160 loc) · 5.7 KB
/
Day07.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/**
* DAY 07 - Camel Cards
*
* Well, that was pretty tricky. Not really sure that my
* hand-checking is the best - certainly would get ugly if
* the hands had more than 5 cards. But it works
*
* For Part 2, the wildcards took a bit of puzzling out
* before I realised that I only had to sub in each of the
* non-wildcard values for *all* wildcards in one go
*/
package aoc2023
import utils.println
import utils.readInput
/**
* Used to rank the hand types
*/
enum class HandType(val description: String) {
NONE("Nothing"),
HIGHCARD("High Card"),
ONEPAIR("One Pair"),
TWOPAIRS("Two Pairs"),
THREEOFKIND("Three of a Kind"),
FULLHOUSE("Full House"),
FOURKIND("Four of a Kind"),
FIVEOFKIND("Five of a Kind"),
}
// Part 1 deck
val deckNormal = "23456789TJQKA".toList()
// Part 2 deck with wildcard J
val deckWild = "J23456789TQKA".toList()
const val WILD = 'J'
/**
* Extension to check if the section of a given
* string between start and end inclusize contains
* all the same character
*/
fun String.allSame(start: Int, end: Int): Boolean {
val text = this.substring(start..end)
val char = text.first()
text.forEach {
if (it != char) return false
}
return true
}
fun main() {
class Hand(
val hand: String, // The given hand
val bid: Int, // The bid placed
wild: Char? = null // Can supply a wildcard char
) {
val cards = hand.toList() // The card hand as a list
val type: HandType // The hand type, determined below
init {
// If no wild card, then simply process the hand as is
if (wild == null) {
type = type(cards)
}
// Otherwise we need to try out all ways of using the wildcard
else {
// What other cards to we have
val nonWild = cards.filter { it != wild }
// None? Then must be all wildcards
if (nonWild.isEmpty()) {
type = HandType.FIVEOFKIND
}
// We have some, so sub in wildcard for each in turn and find best
else {
var bestType = HandType.NONE
nonWild.forEach { char ->
val testCards = cards.map { if (it == wild) char else it }
val testType = type(testCards)
if (testType > bestType) bestType = testType
}
type = bestType
}
}
}
/**
* Given a set of cards, find what type they are
* Done by sorting first, then checking combinations from
* best (5 of a kind) downwards
*
* Bit clunky... Must be a better way!
*/
fun type(cards: List<Char>): HandType {
val sorted = cards.sorted().joinToString("")
return when {
sorted.allSame(0, 4) -> HandType.FIVEOFKIND
sorted.allSame(0, 3) ||
sorted.allSame(1, 4) -> HandType.FOURKIND
(sorted.allSame(0, 2) && sorted.allSame(3, 4)) ||
(sorted.allSame(0, 1) && sorted.allSame(2, 4)) -> HandType.FULLHOUSE
sorted.allSame(0, 2) ||
sorted.allSame(1, 3) ||
sorted.allSame(2, 4) -> HandType.THREEOFKIND
(sorted.allSame(0, 1) && sorted.allSame(2, 3)) ||
(sorted.allSame(0, 1) && sorted.allSame(3, 4)) ||
(sorted.allSame(1, 2) && sorted.allSame(3, 4)) -> HandType.TWOPAIRS
sorted.allSame(0, 1) ||
sorted.allSame(1, 2) ||
sorted.allSame(2, 3) ||
sorted.allSame(3, 4) -> HandType.ONEPAIR
else -> HandType.HIGHCARD
}
}
override fun toString(): String {
return "$hand (${type.description}), $bid"
}
}
/**
* Run thru all hands and bids
*/
fun parseHands(handList: List<String>, wild: Char? = null): MutableList<Hand> {
val hands = mutableListOf<Hand>()
for (handData in handList) {
val (handCards, bid) = handData.split(" ")
hands.add(Hand(handCards, bid.toInt(), wild))
}
return hands
}
/**
* Sort the hands into rank order (via a bit of a gross
* sorting chain) so we can calc the winnings (rank * bid)
*/
fun totalWinnings(hands: MutableList<Hand>, deck: List<Char>): Int {
hands.sortWith(
compareBy { it: Hand -> it.type }
.thenBy { deck.indexOf(it.cards[0]) }
.thenBy { deck.indexOf(it.cards[1]) }
.thenBy { deck.indexOf(it.cards[2]) }
.thenBy { deck.indexOf(it.cards[3]) }
.thenBy { deck.indexOf(it.cards[4]) }
)
var total = 0
hands.forEachIndexed { i, hand ->
val rank = i + 1
val winnings = rank * hand.bid
println("$hand x $rank -> $winnings")
total += winnings
}
return total
}
// Test data
val testInput = readInput(2023, "Day07_test")
// For Part 1
val testHands1 = parseHands(testInput)
check(totalWinnings(testHands1, deckNormal) == 6440)
// For Part 2
val testHands2 = parseHands(testInput, WILD)
check(totalWinnings(testHands2, deckWild) == 5905)
// The real thing
val input = readInput(2023, "Day07")
// For Part 1
val hands1 = parseHands(input)
totalWinnings(hands1, deckNormal).println()
// For Part 2
val hands2 = parseHands(input, WILD)
totalWinnings(hands2, deckWild).println()
}