Kotlin in Advent Of Code 2022 — Day3

Andy Lu
7 min readDec 8, 2022

Day3 ## Rucksack Reorganization

完整的題目可參考:Day 3 — Advent of Code 2022

Part I

每一行都代表一個背包,而每一個背包都含有兩個隔層,我們需要找出這個背包所裝的物品類型,而物品的類型為兩個隔層相同的字元。

以測試內容來看,第一行的 vJrwpWtwJgWrhcsFMMfFFhFp 前半部為 vJrwpWtwJgWr ,後半部為 hcsFMMfFFhFp,其中相同的字元為 p

將每一行相同的字元找出來之後,將所有字元轉成整數,並加總起來。

而字元轉成整數的規則為 az -> 126,AZ -> 2752。

vJrwpWtwJgWrhcsFMMfFFhFp
jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
PmmdzqPrVvPwwTWBwg
wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
ttgJtRGJQctTZtZT
CrZsJsPPZsGzwwsLwLmpwMDw

解題思路:

  1. 將每一行 String 整齊的分成兩個 Sting,輸出的型別為 List<String>
  2. 拿 List 的第一個元素,也就是前半部的 String 內的所有字元拿去跟後半部的字元作比對,找出存在兩部分的字元。
  3. 將前一步驟取得的字元,查表得到對應的整數值。
  4. 重複 1~3。
  5. 將所有取得的整數值加總起來。

實作時間:

我們先假設只有一行字串,也就是 vJrwpWtwJgWrhcsFMMfFFhFp

  1. 將字串分成兩部分,可以使用 chunked() 來將輸入切成兩半,若我們要將字串整齊切成兩半,只需要使用字串的長度一半當作 chunked() 的參數,就能夠輕鬆的切成兩半。string.chunked(it.length / 2),使用 chunked() 得到的結果為 List。
  2. 得到 List 之後,拿 List 的第一個元素裡的所有字元去跟 List 第二個元素作比較。可以使用 contains() ,為了要找出是哪一個元素,使用 filter 將所有比對成功的字元濾出來,compartment[0].filter { c: Char -> compartment[1].contains(c) }
  3. 得到字元後,接下來要將字元轉換成整數值。在這邊有兩點要注意,1. 用 filter 得到的值會是一個 List,2. 字元轉整數是按照順序,有沒有方法可以快速的轉換。
  • 因為字元轉整數是按照小寫字母,大寫字母的順序排列,我想到可以將這些字元放在 List 內,之後用 indexOf() 來找出 Index。
  • 有沒有什麼方法能夠很快速的將字元塞入至 List 內呢?我想到可以使用 CharRange ,如:('a' .. 'z') 代表的是一組由 a 到 z 的字元組,只需要加上 toList() 就能夠轉換成 List 了。所以由於我們需要 az, AZ,只需要將兩個元素分別用 CharRange 填入,並分別轉換成 toList() 即可。不過這樣的型別是 List<List>,我們需要的可是 List ,只要再呼叫 flatten() 就能夠將List<List> 壓扁成 List。listOf(('a'..'z').toList(), ('A'..'Z').toList()).flatten() ,之後就可以使用 indexOf() 取得其 Index,不過由於 Index 是從 0 開始計算,所以我們需要將得到的 Index 加上 1。
  • 由於 filter 的結果是一個 List,為了取得單一的值,可以使用 take(1).single()

所以當我們要處理一行字串時,我們的程式碼如下:

val ansChar = listOf(('a'..'z').toList(), ('A'..'Z').toList()).flatten()
val input = "vJrwpWtwJgWrhcsFMMfFFhFp"
input.chunked(input.length / 2)
.let { compartment ->
compartment[0].filter { c: Char -> compartment[1].contains(c) }
.map { ans: Char -> ansChar.indexOf(ans) + 1 }
.take(1)
.single()
}

如果要處理一個 List<String>,我們只要將這段程式碼使用 map 包起來,如此就能夠將 List<String> 轉換成 List<Int> ,最後如果需要計算其總和,只要在最後使用 sum() ,就能夠計算 List 內所有值得加總。

結果如下:

fun part1(input: List<String>): Int {
return input.map {
it.chunked(it.length / 2)
.let { compartment ->
compartment[0].filter { c: Char -> compartment[1].contains(c) }
.map { ans: Char -> ansChar.indexOf(ans) + 1 }
.take(1)
.single()
}
}.sum()
}

使用 map().sum()可以替換成 sumOf(),所以可以將上面的程式碼改成使用 sumOf():

```kotlin
fun part1(input: List<String>): Int {
return input.sumOf {
it.chunked(it.length / 2)
.let { compartment ->
compartment[0].filter { c: Char -> compartment[1].contains(c) }
.map { ans: Char -> ansChar.indexOf(ans) + 1 }
.take(1)
.single()
}
}
}

Part II

與 Part I 類似,只不過現在是要從三段字串之中,找到共同擁有的字元。

解題思路:

  1. 將輸入的字串,每三行分隔出來,所以我們應該會得到 List<List> 的結果。
  2. 同樣先利用單一的輸入 List 來處理。與 Part I 相似,我們同樣可以使用 contains() 來比對相同的字元,不過這邊要同時與其與兩個字串比對。
  3. 接下來的動作就與 Part I 的 3~5 相同,這邊就不贅述。

實作時間:

  1. 將輸入 List 按每三個字串切割,我們同樣可以使用 chunked() 來切割。因為每段包含著三個字串,所以我們可以直接使用 chunked(3) 取得 List<List> ,其中內層的 List 大小一定為 3。
  2. 先使用一個 List 來作處理,所以我們只需要將 List 的第一個字串裡的每一個字元拿去與其他兩個字串使用 contains() 比對。因為同樣要取得比對到的字元,這邊也是選擇使用 filter 來將比對到的字元濾出。compartment[0].filter { c: Char -> compartment[1].contains(c) && compartment[2].contains(c) }
  3. 後面的步驟同樣也是將取得的字元轉換成整數,並取得其值。
  4. 使用 sumOf 處理所有的 List<List>。

最後的結果如下:

fun part2(input: List<String>): Int {
return input.chunked(3).sumOf {
it.let { compartment ->
compartment[0].filter { c: Char -> compartment[1].contains(c) && compartment[2].contains(c) }
.map { ans: Char -> ansChar.indexOf(ans) + 1 }
.take(1)
.single()
}
}

以上就是第三天的答案以及思路,如果有錯誤以及有更好的想法,歡迎與我分享,謝謝各位。

github: https://github.com/andyludeveloper/Kotlin-in-Advent-of-Code-2022

--

--

Andy Lu

Android/Flutter developer, Kotlin Expert, like to learn and share.