เทคนิค filter collection หล่อๆ ของ Clojure ด้วย set

โจทย์คือเราต้องการเลือก element จาก list สำหรับ element ที่มีค่าตรงกับค่าที่ระบุ

เริ่มด้วย Ruby กันก่อน เราอาจจะทำด้วยวิธีต่างๆ ดังนี้

(1..5).select { |e| e == 2 || e == 3 } # => [2, 3]

## ใช้ array เมื่อเริ่มมีหลายตัว
to_take = [2,3,5,7]
(1..10).select { |e| to_take.include?(e) } # => [2, 3, 5, 7]

## ใช้ set เพื่อ performance ที่ดีขึ้น
require 'set'
to_take = Set.new([2,3,5,7])
(1..10).select { |e| to_take.member?(e) } # => [2, 3, 5, 7]

มาดูเทคนิคที่ว่านี้บน Clojure กัน

(filter #{2 3 5 7} [1 2 3 4 5 6 7 8 9 10]) ;=> (2 3 5 7)
; หรือถ้าเรียกใช้ range แทน
(filter #{2 3 5 7} (range 1 11)) ;=> (2 3 5 7)

แทนที่ผมจะต้องตรวจสอบว่าค่าแต่ละตัวตรงกับที่ต้องการไหมใน lambda (วงเล็บปีกกาหลัง select) ผมสามารถส่งเซตที่มีตัวเลขที่ต้องการไปแทนได้ ซึ่งปกติถ้าผมเขียนเหมือน Ruby ตรงๆ จะได้ผลลัพธ์แบบนี้

(filter #(contains? #{2 3 5 7} %) (range 1 11)) ;=> (2 3 5 7)

จะเห็นว่าแบบก่อนหน้าซับซ้อนน้อยกว่า

สาเหตุที่สามารถทำแบบนี้ได้ เพราะตัว set ของ Clojure เองนั้นก็เป็นฟังก์ชัน ที่จะคืนค่า element ที่ตรงกับอากิวเมนต์ที่ส่งมา หากมีค่านั้นอยู่ใน set เช่น

(#{2 3} 2) ;=> 2
(#{2 3} 4) ;=> nil

และตัวฟังก์ชัน filter สนใจแค่เพียงว่าค่าที่ได้จากการ apply element แต่ละตัวจากพารามิเตอร์ที่ 2 ไปที่ฟังก์ชันในพารามิเตอร์แรกแล้วได้ผลลัพธ์เป็น false หรือ nil หรือไม่ หากไม่ใช่ก็จะ return element นั้นๆ ออกมาในผลลัพธ์

(filter (fn [x] 1) (range 1 11)) ;=> (1 2 3 4 5 6 7 8 9 10)
(filter (fn [x] nil) (range 1 11)) ;=> ()

มาลองดูตัวอย่างการใช้งานเทคนิคนี้ที่ดูมีประโยชน์มากขึ้นกันบ้าง

ผมอยากหาว่าใน list ต่อไปนี้ [3 2 5 3 4 1 6 2 5 4 3 5 2 1 6 2 3 5 1 4 6 3 5 1 4 2 3] มีเลขใดบ้างที่มีอยู่ 3 หรือ 4 ตัว

(->> [3 2 5 3 4 1 6 2 5 4 3 5 2 1 6 2 3 5 1 4 6 3 5 1 4 2 3]
     (group-by identity)
     (map (juxt key (comp count val)))
     (filter (comp #{3 4} last))
     (map first)) ; => (4 1 6)

;; สำหรับคนที่ตามไม่ทัน
;; (group-by identity) => {3 [3 3 3 3 3 3], 2 [2 2 2 2 2], 5 [5 5 5 5 5], 4 [4 4 4 4], 1 [1 1 1 1], 6 [6 6 6]}
;; (map (juxt key (comp count val))) => ([3 6] [2 5] [5 5] [4 4] [1 4] [6 3])
;; (filter (comp #{3 4} last)) => ([4 4] [1 4] [6 3])

ในบรรทัดที่ 4 ผมรวมฟังก์ชัน #{3 4} เข้ากับ last ซึ่งทำให้ได้ผลลัพธ์เป็น ฟังก์ชันที่นำตัวเลขตัวสุดท้ายของแต่ pair มาตรวจสอบว่าอยู่ใน set  #{3 4} หรือไม่ ซึ่ง filter ก็จะทำการเลือก pair ที่เป็นจริงออกมา

เทคนิคการใช้ set แทน function นี้เป็นเทคนิคที่หล่อดีและคิดว่าภาษาตระกูลอื่นน่าจะเลียนแบบได้ยากครับ

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s