Advance Clojure Command
ในบทความนี้เราจะพูดถึง Data structure ต่างๆที่เป็นจุดเด่นของ Clojure ได้แก่ Vector Maps และ Set เป็นต้น
Vector
การสร้าง vector สามารถทำได้ 2 วิธี
เราจะทดลองสร้าง vector ว่าง ๆ ขึ้นมา โดยใช้วิธีแรกสามารถทำได้โดย
(vector)
;=> []
ส่วนวิธีที่สองจะสั้นกว่าวิธีแรก เราจะสร้าง vector ที่มีสมาชิก 1 2 3 สามารถทำได้โดย
[1 2 3]
;=> [1 2 3]
คำสั่ง conj ใช้เมื่อเราต้องการนำ collection มารวมกัน เช่น เพิ่มสมาชิกเข้าไปใน vector , เพิ่ม vector เข้าไปใน vector , เพิ่ม vector เข้าไปใน map , เพิ่มสมาชิกเข้าไปใน set
เราสามารถเพิ่มสมาชิกเข้าไปใน vector ได้ด้วยการใช้คำสั่ง conj สมาชิกที่เพิ่มเข้ามาจะไปอยู่หลังสุดของ vector
เราจะทดลองเพิ่ม 3 4 เข้าไปใน vector [1 2] สามารถทำได้โดย
(conj [1 2] 3 4)
;=> [1 2 3 4]
คำสั่ง conj สามารถใช้เพิ่มสมาชิกเข้าไปใน list ได้ด้วย โดยสมาชิกที่เพิ่มเข้าไป จะไปเป็นสมาชิกตัวแรกของ list เช่น ต้องการเพิ่ม 4 เข้าไปใน list (1 2 3)
(conj ‘(1 2 3) 4)
;=> (4 1 2 3)
นอกจากเพิ่มสมาชิกธรรมดาเข้าไปได้แล้ว เรายังเพิ่ม vector เข้าไปใน vector ได้ด้วย
ตัวอย่าง ต้องการเพิ่ม vector [5 6] เข้าไปใน vector [[1 2] [3 4]]
(conj [[1 2] [3 4]] [5 6])
;=> [[1 2] [3 4] [5 6]]
vector นั้นเป็น collection ที่มีอาจจะมีสมาชิกหลายตัว เราจึงมีฟังก์ชั่นที่ใช้ระบุตำแหน่งของ vector ที่เราต้องการนำไปใช้ให้นำไปใช้งานได้ง่าย ๆ
คำสั่ง first ใช้สำหรับบอกสมาชิกตัวแรกของ vector
เช่น ต้องการหาสมาชิกตัวแรกของ vector [1 2 3 4 5]
(first [1 2 3 4 5])
;=> 1
คำสั่ง rest ใช้สำหรับบอกสมาชิกทุกตัว ยกเว้นตัวแรกของ vector
เช่น ต้องการหาสมาชิกที่ไม่ใช่ตัวแรกของ vector [1 2 3 4 5]
(rest [1 2 3 4 5])
;=> (2 3 4 5)
คำสั่ง pop ใช้สำหรับเอาสมาชิกตัวสุดท้ายของ vector ออก
เช่น ต้องการหาสมาชิกทุกตัว ยกเว้นตัวสุดท้าย vector [1 2 3 4 5]
(pop [1 2 3 4 5])
;=> [1 2 3 4]
คำสั่ง peek ใช้สำหรับบอกสมาชิกตัวสุดท้ายของ vector
เช่น ต้องการหาสมาชิกตัวสุดท้ายของ vector [1 2 3 4 5]
(peek [1 2 3 4 5])
;=> 5
คำสั่ง subvec ใช้สำหรับค้นหาสมาชิกตั้งแต่ค่า start ถึงค่า end ใน vector
เช่น ต้องการหาสมาชิกที่อยู่ระหว่างเลข 2 และ 4 ใน vector [1 2 3 4 5 6 7]
(subvec [1 2 3 4 5 6 7] 2 4)
;=> [3 4]
จากตัวอย่าง ค่า start คือ 2
ค่า end คือ 4
จะสังเกตว่า output ออกมาไม่รวมค่า start แต่จะรวมค่า end
ในกรณีที่เราใส่ vector และใส่ค่าตามหลังมาค่าเดียว โปรแกรมจะมองว่าเราใส่แต่ค่า start ไม่ใส่ค่า end ทำให้แสดงผลตั้งแต่ start ไปจนจบ vector
เช่น ต้องการหาสมาชิกที่มากกว่า 2 ใน vector
(subvec [1 2 3 4 5 6 7] 2)
;=> [3 4 5 6 7]
คำสั่ง replace ใช้สำหรับการแทนที่ index ของ vector ที่กำหนด
เช่่น ต้องการเปลี่ยน vector ตัวเลขเป็น vector ภาษาอังกฤษ
(replace [:zeroth :first :second :third :fourth] [0 2 4 0])
;=> [:zeroth :second :fourth :zeroth]
จากผลลัพธ์ vector ตัวเลข จะเปลี่ยนแปลงเป็น vector ภาษาอังกฤษ แสดงว่า vector แรก จะเป็น vector เก็บคำที่ต้องการนำไปแทนที่
ส่วน vector ด้านหลัง จะเก็บตำแหน่งของ vector แรกที่ต้องการนำไปแทนที่
สำหรับบางคนอาจจะยังไม่เข้าใจ ลองดูอีกตัวอย่างนะครัช
(replace [10 9 8 7 6] [0 2 4])
;=> [10 8 6]
จากผลลัพธ์ จะเห็นว่า 10 อยู่ตำแหน่งที่ 0 ใน vector แรก และตัวแรกใน vector หลัง คือ 0 ผลลัพธ์ตัวแรกจึงออกมาเป็นค่า 10
8 อยู่ตำแหน่งที่ 2 ใน vector แรก และตัวที่สองใน vector หลัง คือ 2 ผลลัพธ์ตัวที่สองจึงออกมามีค่า 8
6 อยู่ตำแหน่งที่ 4 ใน vector แรก และตัวที่สามใน vector หลัง คือ 4 ผลลัพธ์ตัวที่สามจึงออกมามีค่า 8
คำสั่ง assoc ใช้สำหรับการแทนที่ index ที่เรากำหนดได้เลย
เช่น ต้องการเปลี่ยน index ที่ 0 ของ vector เป็น เลข 10
(assoc [1 2 3] 0 10)
;=> [10 2 3]
แต่ถ้าเราใส่ค่า index มากกว่าจำนวน index ที่ vector มีล่ะ ??
(assoc [1 2 3] 3 10)
;=> [1 2 3 10]
คำสั่งจะคิดว่าเราต้องการเพิ่มค่าเข้าไป จึงเพิ่ม 10 เข้าไปใน vector
แต่ถ้าเราใส่จำนวน index เข้าไปมากกว่าจำนวน index vector+1 จะ error เช่น
(assoc [1 2 3] 4 10)
;=> java.lang.IndexOutOfBoundsException (NO_SOURCE_FILE:0)
Maps
โครงสร้างข้อมูลแบบ Maps จะเหมือน Array ที่ใช้ key ในการเข้าถึงค่า การทำงานคล้ายๆกับ Dictionary ใน Python มาลองดูตัวอย่างกัน
(def person {:name “John” :age 30})
person
;=> {:name “John”, :age 30}
หมายความว่าในตัวแปร person มี 2 key คือ :name กับ :age โดยมีค่าเท่ากับ “John” และ 30 ตามลำดับ
เราสามารถเรียกดูค่าใน key ได้ดังนี้
(:name person)
;=> “John”
(:age person)
;=> 30
เราสามารถเพิ่ม key ได้ดังนี้
(assoc person :job “writer”)
;=> {:job “writer”, :name “John”, :age 30}
โปรดจำไว้ว่า assoc นี้ return map ตัวใหม่ออกมาและไม่ได้แก้ไขตัว person ตัวเดิม
(assoc person :job “writer”)
;=> {:job “writer”, :name “John”, :age 30}
Person
;=>{:name “John”, :age 30}
ถ้าหาก assoc กับ key ที่มีอยู่แล้ว จะกลายเป็นการแก้ไขข้อมูลใน key ใหม่
(assoc person :name “Jake”)
;=>{:name “Jake”, :age 30}
เราสามารถใช้ dissoc เพื่อลบ key ได้ เช่น
(dissoc person :age)
;=>{:name “John”}
ถ้าหากเรา dissoc กับ key ที่ไม่มีอยู่จริง ก็จะไม่มีผลอะไร
(dissoc person :wife)
;=> {:name “John”, :age 30}
เช่นเดียวกับ assoc โปรดจำไว้ว่า dissoc ไม่ได้แก้ไขข้อมูลในตัวแปร แต่จะ return ค่า map ตัวใหม่ออกมา
Set
Set เป็นโครงสร้างข้อมูลชนิดนึง ที่จะไม่มีค่าซ้ำใน set เช่น {1 2 3 4}หรือ {“abcde”}เราสามารถเขียนคำสั่งสร้าง Set ได้ดังนี้
(Set #{1 2 4 5})
;=>#{1 2 4 5}
ใน clojure มีคำสั่งที่สามารถเปลี่ยนโครงสร้างของข้อมูล ให้เป็นในรูปแบบของ Set โดย คำสั่ง set สามารถ ลบข้อมูลที่มีการซ้ำกันออกได้ด้วย โดยไม่ได้ทำการแก้ไขข้อมูลในตัวแปร แต่จะเป็นการreturnค่าออกมาใหม่ ยกตัวอย่าง ดังต่อไปนี้
ใช้กับตัวเลข
(set [1 3 1 2 3 2 2 4 5 4 ] )
;=> #{1 2 3 4 5}
หรือจะใช้กับข้อมูลที่เป็นstring
(set “abcdac”)
;=> #{\a \b \c \d}
ข้อมูลที่เป็นแบบ Set จะมีคำสั่ง contains?
Contains? เป็นคำสั่งที่สามารถบอกได้ว่า ใน Set นั้น มีค่าที่เราต้องการหาหรือไม่
ขอยกตัวอย่างเพื่อให้มีความเข้าใจ
เขียนโปรแกรมเพื่อเช็คว่า ในข้อมูลชุด 1 2 3 1 2 3 2 2 4 5 4 มี เลข 2 อยู่หรือไม่
สามารถเขียนได้ดังต่อไปนี้
(set [1 3 1 2 3 2 2 4 5 4 ] )
;=> #{1 2 3 4 5}
โค้ดนี้จะลบข้อมูลที่ซ้ำออก
(contains? #{1 2 3 4 5} 2)
;=> true
หมายความว่า ใน set มี 2 อยู่ ซึ่งเป็นความจริง หรือสามาถเขียนได้แบบนี้
(contains? (set [1 3 1 2 3 2 2 4 5 4 ] ) 2)
:=> true
(contains? (set [1 3 1 2 3 2 2 4 5 4 ] ) 6)
:=> false
**คำเตือน หากเราเขียนข้อมูลที่มีโครงสร้างเป็น set ห้ามมีข้อมูลซ้ำกัน เพราะ set ห้ามมีค่าที่ซ้ำ
ต่อไปจะเป็นคำสั่งที่ นำค่ามาต่อกับ Sets นั้นก็คือ คำสั่ง Conj
Conj จะนำค่าที่เราต้องการเพิ่มเข้าไป ไปใส่ไว้ใน sets โดยหากค่าที่ใส่นั้นซ้ำก็จะถูกลบออก หากไม่มีก็จะถูกเพิ่มเข้าไป
(conj #{1 2} 2 4 3)
;=>#{1 2 3 4}
ในเมื่อมีคำสั่งที่สามารถเพิ่มได้ ก็ต้องมีคำสั่งในการลบ นั้ก็คือคำสั่ง Disj
Disj จะลบค่าที่เราต้องการที่จะลบใน sets
(disj #{1 2 3} 2)
;=>#{1 3}
ไหนก็พูดถึงSets แล้ว มาดู operation ของ sets หน่อยละกัน เริ่มจาก Union เลยละกัน Union เป็นคำสั่งที่นำ แต่ละ set มารวมกัน พร้อมกับ ลบตัวซ้ำออกไป
(union #{1 2} #{2 3} #{3 4})
;=>{1 2 3 4}
ต่อไป difference
Difference เป็นคำสั่ง ที่ จะลบตัวที่ซ้ำset ที่นำมาเปรียบเทียบ ดังตัวอย่าง
(difference #{1 2 3} #{1} #{1 4} #{3})
;=>{2}
จะเห็นว่า เรานำ set{1 2 3} มาเทียบกับset {1} {1 4}และ {3}
ผลลัพธ์ จึงออกมาเป็น {2}
ต่อไป เป็น intersection
Intersection เป็นการนำค่าที่ซ้ำกันของแต่ละsets มาเป็นคำตอบ ยกตัวอย่าง
(clojure.set/intersection #{1 2} #{2 3})
;=> {2}
โค้ดนี้เป็นการนำ set {1 2} มาหาตัวที่ซ้ำกันกับ {2 3}
เราก็ได้รู้จักกับ Data Structure ต่างๆไปแล้ว
จะเห็นว่า Clojure คำนวณอะไรได้มากมายโดยใช้เพียงโค้ดสั้นๆ นั่นเป็นเพราะว่า Clojure เป็น Lisp สมัยใหม่ที่ออกแบบมาเพื่อใช้แก้ปัญหาในโลกความจริง (Lisp เก่าๆก็ดี แต่เมื่อใช้ทำงานจริงแล้ว เราต้อง implement เพิ่มเติมอีกมาก อาจจะทำให้งานซับซ้อนขึ้น) ความจริงแล้วยังมีคำสั่งอื่นๆกับ feature อื่นๆของ Clojure มากมายที่เราไม่ได้พูดถึงในบล็อก ท่านสามารถหาข้อมูลเพิ่มเติมได้ที่ https://clojure.org/