129 lines
3.9 KiB
Kotlin
129 lines
3.9 KiB
Kotlin
import java.io.File
|
|
|
|
typealias Hand = List<Char>
|
|
|
|
fun main() {
|
|
val input = File("input")
|
|
val bets = parseHands(input)
|
|
|
|
println(part1(bets))
|
|
println(part2(bets))
|
|
}
|
|
|
|
fun part1(bets: List<Pair<Hand, Int>>): Int {
|
|
return bets.sortedBy { handValue(it.first) }.foldIndexed(0) { i, acc, bet ->
|
|
acc + (i + 1) * bet.second
|
|
}
|
|
}
|
|
|
|
fun part2(bets: List<Pair<Hand, Int>>): Int {
|
|
return bets.sortedBy { handValueJoker(it.first) }.foldIndexed(0) { i, acc, bet ->
|
|
acc + (i + 1) * bet.second
|
|
}
|
|
}
|
|
|
|
fun cardValue(card: Char): Int {
|
|
return when (card) {
|
|
'A' -> 12
|
|
'K' -> 11
|
|
'Q' -> 10
|
|
'J' -> 9
|
|
'T' -> 8
|
|
in '2'..'9' -> card.digitToInt() - 2
|
|
else -> throw Exception("Invalid card")
|
|
}
|
|
}
|
|
|
|
fun type(cards: Hand): Int {
|
|
return cards.groupBy { it }.mapValues { it.value.count() }.entries.fold(0) { acc, entry ->
|
|
acc +
|
|
when (entry.value) {
|
|
5 -> 6000000
|
|
4 -> 5000000
|
|
3 -> 3000000
|
|
2 -> 1000000
|
|
1 -> 0
|
|
else -> throw Exception("Too many cards")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun handValue(cards: Hand): Int {
|
|
// Each hand has 5 cards, the possible card values are 2-9+TJQKA,
|
|
// and the order matters.
|
|
//
|
|
// This means the hands themselves are basically a 5-digit base 13
|
|
// number.
|
|
//
|
|
// In addition, particular groupings of the cards matter. This is
|
|
// represented by the type - to condense this into an easily
|
|
// usable number, the type is therefore encoded in digits just
|
|
// beyond the max of the 5-digit base 13 numbers. For convenient
|
|
// programming, this means the type is encoded by the millions
|
|
// digit in base 10, hence the odd type return numbers.
|
|
return type(cards) + cards.fold(0) { acc, label -> acc * 13 + cardValue(label) }
|
|
}
|
|
|
|
fun cardValueJoker(card: Char): Int {
|
|
return when (card) {
|
|
'A' -> 12
|
|
'K' -> 11
|
|
'Q' -> 10
|
|
'T' -> 9
|
|
in '2'..'9' -> card.digitToInt() - 1
|
|
'J' -> 0
|
|
else -> throw Exception("Invalid card")
|
|
}
|
|
}
|
|
|
|
fun typeJoker(cards: Hand): Int {
|
|
var groups: MutableMap<Char, Int> =
|
|
cards.groupBy { it }.mapValues { it.value.count() }.toMutableMap()
|
|
|
|
// If there is only one group, we return early with the highest
|
|
// value (this is to catch edge cases where there are only jokers)
|
|
if (groups.entries.size == 1) {
|
|
return 6000000
|
|
}
|
|
|
|
val jokers = groups.remove('J') ?: 0
|
|
return groups.entries.sortedByDescending { 13 * it.value + cardValueJoker(it.key) }.foldIndexed(
|
|
0
|
|
) { i, acc, entry ->
|
|
acc +
|
|
when (entry.value + if (i == 0) jokers else 0) {
|
|
5 -> 6000000
|
|
4 -> 5000000
|
|
3 -> 3000000
|
|
2 -> 1000000
|
|
1 -> 0
|
|
else -> throw Exception("Too many cards")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun handValueJoker(cards: Hand): Int {
|
|
// This is just a bit more complicated; the card value doesn't
|
|
// change much, but the *type* changes quite a bit.
|
|
//
|
|
// Rather than just giving the best type the highest value, we now
|
|
// need to take into account scenarios in which ties are broken by
|
|
// jokers. To do this, we sort the groups by the value of their
|
|
// cards and the number of them.
|
|
//
|
|
// Since the tie breaker in this case is again the value of the
|
|
// card, this is kind of recursive.
|
|
return typeJoker(cards) + cards.fold(0) { acc, label -> acc * 13 + cardValueJoker(label) }
|
|
}
|
|
|
|
fun parseHands(input: File): List<Pair<Hand, Int>> {
|
|
return input.useLines {
|
|
it
|
|
.map {
|
|
val (hand, bid) = it.split(' ')
|
|
hand.toList() to bid.toInt()
|
|
}
|
|
.toList()
|
|
}
|
|
}
|