FP(03): Map Filter Reduce และเพื่อนๆ พระเอกแห่งโลก Functional
Tags: beginners, programming, functional
Published: 2020-04-18 17:17:26

> ติดตามบทความอื่นๆ ของเราได้ที่ [TAMEMO.com](https://www.tamemo.com)

---

เนื้อหาในบทนี้น่าจะเป็นสิ่งที่เราเอาไปใช้เขียนโปรแกรมในชีวิตประจำวันมากที่สุดเรื่องหนึ่งเลย พอๆ กับเรื่อง pure function ในบทที่แล้ว

สำหรับภาษาโปรแกรมกระแสหลักที่เป็นแนว imperative หรือ OOP ซะเป็นส่วนใหญ่ ฟีเจอร์ของ FP ที่ได้รับความนิยมเอามาใช้กันมากที่สุดน่าจะเป็นเรื่องการโปรเซส List ด้วยคำสั่ง `map`, `filter`, และ `reduce` แทนการวน **loop** แบบปกติ

> **TL;DR (ยาวไป;ไม่อ่าน)** - ในบทความนี้จะเน้นหลักการใช้ `map`, `filter`, `reduce` และอื่นๆ แบบเจาะลึกในมุมมองของ FP (ฉบับ insight!)
> 
> ถ้าอยากอ่านวิธีการใช้ helper function พวกนี้เลย ข้ามไปส่วนปูพื้นฐาน ข้ามไปอ่านข้างล่างได้เลยนะ

- [map](#content-map)
- [filter](#content-filter)
- [reduce](#content-reduce)
- [flatMap](#content-flatMap)
- [zip](#content-zip)
- [forEach](#content-forEach)

## Operation การดำเนินการกับตัวแปร

ข้อมูลในมุมมองการเขียนโปรแกรมนั้นมีหลายชนิด เริ่มจากแบบมาตราฐานมากๆ เช่น

```c
int x = 1;
double d = 12.5;
string s = "FP";
```

แต่ก็ยังมีตัวแปรอีกประเภท ที่ถือตัวแปรชนิดอื่นไว้ในตัวเองอีกทีหนึ่ง นึกออกมั้ยว่าคืออะไร?

คำตอบคือตัวแปรประเภท **"Collection"** เช่น
```java
int[] arr = [1, 2, 3, 4]
```
- **Array** หรือ **List**: ที่เก็บตัวแปรหลายๆ ตัวเรียงกัน **ซึ่งเป็นตัวหลักที่เราจะพูดถึงกันในบทนี้!**
  
ชนิดตัวแปรอื่นๆ ที่เข้าข่ายก็ เช่น
- **Map** หรือ **Dict**: ที่เก็บตัวแปรหลายๆ ตัวโดยมี key เป็นตัวอ้างอิง
- **Pair** หรือ **Tuple**: เป็นการจัดกลุ่มตัวแปรมากกว่าหนึ่งตัวไว้ด้วยกัน

**Note:** เราสามารถแบ่งประเภทตัวแปรออกตาม dimension (มิติที่เก็บข้อมูลได้) โดยจะเรียกว่า **Tensor** เริ่มตั้งแต่ rank 0, 1, 2 ไปเรื่อยๆ
![](https://1.bp.blogspot.com/-FC-BahNnu0E/XpdFncmLuUI/AAAAAAAASP8/dSEFem9zhpU3htp5fWfpnY7K2hXvm48rwCNcBGAsYHQ/s1600/fp-02-1.png)
- rank 0 **Scala**: คือตัวแปรทั่วไป เก็บ value ได้ค่าเดียว เช่น `int`, `doudle`, `string` หรือพวก object ต่างๆ ก็ถือว่าใช่ได้ด้วย
- rank 1 **Vector**: หรือในการเขียนโปรแกรม เรามักจะชินกันคำว่า Array หรือ List กันมากกว่า
- rank มากกว่านั้นคือพวก array-2D, array-3D ไปเรื่อยๆ

ทีนี้, มีปัญหาอะไรล่ะ ที่เราต้องมาแบ่งตัวแปรออกเป็นกลุ่มตาม dimension ของมันแบบนี้?

สมมุติว่าเราจะ increment ค่าให้ตัวแปรหนึ่ง ถ้าจะทำให้มุมมอง imperative ก็อาจจะเขียนโค้ดแบบนี้
```java
int x = 1
x = x + 1
print(x) // 2
```
แต่ไหนๆ ก็อยู่ในโลกแห่ง FP แล้ว แทนที่จะแก้ไขค่าตัวแปรตรงๆ ขอทำเป็นฟังก์ชันแทนละกัน
```java
int inc(x) = x + 1

int x = 1
print(inc(x)) // 2
```
เทียบได้กับรูปข้อล่างนี่ 
![](https://2.bp.blogspot.com/--2oIPHm1kBY/Xn9xTwHaPpI/AAAAAAAASMc/39C3f6CoeHkBYMKmCQRge3p2Q5HIEa10ACNcBGAsYHQ/s1600/fp-02-2.png)
นั่นคือถ้าข้อมูลของเราเป็น Scala การโยนข้อมูลนี้เข้าฟังก์ชันก็ไม่มีปัญหาอะไรติดขัด

**แต่ทว่า !**

แล้วถ้าข้อมูลของเราอยู่ในรูปของ Tensor rank 1 หรือ Array ล่ะ? จะเกิดอะไรขึ้น?
```java
int inc(x) = x + 1

int[] x = [1, 2, 3, 4]
print(inc(x)) // ???
```
![](https://2.bp.blogspot.com/-PeX9Dh6B-zU/Xn9xTrzmk7I/AAAAAAAASMU/5To2hhDD1AINVd5yD3H4Hkp-cwJNgVxsQCNcBGAsYHQ/s1600/fp-02-3.png)
แน่นอนว่าโค้ดนี้ ทำงานไม่ได้!

นั่นเพราะว่าถ้าเราโยน Array เข้าฟังก์ชัน จะได้แบบนี้

```java
[1, 2, 3, 4] + 1 ???
```
Array (rank 1) จะถูกนำไป**บวก**กับ int ซึ่งเป็นค่าแบบ Scala (rank 0) ค่าทั้งสองอยู่คนละ dimension เลยนำมาบวกกันไม่ได้

วิธีการแก้ก็คือ ... ฟังก์ชันที่โปรเซส Scala ก็ต้องรับค่าเป็น Scala

![](https://3.bp.blogspot.com/-YZmNPbM1IQ8/Xn9xTmJAYcI/AAAAAAAASMY/vU7DZJIOMdUW38y3ytbZ-jZVpGi6-03aACNcBGAsYHQ/s1600/fp-02-4.png)

วิธีมาตราฐานแบบ imperative เราก็จะแก้มันด้วยการ **วน loop** ยังไงล่ะ! แหม่..คิดไม่ถึงเลย (ฮา)

```java
int inc(x) = x + 1
int[] arr = [1, 2, 3, 4]

for(i=0; i<x.length; i++){
  arr[i] = inc(arr[i])
}

print(arr) // [2, 3, 4, 5]
```

ปัญหาคือ แล้วเราจะทำให้ `inc()` นั้นสามารถใช้ได้กับ Array ได้ยังไงกัน?

คอนเซ็ปของฟังก์ชันควรจะใส่ชนิดตัวแปรได้ทั้งหมดไม่ใช่เหรอไง? ... ลองดูโค้ดนี้ต่อ

```java
int incArray(arr){
  int[] output = []
  for(i=0; i<x.length; i++){
    output[i] = inc(x[i])
  }
  return output
}

int[] arr1 = [1, 2, 3, 4]
int[] arr2 = incArray(arr1)

print(arr2) // [2, 3, 4, 5]
```
อืมม... ก็ดูดีไม่ใช่เหรอ? (ดูดีกะ***น่ะสิ!)

แค่จับโค้ดทั้งหมดที่เป็น imperative ไปยัดในฟังก์ชัน ไม่ได้แปลว่าคุณเขียน FP แล้วนะ เพราะนี่มันแนวคิดแบบ imperative ชัดๆ

*(เหมือนกับที่แค่สร้าง class สร้าง object ไม่ได้แปลว่าคุณเขียน OOP แล้วนะ ถ้าคุณไม่ได้ใช้คอนเซ็ปของ OOP อย่างเช่น Abstraction, Inheritance, หรือ Polymorphism ... อุ๊ปส์ มีใครทำแบบนั้นอยู่รึเปล่า?)*

---

เอาล่ะ ปูพื้นฐานกันมาพอล่ะ มาเข้าเรื่องกันดีกว่า

ในเมื่อการวนลูป ไม่ใช่สไตล์ของ FP, แต่เมื่อ FP เจอ Array เข้าไปมันก็ต้องมีวิธีแก้ปัญหาของมัน (โดยไม่ใช้ลูป)

นั่นคือภาษาสาย FP จะมี **helper function** แถมมาให้ในตัว ซึ่งทำให้เราสามารถใช้ฟังก์ชันแบบนี้

```java
int inc(x) = x + 1
```
ฟังก์ชัน Scala สามารถเอาไปใช้กับ Array ได้

มาเริ่มกับฟังก์ชันแรก...













<h2 id="content-map">map</h2>

ฟังก์ชัน map เอาไว้ mapping หรือก็คือการ "แปลง" (transform) ข้อมูลจากอย่างหนึ่งไปเป็นอีกอย่างหนึ่ง เช่น **ใส่ border ให้กับไอเทมทุกชิ้น** หรือ **x 10 กับตัวเลขทุกตัว**
![](https://1.bp.blogspot.com/-nmfepY1h1ys/XoILBr6cRXI/AAAAAAAASNE/ADytQDYAUEcqq-jGJFFef8omKU6xjsFMQCNcBGAsYHQ/s1600/fp-02-5.png) 

ซึ่ง `map` จะรับฟังก์ชัน**ที่แปลงค่า Scala ธรรมดา**เข้าไป แล้วทำการโปรเซสกันไอเทมทุกๆ ตัวในลิสต์

ตัวอย่างเช่น ถ้าเรามีฟังก์ชัน mul10 แบบนี้

```java
int mul10(x) = x * 10
var x = 1
var y = mul10(x)
```
แทนที่จะเอาไปวนลูปเองแบบนี้
```java
int mul10(x) = x * 10
var arr1 = [1, 2, 3]
var arr2 = []

for(var item in arr1){
  arr2[i] = mul10(arr1)
}
```
เราก็จะใช้ฟังก์ชัน `map` แทน

ก็จะกลายเป็นแบบนี้
```java
int mul10(item){
  return item * 10
}
var arr1 = [1, 2, 3]

// ถ้าภาษานั้นใช้ map ในรูปแบบ method
var arr2 = arr1.map(mul10)

// ถ้าภาษานั้นใช้ map ในรูปแบบ function
var arr2 = map(arr1, mul10)
```
หรือถ้าเราอยากให้โค้ดสั้นลง โดยการใส่ **"lambda"** หรือฟังก์ชันนิรนาม (ในบางภาษาอาจจะมี syntax การเขียนที่ไม่เหมือนกันนะ และบางภาษาอาจจะเรียกว่า anonymous function) โค้ดข้างบนก็จะดูสั้น กระชับ กรุบกริบดี!
```java
// method
var arr2 = arr1.map(item -> item * 10)

// function
var arr2 = map(arr1, item -> item * 10)
```
แปลว่า *นำไอเทมที่ตัวใน arr1 ไป x 10 แล้วได้ผลลัพธ์มา ใส่ไว้ใน arr2*

















<h2 id="content-filter">filter</h2>

ฟิลเตอร์เป็น helper function สำหรับเลือกไอเทมในลิสต์ว่าจะเอา/หรือไม่เอาตัวนี้ เช่น **เลือกรูปทรงที่มี"มุม"เท่านั้น** หรือ **เลือกตัวเลขที่ ≤ 2 เท่านั้น**

![](https://1.bp.blogspot.com/-EPCbAEOt0Ac/XoILBcAdozI/AAAAAAAASNA/1dw2KK_x8mUh-pFTpNAfTmuwOrUn0vSbACNcBGAsYHQ/s1600/fp-02-6.png)

ในการใช้งาน filter เราจะต้องสร้างฟังก์ชันสำหรับระบุว่าของชิ้นไหนบ้างที่เราอยากได้ (ของที่ผ่านเงื่อนไข จะถูกเลือกมา) ฟังก์ชันที่ทำหน้าที่เลือกนี้จะถูกเรียกอีกอย่างว่า **Predicate** ซึ่งจะรับไอเทมที่อยากเช็กเข้ามา แล้วรีเทิร์น `true` - ถ้าจะเอาไอเทมตัวนั้น / `false` - ถ้าไม่เอาไอเทมตัวนั้น

ลองมาดูโค้ดแบบ imperative กันก่อน
```java
int lessThanOrEqualsTwo(item){
  return x <= 2
}
var arr1 = [1, 2, 3]
var arr2 = []

for(var item in arr1){
  if(lessThanOrEqualsTwo(item)){
    arr2.push(item)
  }
}
```
ก็คือการวนลูป แล้วเช็กค่าที่ละตัว ถ้าผ่านเงื่อนไข (ตามฟังก์ชัน predicate) ก็ `push` ลงลิสต์ที่เป็นคำตอบเก็บไว้

ทีนี้ลองมาดูวิธีแบบฟังก์ชันนอลกันบ้าง
```java
int lessThanOrEqualsTwo(item){
  return x <= 2
}
var arr1 = [1, 2, 3]
var arr2 = []

// ถ้าภาษานั้นใช้ filter ในรูปแบบ method
var arr2 = arr1.map(lessThanOrEqualsTwo)

// ถ้าภาษานั้นใช้ filter ในรูปแบบ function
var arr2 = map(arr1, lessThanOrEqualsTwo)
```
และก็เหมือน `map` ล่ะนะ คือถ้าเปลี่ยนมาเขียนด้วย lambda โค้ดก็จะสวยและสั้นลง
```java
// method
var arr2 = arr1.filter(item -> item <= 2)

// function
var arr2 = filter(arr1, item -> item <= 2)
```















<h2 id="content-reduce">reduce</h2>

> สำหรับ reduce ในบางภาษาอาจจะแยกออกเป็น 2 ฟังก์ชันคือ `reduce` กับ `fold` นะ

reduce น่าจะเป็นตัวที่เข้าใจยากที่สุดในสามสหาย map, filter, reduce ดังนั้นก่อนจะอธิบาย `reduce` ขอยกตัวอย่างด้วยฟังก์ชันที่เข้าใจง่ายกว่าคือ sum และ join

### sum
ฟังก์ชันที่นำเลขทุกตัวในลิสต์มาบวกกัน จนได้เป็นคำตอบสุดท้ายเพียงคำตอบเดียว
```java
var arr = [1, 2, 3, 4]
var x = sum(arr)
// x = 1+2+3+4 = 10
```

### join
ฟังก์ชันที่ทำสตริงทุกตัวมาเชื่อมต่อกันด้วยสิ่งที่เรียกว่า delimiter ที่เรากำหนดเองได้ เช่น เชื่อมด้วย `" "`,`"-"`,หรือจะเป็นคำว่า `" and  "` ก็ยังได้
```java
var arr = ["A", "B", "C"]
var x = join(arr, ", ")    // x = "A, B, C"
var y = join(arr, "-")     // y = "A-B-C"
var z = join(arr, " and ") // z = "A and B and C"
```

แต่สังเกตอะไรมั้ย?

ว่า ... ฟังก์ชันทั้ง 2 ตัวนี้มีอะไรบางอย่างที่เหมือนกัน นั่นคือฟังก์ชันทั้งสองตัว**จะทำการรวม item ทุกตัวในลิสต์ด้วย "ด้วยวิธีการอะไรอย่างหนึ่ง" ให้ค่าทั้งหมดรวมกันกลายเป็น "ค่าๆ เดียว"**

![](https://1.bp.blogspot.com/-_dVYL0rhV-k/XoISQ4HIGlI/AAAAAAAASNY/Rl2upPqd_ocZBJyvnTgTfm3LcYBnkvZMwCNcBGAsYHQ/s1600/fp-02-7.png)

ฟังก์ชันพวกนี้แหละ ที่เราเรียกว่า **reduce** วิธีการสร้าง lambda ที่เอาไว้กำหนดวิธี**รวม**จะยากนิดนึง เพราะ lambda ของ reduce จะต้องเป็นฟังก์ชัน 2 parameters

เช่นถ้าเราจะสร้าง reduce แบบการบวก (เหมือน sum) 
```Java
var arr = [1, 2, 3, 4]
var x = arr.reduce((a, b) -> a + b)
```
สร้าง `(a, b) -> a + b` เพื่อกำหนดว่าวิธีการที่จะเอาไอเทมทุกตัวในลิสต์ตัวนี้มารวมกันคือ **ถ้ามีไอเทม 2 ตัวคือ a, b ให้นำสองตัวนี้มา + กัน** นั่นเอง

> จริงๆ lambda ที่เราต้องสร้างให้ reduce ประกอบด้วย 2 parameters ก็จริง แต่มีความหมายต่างกันอยู่ 
> 
> นั่นคือตัวแรกจะเป็น **"accumulator"** หรือ **"ค่าสะสม"** ในขณะนั้น ส่วนตัวที่สองจะเป็น current item ในการวนรอบนั้นๆ

มาดูตัวอย่างการใช้ reduce กันต่อ

เราสามารถใช้ reduce ในการหาค่ามากสุด/น้อยสุดในลิสต์ได้เช่นกัน เพราะ min/max คือการยุบลิสต์ให้เหลือแค่ค่าเดียว ก็ใช้ reduce ได้ดังนี้

```java
var maxItem = arr.reduce((a, b) -> max(a, b))
var minItem = arr.reduce((a, b) -> min(a, b))
```
แต่ถ้าเรามองว่า lambda ของเรากับฟังก์ชัน `max`, `min` มันก็เหมือนๆ กันนั้นแหละ (parameter เท่ากันเลย) ก็จะย่อได้อีก
```java
var maxItem = arr.reduce(max)
var minItem = arr.reduce(min)

//หรือถ้าเป็นภาษาเชิง OOP อาจจะต้องเขียนจาก
var maxItem = arr.reduce(a, b -> Math.max(a, b))
//เป็นแบบนี้
var maxItem = arr.reduce(Math::max)
```

ทีนี้! บางครั้งการ reduce ก็ไม่ได้ง่ายและตรงไปตรงมาขนาดนั้น เพราะ**ทิศทาง**หรือ direction ของการ reduce จะมีผลกับคำตอบเสมอ!

เช่น ถ้าเราเลือกที่จะ reduce ค่าด้วยการ `-` บ้างล่ะ จะเกิดอะไรขึ้น?

![](https://1.bp.blogspot.com/-zfj3bki6TZA/XpiL_6wYXTI/AAAAAAAASQY/5tFqQ1WMcicbzU2IdJ4wZy7K1xRzLqrUgCNcBGAsYHQ/s1600/fp-01-8.png)

จะเห็นว่า ถ้าเรา reduce จาก `ซ้าย->ขวา` ผลที่ได้จะต่างจาก `ซ้าย<-ขวา`

ค่าดีฟอลต์ของ reduce ส่วนใหญ่จะเป็น left-to-right ถ้าอยากให้มันเริ่มจากฝั่งขวา ในภาษาส่วนใหญ่จะมีคำสั่ง `reduceRight` (reduce จากทางขวา) หรือซักอย่างที่ชื่อประมาณนี้ให้ใช้งาน

```java
var ans1 = arr.reduce((a, b) -> a - b)
var ans2 = arr.reduceRight((a, b) -> a - b)
```
แต่ถึงไม่มี `reduceRight` เราก็อาจจะเลี่ยงไปใช้งานคำสั่งนี้ผสมเข้ามาช่วยได้

```java
var ans1 = arr.reverse().reduce((a, b) -> a - b)
```
คือ**กลับหัว**ลิสต์ซะก่อน แล้วค่อย reduce ไงล่ะ (หืม? ถ้าภาษานั้นไม่มี `reverse` อีกจะทำยังไง? ... นั่นสิ 555 อ่านต่อไปก่อนละกัน ฮา)

มีอีกเคสหนึ่ง ที่reduceจะมีปัญหา นั่นคือถ้าเราต้องการกำหนดค่าเริ่มต้นจะทำยังไง? (ปกติแล้วเวลา reduce ค่าaccumulatorจะเริ่มต้นด้วย**ไอเทมแรกสุด**ในลิสต์) 

เช่นอยาก sum เลขทุกตัวเหมือนเดิม แต่ไม่อยากให้ accumulator เริ่มต้นที่ `0` แต่เริ่มจาก `100` แทน
```Java
var arr = [1, 2, 3, 4]
var x = ([100] + arr).reduce((a, b) -> a + b)
```
วิธีแก้แบบง่ายที่สุดคือ ... ก็เติมไอเทมเข้าไป 1 ตัวก่อน reduce สิ

เออ มันก็เวิร์คแหละ แต่ส่วนใหญ่ฟังก์ชัน reduce มักจะมี parameter ที่2ให้เรากำหนด **initial value** ได้นะ

แต่บางภาษาก็ไม่มีอ๊อบชันนี้ให้ใช้ แต่แยกคำสั่งออกไปอีกชื่อนึงเลย คือ `fold` = reduce ที่ต้องกำหนด initial value เสมอ
```Java
var arr = [1, 2, 3, 4]
var x = arr.reduce((a, b) -> a + b, 100)

// หรือใช้ fold
var x = arr.reduce((a, b) -> a + b)
var y = arr.fold(100, (a, b) -> a + b)
```
และแน่นอน เมื่อ `reduce` มี `reduceRight`, `fold` ก็มี `foldRight` เช่นกันนะ

---

ขั้นเวลา! ตอนนี้เราผ่านฟังก์ชันหลัก 3 ตัวไปแล้ว แต่ก่อนจะไปดูฟังก์ชันตัวต่อไป เราลองมาคิดอะไรเล่นๆ กันดีกว่า กับคำถาม..

## map ปะทะ filter ปะทะ reduce
ตอนนี้เรารู้จักฟังก์ชัน 3 ตัวกันไปแล้ว ถ้าถามว่าตัวไหนเจ๋งสุด คุณคิดว่าคำตอบคืออะไรเอ่ย?

ลองคิดดูเล่นๆ ก่อนนะแล้วค่อยอ่านต่อไป

...

คำตอบคือ *reduce* ครับ!

ถ้าใครเคยเรียนวิชา Digital System มาก่อนน่าจะรู้จักกับ circuit gate เช่น AND OR NOT ในจำนวนพวกนี้เกต NAND เป็นเกตที่เรียกว่า *universal gate* หรือเกตครบจักรวาล นั่นหมายความว่าเราสามารถเอา NAND มาต่อกันเป็นเกตตัวอื่นเช่น AND หรือ OR ได้ทั้งหมด

reduce ก็เหมือนกันครับ เราสามารถเอา reduce มาประยุกต์ใช้งานแทน map กับ filter ได้ทั้งหมด!

คุณอาจจะสงสัยว่าเป็นไปได้ยังไง เพราะทั้ง map, filter ให้ค่าออกมาเป็นอะเรยเสมอ แต่reduceจะให้ค่าออกมาเป็นแค่ค่าเดียว

ใช่ครับ, reduce ให้ค่าออกมาแค่ค่าเดียว แต่ไม่จำเป็นว่าค่านั้นต้องเป็น scala นี่นา จะตอบค่ากลับมาเป็น array (list) ก็ได้นะ แบบนี้..

```java
// map: (item) -> item * 10
var x = arr.reduce((arr, item) -> {
  arr.push(item * 10)
  return arr
}, [])

// filter: (item) -> item <= 2
var x = arr.reduce((arr, item) -> {
  if(item <= 2){
    arr.push(item)
  }
  return arr
}, [])
```
แต่แบบนี้ map, filter ก็ไม่จำเป็นน่ะสิ เพราะเราใช้ reduce แทนได้ทั้งหมด ... ก็ใช่ล่ะนะ แต่การใช้แบบนั้น **ชื่อมันไม่สื่อ** ครับ อ่านไปจะงงเองว่าฉันเขียนอะไรลงไป

---

กลับมาต่อกับฟังก์ชันตัวต่อไป...
















<h2 id="content-flatmap">flatMap</h2>

่ย่อมาจาก **"Flatten Map"** บางภาษาอาจจะใช้ในชื่อของ `flatten`

ถ้าเราลองวิเคราะห์ความสามารถของ map, filter, reduce แล้วก็จะพบว่า

- `map`: จำนวนไอเทมจะเท่าเดิม (แต่ข้อมูลจะเปลี่ยนไป)
- `filter`: จำนวนอาจจะน้อยลงจากเดิมจนถึง 0 ตัวเลยก็ได้ (แต่ข้อมูลจะไม่เปลี่ยน)
- `reduce`: ผลลัพธ์ 1 ตัวเท่านั้น

นั่นแปลว่าเราขาดฟังก์ชันที่สร้างไอเทมมากกว่า input ที่ใส่เข้าไป นั่นแหละคือความสามารถของ `flatMap` ... ลองดูรูปข้างล่างประกอบ

![](https://1.bp.blogspot.com/-UpkzUEC-snY/XoISQ0Dg26I/AAAAAAAASNU/8CjTUtMUOx4QTYAN0pK41sdW91PB8Rh8wCNcBGAsYHQ/s1600/fp-01-9.png)

ถ้าลิสต์มีจำนวนไอเทมทั้งหมด **N** ตัว เราจะได้ชาร์ตตามข้างบนนี่

```java
var arr1 = [1, 2, 3, 4]

var arr2 = arr1.flatMap(item -> [item, item * 10])
// arr2 is [1, 10, 2, 20, 3, 30, 4, 40]
```
จากตัวอย่างข้างบน เราสามารถใช้ `flatMap` ในการขยายลิสต์ได้ เช่นในกรณีนี้เรากำหนดว่า `item -> [item, item * 10]` คือ 1 ไอเทมของลิสต์ ให้ขยายเป็น 2 ไอเทมคือitemตัวเดิม และ itemอีกตัวที่`x10`

หรืออีกวิธีหนึ่ง คือเราสามารถแกะ nested array ออกมาให้เหลือชั้นเดียวได้

```java
var arr1 = [[1, 2], [3], [], [4, 5, 6]

var arr2 = arr1.flatMap(item -> item)
// arr2 is [1, 2, 3, 4, 5, 6]
```
นั่นคือถ้าเรารีเทิร์นค่ากลับเป็น item เลย จะเป็นการเอาอะเรย์ที่ครอบมันอยู่ออก

> ส่วนใหญ่ เราไม่ค่อยจะเจอโอกาสจะได้ใช้ `flatMap` เท่าไหร่ แต่เดี๋ยวในบทหลังๆ เราจะได้ใช้ `flatMap` กันเยอะมาก .. ไม่ได้ใช้ด้วยการแกะชั้นอะเรย์ออก แต่ใช้ในเรื่องของ **"Monad"** 
















<h2 id="content-zip">zip</h2>

ชื่อของ zip มาจากคำว่า "ซิป" นั่นแหละ เพราะสิ่งที่มันทำคือการจับคู่ลิสต์ 2 ตัว (หรืออาจจะมากกว่า 2 ก็ได้) เข้าด้วยกัน

![](https://1.bp.blogspot.com/-iePOqjndXJA/XpsqDM5Tu9I/AAAAAAAASRE/nZoDVu-MaX0mJZS1uII8DtrDsakM3oORQCNcBGAsYHQ/s1600/fp-03-zip.png)

```java
var arr1 = [1, 2, 3]
var arr2 = ["A", "B", "C"]

var arr3 = zip(arr1, arr2)
// arr3 is [(1, "A"), (2, "B"), (3, "C")]
```
ตัวอย่างการใช้งาน เช่นถ้าเราต้องการจะวนลูปลิสต์ 2 ตัวพร้อมๆ กัน จากเดิมที่เราใช้ foreach ไม่ได้เพราะ foreach วนได้ทีละลิสต์ ทำให้เราต้องไปวนลูปแบบใช้ตัวนับ `i` 

```java
var arr1 = [1, 2, 3]
var arr2 = ["A", "B", "C"]

var n = min(arr1.length, arr2.length)
for(i=0; i<n; i++){
  var a = arr1[i]
  var b = arr2[i]
  ...
}

// แต่ถ้าเรา zip ลิสต์ 2 ตัวนั้นเข้าด้วยกัน ก็จะสามารถวนลูปพร้อมกันได้
for(a, b in zip(arr1, arr2)){
  ...
}
```


















<h2 id="content-foreach">forEach</h2>
ในฟังก์ชันทั้งหมดในบทความนี้ ส่วนใหญ่จะมีรูปแบบการใช้งานแบบฟังก์ชันนอลทั้งหมด คือพยายามทำให้โค้ดเป็น pure function ไม่มีการเปลี่ยนค่าตัวแปรหรือยุ่งกับ I/O ภายนอก .. ยกเว้น `forEach` นี่แหละที่นับว่าเป็นฟังก์ชันที่มักจะยุ่งกับ I/O หรือ global variable ตรงๆ 

วิธีการใช้งานของ `forEach` ถือว่าเข้าใจง่ายมาก เหมือนการวนลูป for แบบปกติ

```java
var arr = [1, 2, 3]

for(item in arr){
  print(item)
}

// เปลี่ยนไปใช้ฟังก์ชันแทน
arr.forEach(item -> {
  print(item)
})
```
หรืออีกตัวอย่าง.. เราต้องการวนลูปเพื่อรวมค่า sum ของอะเรย์

```java
var arr = [1, 2, 3]

var sum = 0
for(item in arr){
  sum += item
}

// เปลี่ยนไปใช้ฟังก์ชันแทน
var sum = 0
arr.forEach(item -> {
  sum += item
})
```
สังเกตว่า `forEach` มักจะยุ่งกับค่าภายนอกเสมอ (ไม่เป็น pure function) เพราะโดยหลักการของมันคือจะให้ item มา1ตัว แต่ไม่ต้องรีเทิร์นค่าอะไรกลับไปเลย ถ้าเราไม่เอาค่านั้นมาใช้ก็ไม่รูปจะวนลูปทำไม

## สรุป
ในบทนี้เราได้เรียนรู้ helper function ตัวสำคัญๆ ใน FP ที่ทำให้การทำงานกับ List, Array ของเรานั้นง่ายขึ้นเยอะมาก ไม่ต้องมาวนลูปแบบเดิมๆ

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

```java
var arr = [1, 25, 20, 4, ...]

var sum = 0
for(item in arr){
  item = item * 10
  if(item > 100){
    sum += item
  }
}

print(sum)
```
โค้ดข้างบนนี่ เขียนด้วยสไตล์ imperative แบบเดิมๆ ถ้าเราไม่เคยรู้มาก่อนว่าจุดประสงค์ของโค้ดต้องการจะทำอะไร เราจะต้องใช้เวลาสักพักหนึ่งเลยในการแกะโค้ดให้ออกว่ามันทำหน้าที่อะไร

แต่ถ้าเราเปลี่ยนเป็นโค้ดสไตล์ FP จะได้แบบนี้
```java
var arr = [1, 25, 20, 4, ...]

var sum = arr.map(item -> item * 10)
             .filter(item -> item > 100)
             .reduce(a,b -> a + b)
print(sum)
```
นั่นคืออ่านได้ง่ายมากว่า 
1. (map) นำตัวเลขทุกตัวมา x10
2. (filter) จากนั้นเลือกเฉพาะเลขที่ >100
3. (reduce) สุดท้ายเอาตัวเลขทั้งหมดมา + กัน