如何使用交換兩個變數的值(swap)

如何使用交換兩個變數的值(swap)

一般來說,我們常見的交換兩個變數的值,會透過一個 tmp 的變數來幫忙轉換,在 Kotlin 裡面可以透過一些簡單的方式來進行。

一般來說,我們看到的方式可以透過以下方式。

var a = 1
var b = 2
var tmp = a
a = b
b = tmp
println("a = $a, b = $b") //a = 2, b = 1

這是最基礎的方式也是最容易理解的想法。

看到官方網站有一段出現以下兩個變數的交換寫法。

also

var a = 1
var b = 2
a = b.also { b = a }

覺得很新奇,所以特地研究了一下,發現其實交換兩個變數有很多種變化,有很多方法可以實現。

透過 also、apply、with 都可以完成這個功能。

apply

var a = 1
var b = 2
a = b.apply { b = a }

with

var a = 1
var b = 2
with(a){
  a = b
  b = this
}

如果可以直接對著變數.swap提供一個方法來進行更換該有多好?

這時候就會想要用 swap 這個字來取代 scope function,因此就想到了 extension function。

fun Int.swap(a:Int, b:Int){
    a = b.apply { b = a }
}

這樣會出現一個編譯錯誤的訊息。

Val cannot be reassigned

原來在 Kotlin 每個方法傳入的參數(Parameter)都會被限定為 val (immutable) 的,因此是不能再重新指定變數,避免產生邊際效應(side effect)。

Pair

朋友 Andy 提供了另外一種思維的寫法。

fun main(args: Array<String>) {
	var a = 1
	var b = 2
  val (c, d) = a.swap(b)
	println("c = $c, d = $d")// c = 2, d = 1 
}

fun Int.swap(other: Int): Pair<Int, Int> {
    return other to this
}

相當於將兩個變數交換以後塞進另外兩個變數,這樣一來,我們再繼續思考有沒有辦法原地置換的方法?

Lambda

所以從參數著手就變成不可行了,難怪官方要透過 scope function 來處理,那我們退一步就透過改變 scope function 的名稱來進行,如果能夠把 also 改成 swap 就好了。

fun main(args: Array<String>) {
    var a = 1
    var b = 2
    a = b.swap { b = a }
    println("a = $a, b = $b")
}

fun Int.swap(s: ()-> Unit): Int{
    s()
    return this
}

Generics

可是這樣侷限於特定型態,因此又想到了泛型 (Generics),所以改寫成這樣。

fun main(args: Array<String>) {
    var a = 1
    var b = 2
    a = b.swap { b = a }
    println("a = $a, b = $b")
}

fun <T> T.swap(s: ()-> Unit): T{
    s()
    return this
}

inline function

想到 Collections functions 都會設置成 inline 方式來減少 function call 強化效能,所以我們來進行修正為 inline function 就可以保障被大量呼叫的時候,效能的提升。

fun main(args: Array<String>) {
    var a = 1
    var b = 2
    a = b.swap { b = a }
    println("a = $a, b = $b")
}

inline fun <T> T.swap(s: ()-> Unit): T{
    s()
    return this
}

這樣就完成我們最後的版本了。

參考資料

https://kotlinlang.org/docs/reference/idioms.html#swapping-two-variables