[Day 8] 字串 & leetcode 相關練習
今天要來講 Kotlin 在字串上的處理,字串處理算是平日處理商業邏輯很常碰到的,所以不可掉以輕心。
substring
subtring 跟以往 Java 使用的方式其實差不多
- 這裡首先用 indexOf 取得
‘
的 index - until 這個 ranges function,讓我們寫起來更有語意上的感覺,也就是要取 index 0 ~ 3 的子字串,也就是 "Tim"
- 下面兩種寫法就跟 Java 內的方式一樣
val str = "Tim's string"
val index = str.indexOf('\'')
println(str.substring(0 until index)) // Tim
println(str.substring(0)) // Tim's string
println(str.substring(0, index)) // Tim
split
這裡使用了 split 依據,
逗號把字串拆成一個 array
每個變數在賦予 array 內的內容
val names = "tim, jerry, anna"
val data = names.split(',') // ["tim", "jerry", "anna"]
val tim = data[0] // "tim"
val jerry = data[1] // "jerry"
val anna = data[2] //"anna"
解構 (destructuring)
之前的程式也可以寫成這樣
解構函式在寫 JS 的時候我覺得真的是一個很方便的東西
val (tim, jerry, anna) = names.split(',')
如此就可以依順序直接把值塞入這三個變數!
這裡介紹的是 Collections 的解構(像是 List, Map..等等),物件的解構之後會再講到 Class 再講
在 Kotlin 裡面,如果不需要的參數會用底線來表示
像是這個例子不需要 data[1] 的資料,所以用底線表示不賦予值
val (tim2, _, anna2) = names.split(',')
Nullable 的時候
以往寫程式的時候,最常使用的就是要判斷字串是不是空或 null,還是含有空白的字串
Kotlin 很貼心,幾乎都內建好方法了,搭配之前提到的 ?.
更方便,終於可以不用依靠一些 lib 了...
這裏的變數 s 是一個可以 null 的字串
- isEmpty() 指得是真的長度為 0 的空字串
- 因為 isEmpty(), isNotEmpty() 並沒有判斷 null,所以需要
? Safe Calls
符號來判斷,如果是 null 就不會呼叫下去 - isNullOrEmpty() 就會包含 null 的判斷所以不需要 ? Safe Calls 符號來判斷 null
val s: String? = "kotlin"
// isEmpty, isNotEmpty 要有 ?, isNullOrEmpty 不需要
println(s?.isEmpty()) // length == 0
println(s?.isNotEmpty())
println(s.isNullOrEmpty())
isBlank() 指得是有空白的字串或長度為 0 的空字串
其他就跟上面解釋的差不多意思
// isBlank, isNotBlank 要有 ?, isNullOrBlank 不需要
println(s?.isBlank()) // length == 0 或有空白
println(s?.isNotBlank())
println(s.isNullOrBlank())
字串相接
要連接字串很簡單,一樣用 +號或 plus 來串接字串
println(s + " very good")
println(s.plus(" very good"))
println(s.plus(" very good").plus("haha"))
println("$s very good")
StringBuilder
上面是串接很少字串的狀況
但如果今天是多字串的串接,例如跑了一個 for 迴圈要把字串串起來
跟在 Java 一樣,String 是 immutable 的, 串接字串用 + 或 plus 都會造成 String 新的記憶體不斷的被創造出來。
在這種狀況下還是使用 StringBuilder 比較好
val builder = StringBuilder()
builder.append("Aloha")
.append(" ")
.append("Tim")
String template 會幫你優化成 StringBuilder
值得注意的是,如果是使用 String template 串接的話,去看 byte code 的話,Kotlin 也會自動幫我們優化成 StringBuilder!
val a = "Aloha"
val b = " "
val c = "Tim"
val d = "$a$b$c"
// 會幫我們轉成 (new StringBuilder()).append(a).append(b).append(c).toString();
但在這個例子,byte code 裡面還是是使用 +號,因為這裡只是兩個字串的相接,並不需要 StringBuilder
println("$s very good")
// var19 = s + " very good"; 只有一次的字串串接, 還是會只用 +
最後,來讓我們練習個 leetcode 吧!
Leetcode 344. Reverse String
https://leetcode.com/problems/reverse-string/
難度: EASY
這題很簡單其實就是要我們做字串反轉,但當然不能呼叫 reversed() api
解法跟 Java 類似可以用 two points 的方式,字串頭尾互相對調,最後就會是反轉的字串了
fun reverseString(s: CharArray): Unit {
var left = 0
var right = s.size - 1
while (left < right) {
val temp = s[left]
s[left] = s[right]
s[right] = temp
left++
right--
}
}
或是跑 for 從 0 到 index 的一半位置,對 index 和 (lastIndex - index) 做對調,這樣也會是頭尾對調
fun reverseString(s: CharArray): Unit {
if (s.isEmpty()) return
for (i in 0..s.lastIndex/2) {
val temp = s[i]
s[i] = s[s.lastIndex - i]
s[s.lastIndex - i] = temp
}
}
這題很簡單,讓我們來看下一題
Leetcode 468. Validate IP Address
https://leetcode.com/problems/validate-ip-address/
難度: MEDIUM
這題算是很生活化的 leetcode 題目吧!
總結上面的描述和例子,可以歸納出
題目描述了蠻多 ipv4 和 ipv6 的限制狀況
- ipv4 由 4 組數字用 . 號隔開:
- 內容都是數字會介於 0~255,,所以要排除有文字的例外狀況
- ipv6 由 8 組文數字用 : 隔開:
- 內容是 16進位字串
- 所以內容會有
- 數字 和 字母 'a' 到 'f' 和大寫的 字母 'A' 到 'F' (因為 16進位是到 F )
- 可以 0 開頭
- 長度可以 1~4
以下是解法,首先在主函數用 .
符號或 :
符號來判斷要檢查的是 ipv4 還是 ipv6,其他狀況都回傳 "Neither",這裡會用 try catch 包起來是因為,檢查的內容我有使用到字串轉數字,不合法的輸入時會直接丟出 exception,所以統一處理掉。
fun validIPAddress(IP: String): String {
return try {
when {
IP.count { it == '.' } == 3 -> validateIPv4(IP)
IP.count { it == ':' } == 7 -> validateIPv6(IP)
else -> "Neither"
}
} catch (e: Exception) {
"Neither"
}
}
檢查 ipv4,依照之前分析的,如果不是數字,it.toInt()
回直接拋出 exception
i !in 0..255
檢查數字是否落在 0~255
it != i.toString()
把原本的 ip 和 ip 轉成數字再轉成字串做比較,看是不是轉換後還是一樣,用來處理一些特殊的 test case,像是 01.01.01.01
// it != i.toString() 處理一些特殊的 test case, 像是 01.01.01.01
private fun validateIPv4(IP: String): String {
IP.split('.').forEach {
val i = it.toInt()
if (i !in 0..255 || it != i.toString()) {
return "Neither"
}
}
return "IPv4"
}
ipv6 的部分就判斷
內容的文數字有沒有超過長度4 it.length > 4
然後還有用正規表示式來判斷內容是否合法 Regex("^[0-9A-Fa-f]+\$")
private fun validateIPv6(IP: String): String {
IP.split(':').forEach {
if (it.length > 4 || !it.contains(Regex("^[0-9A-Fa-f]+\$"))) {
return "Neither"
}
}
return "IPv6"
}
以下是完整程式碼
class Solution {
fun validIPAddress(IP: String): String {
return try {
when {
IP.count { it == '.' } == 3 -> validateIPv4(IP)
IP.count { it == ':' } == 7 -> validateIPv6(IP)
else -> "Neither"
}
} catch (e: Exception) {
"Neither"
}
}
// it != i.toString() 處理一些特殊的 test case, 像是 01.01.01.01
private fun validateIPv4(IP: String): String {
IP.split('.').forEach {
val i = it.toInt()
if (i !in 0..255 || it != i.toString()) {
return "Neither"
}
}
return "IPv4"
}
private fun validateIPv6(IP: String): String {
IP.split(':').forEach {
if (it.length > 4 || !it.contains(Regex("^[0-9A-Fa-f]+\$"))) {
return "Neither"
}
}
return "IPv6"
}
}
有做了些練習是不是覺得比較踏實呢
今天字串的部分就講到這!謝謝大家我們明天見!
今日練習的程式在這: 請點我