/ 365วันแห่งโปรแกรม

[365 วันแห่งโปรแกรม #day65] Filter Map Reduce

วันที่หกสิบห้าของ ‪#‎365วันแห่งโปรแกรม วันนี้เรามารู้จักกับ Filter Map Reduce กันครับ


หลายคนอาจจะเคยได้ยินคำว่า Map Reduce กันมาบ้างแล้ว ไม่ว่าจะมาจากเรื่อง Search Engine ก็ดี หรือใน Functional Programming ก็ดี วันนี้เราจะมาเริ่มทำความรู้จักกับ Filter Map Reduce กันครับ

Filter

Filter เป็นฟังก์ชันที่ใช้สำหรับกรองข้อมูลใน collection (list) ให้เหลือแต่ค่าที่เราต้องการ หลักการคือใส่ list ลงไปตัวนึง แล้วก็ใส่วิธีกรองลงไป มันก็จะวนทำงานตั้งแต่ข้อมูลแรกใน list ว่าใช่ที่ต้องการไหม ถ้าใช่ก็เก็บไว้ ถ้าไม่ใช่ก็ทิ้งไป ดังนั้นแล้วจำนวนข้อมูลใน collection ขาออกอาจจะไม่เท่ากับจำนวนข้อมูลขาเข้าก็ได้ เรามาดูตัวอย่างกันดีกว่าครับ

กำหนดให้เซ็ต s มีข้อมูลเป็นจำนวนเต็มตั้งแต่ 1 ถึง 100 จงหาสมาชิกทั้งหมดของเซ็ต s ที่เป็นจำนวนคู่

พอเห็นโจทย์ปุ๊บเราต้องต้องดูก่อนเลยว่าทำยังไงถึงจะรู้ว่าข้อมูลตัวไหนที่ใช่ จำนวนคู่ก็คือจำนวนที่หาร 2 ลงตัวนั่นเอง ก็เขียนโปรแกรมวนๆ ตัวไหนหารลงตัวก็ใช่เลย

Haskell

even' x = mod x 2 ==0

filter even' [1..100]

ข้างต้นเป็นโค้ดในภาษา Haskell ครับ ผมสร้างฟังก์ชัน event' ไว้ตรวจว่าค่าที่ใส่ไปเป็นเลขคู่หรือไม่ แล้วก็ใช้คำสั่ง filter กระทำกับ list ของ 0 ถึง 100 ด้วยฟังก์ชัน even'

*ใน Haskell มีฟังก์ชัน even อยู่แล้ว ผมจึงใส่เครื่องหมาย ' ตามหลังเพื่อบอกว่าเป็นคนละอันกัน even ของระบบ

หลายท่านอาจจะไม่คุ้นเคยกับ Functional Language ดังนั้นเรามาลองทำโจทย์นี้บน Imperative Language ดูดีกว่าครับ

C#

public IEnumerable<int> GetOnlyEvenNumbers(IEnumerable<int> list)
{
    foreach (var item in list)
    {
        if(item %2 == 0) yield return item;
    }
}

===main===

var s = Enumerable.Range(1, 100).ToList();
var result = GetOnlyEvenNumbers(s);
PrintCollection(result);

จะเห็นว่าเป็นการวนลูปปกติ โดยในแต่ละรอบที่วนก็จะเช็คว่าใช่สิ่งที่เราต้องการหรือไม่ ถ้าใช่ก็ yield return ไป ไม่ใช่ก็ข้าม

ต่อมาเราจะลองสร้างฟังชัน Filter ที่ลองรับการปรับเปลี่ยนวิธี filter ได้กันดูครับ

โดยทั่วไปแล้วฟังก์ชัน filter จะรับพารามิเตอร์เป็นฟัง์ชันสำหรับตรวจสอบสิ่งที่เราต้องการ ดังนั้นในสถานการณ์นี้เราควรเลือกใช้ Strategy pattern ครับ

สร้าง Strategy Interface ก่อนครับ

C#

public interface IFilterStrategy<E>{
    bool Filter(E item);
}

ต่อไปสร้าง Concrete Strategy

C#

public class EvenFilter : IFilterStrategy<int>
{
    public bool Filter(int item)
    {
        return item % 2 == 0;
    }
}

สร้างฟังก์ชัน Filter

C#

public IEnumerable<E> Filter<E>(IFilterStrategy<E> strategy, IEnumerable<E> list)
{
    foreach (var item in list)
    {
        if (strategy.Filter(item)) yield return item;
    }
}

รัน!!

C#

var s = Enumerable.Range(1, 100).ToList();
var result = Filter(new EvenFilter(),s);
PrintCollection(result);

เพียงเท่านี้เราก็มีฟังก์ชัน Filter ที่รองรับ strategy ไหนๆ ก็ได้ โดยการ implement IFilterStrategy ใหม่

สำหรับภาษาที่รองรับ Function pointer, Anonymous Function, Lambda Expression ก็ไม่จำเป็นสร้าง Strategy เป็นคลาสครับ หน้าตาที่ได้ก็ประมาณนี้

C#

public IEnumerable<E> Filter<E>(Func<E, bool> func, IEnumerable<E> list)
{
    foreach (var item in list)
    {
        if (func(item)) yield return item;
    }
}

===main===

var s = Enumerable.Range(1, 100).ToList();
var result = Filter((a) => a % 2 == 0, s);
PrintCollection(result);

จะเห็นว่าพอเราเขียนแบบ Functional Style ได้ อะไรๆ ก็ดูดีขึ้นมาก ><

ข่าวดีคือในภาษาใหม่ๆ ส่วนใหญ่ก็เริ่มมีของง่ายๆ ให้ใช้สำหรับทำ filter ได้เหมือนกัน เช่นใน Linq และ Java 8

C# + Linq

var s = Enumerable.Range(1, 100).ToList();
var result = from x in s where x % 2 == 0 select x;
PrintCollection(result);
Java 8

List<Integer> s = IntStream.range(1, 101).boxed().collect(Collectors.toList());
List<Integer> result = s.stream().filter(i -> i % 2 == 0).collect(Collectors.toList());
printCollection(result);

สรุป Filter นั้นจริงๆ ก็เหมือนกับการทำ iterate เทียบทีละ item ว่าเป็นสิ่งต้องการหรือเปล่า แล้วคืนค่าเฉพาะส่วนที่ต้องการ ดูเหมือนว่าจะเป็นเรื่องเล็กน้อยมาก แต่จริงๆ มันก็ทำให้โค้ดเราสั้นลงและอ่านง่ายขึ้นมากครับ

#‎day65 #365วันแห่งโปรแกรม ‪#‎โครงการ365วันแห่ง‬...