Arrays, Slices และ Maps ในภาษา Go
บทความนี้จะมาเล่าเกี่ยวกับโครงสร้างข้อมูลชนิด arrays, slices และ maps ในภาษา Go กันครับ โดยเนื้อหาในบาความนี้อ้างอิงและสรุปมาจากหนังสือ Go in Action ของ William Kennedy, Brian Ketelsen และ Erica St Martin
Array
ในภาษา Go อาเรย์นั้นคล้ายกับหลายๆภาษา อาเรย์เป็นโครงสร้างข้อมูลที่มีขนาดเท่ากันมาเรียงต่อๆกัน ทำให้การเข้าถึงข้อมูลง่ายและเร็ว
อาเรย์ในภาษา Goarray := [5]int{10, 20, 30, 40, 50}
การประกาศตัวแปรอาเรย์ที่ที่ขนาด 5 ตัวและเป็นชนิด integer ข้อมูลที่เป็นจริงคือ อาเรย์ตำแหน่งที่ 0 ถึง 4 จะมีข้อมูลเป็น integer
นอกจากนี้เราสามารถประกาศตัวแปรอาเรย์ที่เป็นพอยเตอร์ได้ โดยใช้สัญญาลักษณ์ “*” นำหน้า
array := [5]*int{0: new(int), 1: new(int)}// กำหนดค่า
*array[0] = 10
*array[1] = 20
พอเป็นอาเรย์ของพอยเตอร์แล้ว ค่าที่ถูกเก็บในอาเรย์ก็จะเป็นพอยเตอร์ด้วย และในโค้ดนี้มีการกำหนดค่าในตำแหน่งที่ 0 และ 1 เป็นพอยเตอร์ของ interger แต่ไม่ได้ประกาศส่วนที่เหลือ ทำให้ข้อมูลที่เก็บจริงๆ จะเป็นประมาณนี้
ตำแหน่งที่ไม่ได้ถูกกำหนดไว้จะมีค่าพอยเตอร์เป็น nil หรือค่าว่าง นอกจากนี้ array ของ Go ก็สามารถกำนหดชนิดข้อมูลเป็น string, bool อื่นๆได้ และทำอาเรย์หลายมิติได้คล้ายหลายๆภาษาเลย
การส่งอาเรย์ระหว่างอีกฟังก์ชัน ในภาษา Go การส่งข้อมูลอาเรย์ไปอีกฟังก์ชันหนึ่งเป็นการ passed by value คือการก๊อปปี้ข้อมูลอาเรย์อีกชุดขึ้นมาใหม่ ถ้าเกิดขนาดอาเรย์เราขนาดใหญ่มาก การส่งข้อมูลไปอีกฟังก์ชันนึงก็คงไม่ใช่เรื่องที่ดีมากนัก เราสมารถหลีกเลี่ยงเหตุหการเหล่านี้ได้ โดยการเปลี่ยนไปใช้เป็นพอยเตอร์แทนได้
Slice
slice เป็นโครงสร้างข้อมูลรูปแบบหนึ่งของ Go คล้ายกับ array แต่มีความพิเศษกว่า มีความคล่องตัวกว่า และจัดการได้ง่ายกว่าอาเรย์ โดยโครงสร้างของ slice ประกอบไปด้วย 3 ฟิลด์ที่ต้องการ เอาไว้เข้าถึงข้อมูลที่ตัวมันเองเก็บอยู่
จากรูปเป็นโครงสร้างของ slice ที่เก็บตำแหน่งเริ่มต้นของข้อมูล (addr) เก็บความยาวของข้อมูล (length) และขนาดของ slice (capacity)
// การประกาศตัวแปร slice ที่มี length = 3 และ capacity = 5 โดยใช้คำสั่ง "make"slice := [5]int{10, 20, 30}
ถ้าหากไม่ได้กำหนด capacity เป็น 5 แล้ว ใส่แค่ element 10, 20 และ 30 capacity จะถูกกำหนดให้เท่ากับ length ของ slice นั้นก็คือ 3
นอกจากนี้ยังสามารถสร้าง slice ที่เป็นค่าว่างได้ด้วยการประกาศตัวแปรธรรมดา
var slice []int
โครงส้านข้อมูลก็จะได้แบบนี้
แต่ถ้าหากอย่างได้ slice ที่ไม่ได้เป็น nil แต่เป็น empty slice ได้โดย
slice := make([]int, 0)// หรือslice := []int{}
การเข้าถึงข้อมูล และการแก้ไขข้อมูลของ slice
การเข้าถึงข้อมูลของ slice นั้นไม่ได้ต่างจากอาเรย์มากนัก โดยสามารถเข้าถึงได้โดยตำแหน่ง เช่น slice[0], slice[1] ได้เลย
การก๊อปปี้ slice นึงไปยัง slice นึงสามารถทำได้โดย
slice := []int{10, 20, 30, 40, 50}// ก๊อปปี้ slice ไป newSlice โดยก๊อปแค่ 20 ถึง 40 เท่านั้นnewSlice := slice[1:3]// แก้ไขnewSlice[1] = 35
ผลที่ได้จากการก๊อปปี้ข้อมูลมา แล้วมาแก้ไขจะได้ข้อมูลตำแหน่งที่ 1 ของ newSlice จะเป็น 35 เช่นเดียวกับข้อมูลของ slice ตำแหน่งที่ 2 ก็จะเปลี่ยนเป็น 35 เช่นกัน เนื่องจากการสร้างตัวแปรใหม่มาเท่ากับ slice ตัวเดิมคือการชี้พอยเตอร์ไปที่ข้อมูลชุดเดียวกัน ดังนั้นการเปลี่ยนแปลงข้อมูลของฝั่งใดฝั่งหนึ่งจะกระทบอีกฝั่งด้วย
การใช้งาน function “append” ของ slice
slice := []int{10, 20, 30, 40, 50}newSlice := slice[1:3]// append ข้อมูล 60 เข้าไปใน newSlice
newSlice = append(newSlice, 60)
การ append คือการสร้าง slice ตัวใหม่ที่ชชี้ไปที่จุดเดิม แต่เพิ่มขนาดของข้อมูล แต่เมื่อขนาดของข้อมูลยังน้อยกว่า ขนาดของ slice เดิมที่ก๊อปปี้มา จะทำมันไปเขียนทับข้อมูลเดิมได้อย่างในรูป ข้อมูลตรงตำแหน่งนั้นเคยเป็น 40 หลังจาก newSlice ได้ append ข้อมูลมูลใหม่ 60 เข้าไป ทำให้ข้อมูล 40 เป็น 60
slice เป็นเหมือนอาเยร์ 1 มิติ แต่เราสามารถทำให้มันเป็นหลายมิติได้ด้วยการเก็บ addr ของ slice นึงในอีก slice นึงได้
การส่งข้อมูล slice ระหว่างฟังก์ชัน
การส่ง slice ไปอีกฟังก์ชัน เนื่องจาก slice มีโครงสร้างข้อมูลที่ประกอบไปด้วย 3 ฟิลด์ จึงทำให้สามารถส่ง slice ไปยังอีกฟังก์ชันได้เลย
Map
map เป็นอีกโครงสร้างข้อมูลรูปแบบหนึ่งของภาษา Go โดยเราสามารถจับคู่ key/value ได้ key กับ value จะเป็น int, string, … ก็ได้
แต่ว่า map ไม่ได้เรียงข้อมูของโครงสร้างเป็นลำดับแบบ slice เนื่องจากการทำงานของ map นั้นจะเก็บข้อมูล key และ value ของ map ไว้ใน hash table
map hash table นั้นจะประกอบไปด้วย bucket ที่เอาไว้แบ่งเก็บข้อมูลของ key/value โดย key/value จะลงไปอยู่ bucket อะไรก็จะขึ้นอยู่กับ hash function ของ Go ซึ่งจะทำการกระจายข้อมูลไปทั่ว bucket เพื่อลดการกระจุกตัวของข้อมูล
key ของ map จะถูกแปลงให้เป็นตัวเลข ตัวเลขนี้จะถูกใช้เพื่อเลือก bucket สำหรับเก็บหรือหา key/value ในกรณีนี้มันถูกเรียกว่า lower order bits (LOB)
จากรูปนี้จะเห็นข้างในของ bucket นั้นจะมีโครงสร้างข้อมูล 2 อัน อันแรกเป็น high order bits (HOB) มีอาเรย์ 8 ตัว อาเรย์นี้เอาไว้แยกแยะ key/value ในสอดคล้องกับแต่ละ bucket โครงสร้างข้อมูลอันที่สองเป็นอาเรย์ของ byte เอาไว้เก็บข้อมูลของ key และ value ของ map ไว้ด้วยกัน
// ประกาศตัวแปร map ชื่อ dict ที่มี key และ value เป็น string
dict := map[string]string{"Red": "#da1337", "Orange": "#e95a22"}
การเข้าถึงและกำหนดค่าข้อมูลใน map
// อ่านข้อมูล
dict["Red"] -> จะได้ #da1337// เขียนข้อมูล
dict["Red"] = "#ff0000"
การวนลูป key/valueใน map
colors := map[string]string{ "AliceBlue": "#f0f8ff",
"Coral": "#ff7F50",
"DarkGray": "#a9a9a9",
"ForestGreen": "#228b22",}for key, value := range colors { fmt.Printf("Key: %s Value: %s\n", key, value)}// ผลลัพธ์จะได้
Key: AliceBlue Value: #f0f8ff
Key: Coral Value: #ff7F50
Key: DarkGray Value: #a9a9a9
Key: ForestGreen Value: #228b22
การส่งค่า map ระหว่างฟังก์ชันนั้นจะเหมือนกัน slice เมื่อ map ปลายทางถูกแก้ไขไป map ต้นทางก็จะถูกเปลี่ยนไปด้วยเช่นกันครับ
สรุป
- อาเรย์เป็นโครงสร้างข้อมูลที่จะต้องกำหนดขนาดความยาว และสามารถเข้าถึงข้อมูลผ่านตำแหน่งของอาเรย์ได้
- slice เป็นโครงสร้างข้อมูลที่เป็นมากกว่าอาเรย์เนื่องจากเราสามารถ append ข้อมูลเข้าไปได้เรื่อยๆ ข้อมูลทั้งอาเรย์และ slice นั้นเรียงลำดับตามตำแหน่งของมัน
- map เป็นโครงสร้างข้อมูลที่เป็นคู่ key และ value เราใช้งาน map ได้หลากหลายกว่า slice เนื่องจากเรากำหนด key เองได้ map นั้นจะไม่ได้เรียง key/value ทำให้การวนลูปแสดงผล key/value ใน map นั้นอาจจะต่างกันออกไป ต่างจากอาเรย์และ slice ที่ข้อมูลนั้นจะเรียงกัน
อ้างอิง
หนังสือ Go in action ของ William Kennedy, Brian Ketelsen และ Erica St Martin
📢 มาร่วมเป็นส่วนหนึ่งในการทำให้วงการ E-Commerce ขับเคลื่อนไปข้างหน้า ส่งประวัติการทำงานพร้อมตำแหน่งงานที่คุณสนใจมาได้เลยที่อีเมล hr@sellsuki.com หรือเข้าชมเว็บไซต์ของเราที่ https://lnkd.in/gUqNHSEW 🐶