Skip to content

Commit 913ca9d

Browse files
committed
Solution 2015-11 (Corporate Policy)
1 parent 58287d4 commit 913ca9d

File tree

2 files changed

+190
-0
lines changed

2 files changed

+190
-0
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package de.ronny_h.aoc.year2015.day11
2+
3+
import de.ronny_h.aoc.AdventOfCode
4+
5+
fun main() = CorporatePolicy().run("vzbxxyzz", "vzcaabcc")
6+
7+
class CorporatePolicy : AdventOfCode<String>(2015, 11) {
8+
override fun part1(input: List<String>) = input.first().rotate()
9+
10+
override fun part2(input: List<String>) = input.first().rotate().rotate()
11+
12+
fun rule0AppliesTo(password: String) = password.matches("[a-z]{8}".toRegex())
13+
14+
// must include one increasing straight of at least three letters
15+
fun rule1Applies(password: String): Boolean {
16+
var applies = false
17+
password.windowed(3) {
18+
if (it[2] == it[1].inc() && it[1] == it[0].inc()) {
19+
applies = true
20+
}
21+
}
22+
return applies
23+
}
24+
25+
private val lettersToSkip = listOf('i', 'o', 'l')
26+
private val ambiguousLettersRegex = lettersToSkip.joinToString("|").toRegex()
27+
28+
// may not contain the letters i, o, or l
29+
fun rule2Applies(password: String) = !password.contains(ambiguousLettersRegex)
30+
31+
// must contain at least two different, non-overlapping pairs of letters
32+
fun rule3Applies(password: String): Boolean {
33+
var pairs = 0
34+
var lastWasAPair = false
35+
password.windowed(2) {
36+
if (lastWasAPair) {
37+
// no overlappings
38+
lastWasAPair = false
39+
} else {
40+
if (it[0] == it[1]) {
41+
pairs++
42+
lastWasAPair = true
43+
}
44+
}
45+
}
46+
return pairs >= 2
47+
}
48+
49+
private fun String.rotate(): String {
50+
require(rule0AppliesTo(this)) { "a password has to be exactly 8 lowercase letters: '${this}" }
51+
52+
var newPassword = this
53+
do {
54+
newPassword = newPassword.inc(lettersToSkip)
55+
} while (newPassword.notAllRulesApply())
56+
57+
check(rule0AppliesTo(newPassword)) { "a password has to be exactly 8 lowercase letters: '$newPassword" }
58+
return newPassword
59+
}
60+
61+
private fun String.notAllRulesApply() = !rule1Applies(this) || !rule2Applies(this) || !rule3Applies(this)
62+
}
63+
64+
fun String.inc(toSkip: List<Char> = emptyList()): String {
65+
val highestChar = 'z'
66+
require(highestChar !in toSkip) { "incrementation only works correct if the highest char is not to skip" }
67+
68+
var suffix = ""
69+
var incIndex = lastIndex
70+
while (incIndex >= 0) {
71+
val prefix = substring(0..<incIndex)
72+
if (get(incIndex) == highestChar) {
73+
suffix = "a$suffix"
74+
incIndex--
75+
} else {
76+
var incChar = get(incIndex).inc()
77+
while (incChar in toSkip) {
78+
incChar = incChar.inc()
79+
}
80+
return prefix + incChar + suffix
81+
}
82+
}
83+
throw StringOverflowException("String '$this' cannot be incremented without making it longer.")
84+
}
85+
86+
class StringOverflowException(message: String) : Exception(message)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package de.ronny_h.aoc.year2015.day11
2+
3+
import io.kotest.assertions.throwables.shouldThrow
4+
import io.kotest.core.spec.style.StringSpec
5+
import io.kotest.data.forAll
6+
import io.kotest.data.row
7+
import io.kotest.matchers.shouldBe
8+
9+
class CorporatePolicyTest : StringSpec({
10+
11+
val day11 = CorporatePolicy()
12+
13+
"Passwords must be exactly eight lowercase letters" {
14+
forAll(
15+
row("abcdefgh", true),
16+
row("abcdefg", false),
17+
row("aBcdefgh", false),
18+
row("abcdefghi", false),
19+
) { password, expected ->
20+
day11.rule0AppliesTo(password) shouldBe expected
21+
}
22+
}
23+
24+
"Passwords must include one increasing straight of at least three letters" {
25+
forAll(
26+
row("abc", true),
27+
row("abbcdde", true),
28+
row("aaa", false),
29+
row("aabc", true),
30+
row("abcc", true),
31+
row("aabcc", true),
32+
row("aabbcc", false),
33+
) { password, expected ->
34+
day11.rule1Applies(password) shouldBe expected
35+
}
36+
}
37+
38+
"Passwords may not contain the letters i, o, or l" {
39+
forAll(
40+
row("abc", true),
41+
row("hij", false),
42+
row("mno", false),
43+
row("lmn", false),
44+
) { password, expected ->
45+
day11.rule2Applies(password) shouldBe expected
46+
}
47+
}
48+
49+
"Passwords must contain at least two different, non-overlapping pairs of letters" {
50+
forAll(
51+
row("abcd", false),
52+
row("aabb", true),
53+
row("aabbcc", true),
54+
row("aaaa", true),
55+
row("aaab", false),
56+
) { password, expected ->
57+
day11.rule3Applies(password) shouldBe expected
58+
}
59+
}
60+
61+
"hijklmmn" {
62+
val password = "hijklmmn"
63+
day11.rule1Applies(password) shouldBe true
64+
day11.rule2Applies(password) shouldBe false
65+
}
66+
67+
"abbceffg" {
68+
val password = "abbceffg"
69+
day11.rule3Applies(password) shouldBe true
70+
day11.rule1Applies(password) shouldBe false
71+
}
72+
73+
"increment a" {
74+
"a".inc() shouldBe "b"
75+
}
76+
77+
"increment az with carry-over" {
78+
"az".inc() shouldBe "ba"
79+
}
80+
81+
"increment aaazzz with carry-over" {
82+
"aaazzz".inc() shouldBe "aabaaa"
83+
}
84+
85+
"zzzz cannot be incremented without making the String longer" {
86+
val exception = shouldThrow<StringOverflowException> {
87+
"zzzz".inc()
88+
}
89+
exception.message shouldBe "String 'zzzz' cannot be incremented without making it longer."
90+
}
91+
92+
"part 1: The next password after abcdefgh is abcdffaa" {
93+
day11.part1(listOf("abcdefgh")) shouldBe "abcdffaa"
94+
}
95+
96+
"part 1: The next password after ghijklmn is ghjaabcc" {
97+
day11.part1(listOf("ghijklmn")) shouldBe "ghjaabcc"
98+
}
99+
100+
"part 2 is two times part 1" {
101+
val input = listOf("ghijklmn")
102+
day11.part2(input) shouldBe "ghjbbcdd"
103+
}
104+
})

0 commit comments

Comments
 (0)