Advance Clojure Command

Arm Pacino
Technologies For Everyone
4 min readApr 25, 2017

ในบทความนี้เราจะพูดถึง 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/

--

--