今天我們來探討跟 Array 相關的話題吧!當然不是每個語言都有內建 Array,而是有其他類似的,我們會一起來看看有什麼樣的不一樣囉!(今天比較像是自由探索,哈哈!)
package com.ryan.scala.oop
import scala.collection.mutable.ArrayBuffer
object Solution {
def main(args: Array[String]) {
val intArray1 = new Array[Int](5)
println(intArray1.mkString(","))
intArray1(0) = 100
println(intArray1.mkString(","))
val intArray2 = ArrayBuffer[Int]()
intArray2 += 1
intArray2 += (2, 3)
println(intArray2.mkString(","))
val intArray3 = ArrayBuffer[Int](4, 5, 6)
val intArray4 = intArray2 ++= intArray3
println(intArray4.mkString(","))
}
}
intArray1
雖然定義的時候是 val
,但是那是說 intArray1
只能一直是這個 Array,但是 Array 的內容是可以修改的 ( Array[Int]
,表示這是一個 Int
的 Array,這個部分就是 Scala 的 Generics,以後會再提到),例如這個例子我們把第一個元素改成 100
( intArray1(0) = 100
),而此 Array 的第一個元素真的是可以被改成 100
。Scala 的 Array 當定義之後,Size 就不能再修改了。ArrayBuffer
(要 import scala.collection.mutable.ArrayBuffer
),像是 intArray2
,這裡不用 new
是因為我們先前講過的 Companion Object 中的 apply
Method。而 intArray2
可以看到我們會透過 +=
來把元素加到裡頭。越先加進來的 index 越小。假使今天有兩個 ArrayBuffer
要接在一起,例如 intArray2
跟 intArray3
要接在一起就要使用 ++=
。因為 ArrayBuffer
可以改變長度,所以也提供了像是 remove
, insert
等等 Method,而與 Array 之間也可以透過 toArray
, toBuffer
互相轉換囉!而不管是 Array
還是 ArrayBuffer
都有提供像是 sum
, max
, min
等等方法可以讓你在一些情境下更方便使用囉!最後我們來看當我們想把全部元素印出來,彼此之間用逗號隔開時,我們用了 mkString(",")
,這會 iterate 全部的元素並且以逗號來連接各元素後,回傳 String
囉!更多可以參考這裡和這裡。list1 = ['a', 'b', 1, 2]
print(list1[1:3])
list1.append('c')
print(list1)
list1.remove('b')
print(list1)
list2 = list1
list2[0] = 'd'
print(list1)
print(list2)
list1
。因為可變,所以不管是添加 ( list1.append('c')
)或刪除元素 ( list1.remove('b')
)也都可以,而每次增加元素的時候,底層的 Array 就會改變大小,當然 Python 本身有做一些優化來避免不斷去變更長度。而在用 index 取值的時候,因為底層是 Array,所以效能跟一般的 Array 類似,也就是跟你 index 值的大小無關。假使我們要取 list 中的某一段可以以 <list>[<start>:<stop>]
的方式,例如 list1[1:3]
會取得以 list1[1]
, list1[2]
組成的 list,也就是 [‘b’, 1]
。要注意的是,假使我們把 list1
賦予給 list2
,那麼修改 list2[0]
, list1[0]
也會跟著看到改變後的結果囉!可以思考看看為什麼會這樣。(提示:剛才說 list 裡頭存的是 Pointer)['b', 1]
['a', 'b', 1, 2, 'c']
['a', 1, 2, 'c']
['d', 1, 2, 'c']
['d', 1, 2, 'c']
package main
import "fmt"
func main() {
var array1 [5]string
array1[0] = "a"
array1[1] = "b"
array1[2] = "c"
array1[3] = "d"
array1[4] = "e"
slice1 := make([]string, 5, 5)
slice1[0] = "a"
slice1[1] = "b"
slice1[2] = "c"
fmt.Println(len(slice1))
fmt.Println(cap(slice1))
slice2 := array1[2:4]
slice2[0] = "C"
fmt.Println(len(slice2))
fmt.Println(cap(slice2))
fmt.Println(array1)
slice3 := make([]string, len(slice2))
copy(slice3, slice2)
slice3[0] = "A"
fmt.Println(slice2)
fmt.Println(slice3)
ptr
),也就是真正存放資料的 Array,由此可以知道 Slice 其實是一個 Array 的 “某一段”,第二個是 len
,也就是這個 “某一段” 裡頭有多少元素,第三個是 capacity
,也就是從 ptr
指到的地方開始,一直到最後一個元素到底共有幾個。由於 Slice 存放的是 Pointer,會不會有別的 Slice 也指向同一個 Array 呢?答案是會的喲!所以可能會造成某個 Slice 修改了某值,而另一個 Slice 也看到了修改後的結果,造成非預期的影響。如果要解決這個問題,那麼我們就要使用內建的 copy
,讓不同 Slice 都有自己指向的獨一無二 Array,就能避免這個問題。var array1 [5]string
宣告一個長度是 5 的 Array array1
,並且將每個元素都賦值。而 slice1
是透過 make
來創造 ( make
用來創造 slice
, map
, 以及 channel
。後兩者是什麼以後會提到囉),而 make([]string,5,5)
表示創造一個 Slice of string 並且 len
跟 capacity
都是 5
。再來我們一樣給 slice1
賦值,此時透過 len
和 cap
分別得到 slice1
的 len
是 5
,而 cap
也是 5
。為什麼 len
是 5
不是 3
呢?因為這時候其實沒被特別賦值的部分在被宣告的時候也有初始值唷!這時我們讓 slice2
是 array1[2:4]
,也就是把 slice2
的 ptr
指到 array1[2]
之處,這時可以看到 slice2
的 len
是 2
(因為 array1[2:4]
有 3 個元素),cap
是 3
(因為從 array1[2]
開始到最尾端還有 3 個元素)。這時我們試著改變 slice2[0] = "C"
,也就是 array1[2]
的位置,結果發現印出 array1
的時候,確實值也被改變囉!(就是因為 slice 只是用一個 Pointer 指到 array)。如果要避免這個狀況就要像 slice3
用 copy
來創造出另一個新的空間,這樣一來, slice3
跟 slice2
就互不干擾了!5
5
2
3
[a b C d e]
[C d]
[A d]
fn main() {
let mut array1 = [1, 2, 3, 4, 5];
let mut array2 = array1;
array2[0] = 6;
for x in array1.iter() {
println!("{}", x);
}
let mut vec1 = Vec::new();
vec1.push(1);
let mut vec2 = vec![1, 2, 3];
match vec2.pop() {
Some(x) => println!("{}", x),
None => println!("None"),
}
println!("{}", vec2.pop().unwrap());
vec2.pop().unwrap();
// vec2.pop().unwrap();
let slice1 = &mut[1, 2, 3, 4, 5];
slice1[0] = 6;
}
let mut
來進行變數綁定唷!Array 本身不能直接被 iterate,必須像我們之前所說,要像是 array1.iter()
變成一個迭代器才可以。那麼今天如果我們需要一個比較彈性,可以調整長度,但又像是 Array,我們就會選用 Vector。Vector 因為比較靈活,所以也滿常被使用的。建立 Vector 的方式有兩種,第一種是我們透過 Vec::new()
,先建立出一個空的 Vector 後,再把值透過 push
放入 Vector。第二種是透過 vec!
這個 Macro 來建立。至於使用上跟 Array 差不多,都是可以修改資料,只要一開始用 mut
做綁定。我來看一個比較有趣的是 vec2.pop()
,這個 Function 會從尾端把元素給取出來 (Vector 的長度就會減一),那是如果一直 pop()
,最後沒值會怎樣呢?答案是不會怎樣,但是我要怎麼知道已經沒值了?答案是 vec2.pop()
會回傳的是一個 Option
的 enum type,沒錯,我們之前有講過。所以有值的話,就會被包在 Some
,不然就會回 None
囉!另外來講下,因為每次如果遇到像 Option
這種,用 match
的邏輯都差不多的話,那麼每次都寫成像上述的 Code 就太累贅,所以 Rust 提供另一個 Function 叫 unwrap
,像是 vec2.pop().unwrap()
,如果有值就會直接取出來給你,會自動判斷是不是 Some
。但是如果是 None
的話就會直接送你一個 Panic 啦!如果你覺得直接送 Panic 太殘忍,想要改給一個 default 值的話,可以用 unwrap_or(<default value>)
囉!有興趣可以參考這裡。slice1 = &mut[1,2,3,4,5]
,是說 slice1
是一個指向 [1,2,3,4,5]
這個 Array 的 reference (透過 &
),而 mut
表示可以修改的權限。然後我們就可以像修改 Array 一樣來修改 Slice 囉!今天把四個語言跟 Array 相關的資料結構大致上掃過一遍,至於每個語言在不同資料結構詳細的用法,大家有興趣我相信都可以在網路上找到很多資訊,主要就是讓大家一次可以去看看不同語言的不同與相同之處。那麼我們就明天見囉!