[Dart] รวมคำสั่งดีๆ เอาไว้ใช้กับ List
Tags: beginners, programming, dart, flutter
Published: 2020-02-18 16:24:35

---
canonical_url: https://www.tamemo.com/post/177/dart-list-helper-method/
---

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

แต่ก่อนอื่น เราจะอธิบายคำศัพท์ที่จะใช้ในบทความนี้ซะก่อน

### Immutable vs Mutable

เมธอดของ List แต่ละตัวมีรูปแบบการทำงานที่ไม่เหมือนกัน โดยจะแบ่งเป็น
- **Immutable** - คำสั่งประเภทนี้จะไม่เปลี่ยนแปลง List (แต่ให้ผลลัพธ์ผ่านการ return)
- **Mutable** - คำสั่งประเภทนี้จะเปลี่ยนค่าของ List ตรงๆ
 
ซึ่งในบทความนี้จะใช้สัญลักษณ์แบบนี้ 😤 Immutable | 😱 Mutable

### Result Type

เมธอดแต่ละตัวมีการตอบผลลัพธ์ (return) กลับมาไม่เหมือนกัน แต่แบ่งได้เป็นประมาณนี้

- **List** - ตอบกลับมาเป็น List เหมือนเดิม
- **Iterable** - ตอบกลับเป็น Iterable (เป็นโครงสร้างแบบ abstract คือสามารถเอามาวนลูปเพื่อขอค่าทีละตัวได้ ซึ่งส่วนใหญ่ทำงานแบบ *lazy* นั่นคือถ้ายังไม่มีการเรียกใช้จะไม่ทำงาน ต่างจาก List ที่ทำงานทันที)
- **Scala** - ตอบกลับมาเป็นค่าชนิดอื่นเลย เช่น `int`, `bool`, `String`
- **void** - ไม่ตอบค่าอะไรกลับมาเลย

ซึ่งในบทความนี้จะใช้สัญลักษณ์แบบนี้ 🍇 List | 🍒 Iterable | 🍐 Scala | 🥚 void

## หมวดการสร้าง

### `generate()`
😤 Immutable | 🍇 List

ใช้สำหรับการสร้างลิสต์ขึ้นมาโดยกำหนดจำนวน item ที่ต้องการ แล้วเราต้องกำหนด function สำหรับสร้าง item ขึ้นมา โดยสิ่งที่เราจะได้คือ index 
```dart
List<int> list = List<int>.generate(10, (i) => i + 1);
// List [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
```
### List Comprehension
😤 Immutable | 🍇 List

ซึ่งแทนที่จะใช้ `.generate()` เราสามารถสร้างลิสต์โดยใช้การวน `for` แทนก็ได้
```dart
List<int> list = [ for(var i=0; i<10; i++) i + 1 ];
// List [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
```

## หมวดการคัดลอก/เพิ่ม/ลบ
### `form()`, `of()`
😤 Immutable | 🍇 List

เป็น 2 คำสั่งที่ใช้สร้างลิสต์จากลิสต์อีกตัวหนึ่ง ซึ่งมีข้อแตกต่างกันเล็กน้อยนั่นคือถ้าเราสั่งด้วย `from` ผลที่ได้จะเป็นไทป์แบบ `dynamic` แต่ถ้าใช้ `of` ผลที่ได้จะได้ไทป์ของลิสต์ต้นฉบับมาด้วย
```dart
var list1 = new List.from(<int>[1, 2, 3]); 
// list1 is List<dynamic>
var list2 = new List.of(<int>[1, 2, 3]); 
// list2 is List<int>

List<String> list3 = new List.from(<int>[1, 2, 3]); 
// Ok until runtime.
List<String> list4 = new List.of(<int>[1, 2, 3]); 
// Compile Time Error!
```

### `add()`, `addAll()`
😱 Mutable | 🥚 void

ใช้เพื่อเพิ่ม item ลงไปในลิสต์ โดยใช้ `add` สำหรับเพิ่ม item แบบทีละตัวและใช้ `addAll` สำหรับการเพิ่มหลายๆ ตัวในรูปแบบของ *List* หรือ *Iterable*
```dart
List<int> list1 = [1, 2, 3];
list1.add(4);
// list1 is [1, 2, 3, 4]

List<int> list2 = [1, 2, 3];
list2.addAll([4, 5]);
// list2 is [1, 2, 3, 4, 5]
```

### `followedBy()`
😤 Immutable | 🍒 Iterable

เป็นการสร้างลิสต์ตัวใหม่โดยเอาลิสต์อีกตัวมาต่อกัน แต่จะไม่ใช่การ `add` หรือ `addAll` ที่เพิ่ม item เข้าไปในลิสต์ แต่เป็นการสร้างลิสต์ตัวใหม่ออกมาแทน

```dart
List<int> list1 = [1, 2, 3];
Iterable<int> list2 = list1.followedBy([4, 5]);

//list1 is List [1, 2, 3]
//list2 is Iterable (1, 2, 3, 4, 5)
```
แต่ใน Dart การต่อ List สามารถใช้การ `+` หรือ `...` (spread operator) แทนได้
```dart
List<int> list1 = [1, 2, 3];
List<int> list2 = list1 + [4, 5];
//list1 is [1, 2, 3]
//list2 is [1, 2, 3, 4, 5]

List<int> list3 = [1, 2, 3];
List<int> list4 = [...list1, ...[4, 5], 6];
//list3: [1, 2, 3]
//list4: [1, 2, 3, 4, 5, 6]
```



## หมวด map/filter/reduce

### `forEach()`
😤 Immutable | 🥚 void

ใช้ในการวนลูป item ทุกตัวในลิสต์ในรูปแบบการสร้างฟังก์ชันขึ้นมา (ซึ่งมาผลเทียบกับการวน for แบบธรรมดานั่นแหละ)
```dart
var list = [1, 2, 3];
list.forEach((item) => print(item));

// เขียนแบบใช้ลูป
var list = [1, 2, 3];
for(var item in list){
    print(item);
}
```

### `map()`
😤 Immutable| 🍒 Iterable

ใช้ในการแปลง item **ทุกค่า**ในลิสต์ให้กลายเป็นอีกค่าหนึ่งด้วยวิธีการเดียวกันทั้งหมด โดยสร้างฟังก์ชันขึ้นมา

เช่น ในตัวอย่างข้างล่าง เราต้องการคูณเลขทุกตัวในลิสต์ด้วย 10 ก็ให้สร้าง `(x) => x * 10` ฟังก์ชันที่รับตัวเลขเข้าไปหนึ่งตัวแล้วตอบค่านั้นเอาไปคูณด้วย 10 กลับมา
```dart
List<int> num1 = [1, 2, 3, 4, 5];

Iterable<int> num2 = num1.map((x) => x * 10);

List<int> num3 = num1.map((x) => x * 10).toList();

// num2 is Iterable (10, 20, 30, 40, 50)
// num3 is List [10, 20, 30, 40, 50]

// เขียนแบบใช้ลูป
List<int> num2 = <int>[];
for(var x in num1){
    num2.add(x * 10);
}
```
ข้อควรระวังคือ `map` รีเทิร์นค่ากลับมาเป็น `Iterable` ไม่ใช่ `List` ดังนั้นถ้าเราต้องการผลลัพธ์เป็นลิสต์เหมือนเดิมจะต้องสั่ง `toList()` อีกที

### `where()` (หรือ filter)
😤 Immutable | 🍒 Iterable

ฟังก์ชัน `where` หรือในภาษาอื่นมักจะเรียกว่า **filter** มีไว้ทำการเลือกเฉพาะ item ที่ตรงกับเงื่อนไข โดยจะต้องสร้างฟังก์ชันประเภท **predicate** หรือฟังก์ชันที่ตอบค่าเป็น `bool` (`true` = เลือกไอเทมนี้, `false` = ไม่เอาไอเทมนี้)

เช่นหากเราต้องการเลือกเฉพาะตัวเลขที่เป็น**เลขคู่** ก็ต้องสร้าง predicate function แบบนี้ `(x) => x % 2 == 0`
```dart
List<int> num1 = [1, 2, 3, 4, 5];

Iterable<int> num2 = num1.where((x) => x % 2 == 0);

List<int> num3 = num1.where((x) => x % 2 == 0).toList();

// num2 is Iterable (2, 4)
// num3 is List [2, 4]

// เขียนแบบใช้ลูป
var num1 = [1, 2, 3, 4, 5];
var num2 = <int>[];
for(var x in num1){
    if(x % 2 == 0){
        num2.add(x);
    }
}
```
เช่นเดียวกับ `map` คือ `where` รีเทิร์นค่ากลับเป็น `Iterable`

> ส่วนกลับของ `where` คือ `removeWhere` ซึ่งจะลบตัวที่ตรงกับ **predicate** ทิ้งออกไปแทน 
> 
> ข้อควรระวังคือ `removeWhere` เป็น Mutable นะ!

### `firstWhere()` และ `singleWhere()`
😤 Immutable | 🍐 Scala

นอกจากนี้ ภาษาDartยังมีฟังก์ชันสำหรับเลือกค่าที่ตรงเงื่อนไขอีก 2 ตัวคือ `firstWhere` และ `singleWhere` ซึ่งจะตอบตัวเลขที่ตรงเงื่อนไขกลับมาแค่**ตัวเดียว**เท่านั้น

ข้อแตกต่างระหว่าง firstWhere และ singleWhere คือการใช้ `singleWhere` จะต้องมีค่านั้นเพียงตัวเดียวเท่านั้น ถ้ามีตัวที่ตรงกับเงื่อนไขหลายตัวจะเจอ `Error: Bad state: Too many elements`
```dart
List<int> list = [1, 2, 3, 4, 5];
int num = num1.firstWhere(
    (x) => x % 2 == 0,
    orElse: () => null
);

List<int> list = [1, 2, 3, 4, 5];
int num = num1.singleWhere(
    (x) => x % 2 == 0,
    orElse: () => null
);
```

### `reduce()`, `fold()`
😤 Immutable | 🍐 Scala
reduce จะใช้ในกรณีที่เราต้องการรวม item ทุกตัวในลิสต์ให้ออกมาเป็นค่าใหม่ค่าเดียวเท่านั้นด้วย**วิธีการอะไรบางอย่าง** ซึ่งเราต้องเขียนขึ้นมาด้วยฟังก์ชันที่เรียกว่า **combine**

เช่นหากเราต้องการเอา item ทุกตัวในลิสต์มา `+` กัน ให้เขียน combine function ว่า `(x, y) => x + y` นั่นคือหากเรามีตัวเลขสองตัว ให้เอาสองตัวนั้นมาบวกกัน
```dart
List<int> list = [1, 2, 3, 4, 5];
int num = list.reduce((x, y) => x + y);

// num is 1 + 2 + 3 + 4 + 5
// num is 15

// เขียนแบบใช้ลูป
int num = list.first;
for(var x in list.skip(1)){
    num += x;
}
```
แต่สังเกตดูว่ามันจะเท่ากับการเขียนลูป โดย accumulator (หมายถึงตัวแปรที่เอาไว้สะสมค่า ในกรณีนี้คือ num) จะต้องเริ่มต้นที่ item ตัวแรกเป็นตัวตั้ง แต่ถ้าเราต้องการกำหนดค่าเริ่มต้นเอง เราสามารถเขียนแบบนี้ได้
```dart
List<int> list = [1, 2, 3, 4, 5];
int num = [100, ...list].reduce((x, y) => x + y);
// num is 100+ 1 + 2 + 3 + 4 + 5
```
แต่ก็อย่างที่เห็นค่ามันไม่สวยเลย! ในเคสนี้เราสามารถเปลี่ยนจาก `reduce` ไปใช้ `fold` แทนได้ แบบนี้
```dart
List<int> list = [1, 2, 3, 4, 5];
int num = list.fold(100, (x, y) => x + y);
// num is 100+ 1 + 2 + 3 + 4 + 5
```

ลองมาดูตัวอย่างกันอีก

เช่นเราต้องการหาค่าที่มากที่สุดในลิสต์เราอาจจะเขียน combine แบบนี้
```dart
import 'dart:math';

List<int> list = [1, 2, 3, 4, 5];
int maxNum = list.reduce((x, y) => max(x, y));
```
และไม่จำเป็นจะต้องใช้กับลิสต์ของ `int` เท่านั้น สามารถเอาไปแอพพลายใช้งานกับ `String` ก็ยังได้ เช่นต้องการจะเชื่อมสตริง (concat) เข้าด้วยกันด้วย `-` ก็เขียนแบบนี้ได้
```dart
List<String> list = ['A', 'B', 'C'];
String str = list.reduce((x, y) => '$x-$y');
// str is "A-B-C"
```


## หมวด Utility

### `length`, `isEmpty`
🍐 Scala

`length` ใช้เพื่อเช็กว่าตอนนี้ในลิสต์มี item อยู่กี่ตัว ส่วน `isEmpty` ใช้เช็กว่าตอนนี้ item ในลิสต์ไม่มีเลยใช่หรือไม่
```dart
List<int> list = [1, 2, 3, 4, 5];
print(list.length);     // 5
print(list.isEmpty);    // false
```

### `sublist()`, `getRange()`
😤 Immutable | 
`sublist()` = 🍇 List, `getRange()` = 🍒 Iterable

ใช้สำหรับตัดลิสต์ย่อยออกมาโดยเราต้องกำหนด index ที่เริ่มและจบ (start, end) **ข้อควรระวังคือ end จะไม่ถูกรวมอยู่ในคำตอบด้วย**

```dart
List<int> list = [1, 2, 3, 4, 5];
list.sublist(1, 3);     // [2, 3]
list.getRange(1, 3);    // (2, 3)
```

### `first`, `last`
🍐 Scala

ใช้ขอ item ตัวแรกสุดและตัวท้ายสุดของลิสต์ มีค่าเท่ากับ `list[0]` และ `list[list.length - 1]`
```dart
List<int> list = [1, 2, 3, 4, 5];
print(list.first);      // 1
print(list.last);       // 5
```

### `take()`, `skip()`, `takeWhile()`, `skipWhile()`
😤 Immutable | 🍒 Iterable

คล้ายกับ `getRange` แต่เป็นการเลือก/ข้ามจาก head หรือหัวแถวของลิสต์แทน

สำหรับ `takeWhile` กับ `skipWhile` จะเป็นการสร้างฟังก์ชันเงื่อนไขขึ้นมาเช็กแทน
```dart
List<int> list = [1, 2, 3, 4, 5];

li.take(2)  // (1, 2)
li.skip(2)  // (3, 4, 5)

li.takeWhile((x) => x < 3)  // (1, 2)
li.skipWhile((x) => x < 3)  // (3, 4, 5)
```

### `contains()`, `any()`, `every()`
🍐 Scala

ใช้สำหรับหาว่ามี item ที่ต้องการหรือไม่

สำหรับ `any` (ข้อแค่ตรงเงื่อนไขอย่างน้อย 1 ตัว) และ `every` (ไอเทมทุกตัวในลิสต์จะต้องตรงเงื่อนไขทั้งหมด) จะต้องสร้างฟังก์ชัน **test** ขึ้นมา
```dart
List<int> list = [1, 2, 3, 4, 5];

list.contains(3);               // true
list.any((x) => x % 2 == 0);    // true
list.every((x) => x % 2 == 0);  // false
```

### `sort()`
😱 Mutable | 🥚 void

ใช้สำหรับเรียงข้อมูลในลิสต์
```dart
List<int> list = [4, 2, 1, 5, 3];
list.sort();

// list is [1, 2, 3, 4, 5]
```
ในกรณีที่ต้องการเรียงแบบอื่นเช่น **descending list** หรือลิสต์ที่เรียงจาก **มาก -> น้อย** แทนหรือต้องการ sort ลิสต์ของ `object` ที่ไม่ใช่ Primitive Type เราจะต้องสร้างฟังก์ชัน comparator ขึ้นมา **โดยให้ยึดหลักการว่า ตัวแรก-ตัวที่สอง**เสมอ เช่น
```dart
List<int> list = [4, 2, 1, 5, 3];
list.sort((first, second) => -(first-second));
// นำมาลบกัน แต่ใส่ค่า - เอาไว้เพื่อให้เรียงจากมากไปน้อยแทน

class Score{
    int point;
    People(this.point);
}
var list = [Score(20), Score(5), Score(10))];
list.sort((first, second) => first.point - second.point);
```


### `shuffle()`
😱 Mutable | 🥚 void

เมื่อมีการสั่งให้เรียงข้อมูลด้วย `sort` แล้วก็ต้องมีคำสั่งที่เป็นส่วนกลับกัน นั่นคือ `shuffle` หรือการสลับตำแหน่งลิสต์แบบ random (คล้ายสับกองการ์ดให้เรียงแบบมั่วๆ นั่นแหละ)

ซึ่งการสั่ง `shuffle` แต่ละครั้งก็จะให้ผลออกมาไม่เหมือนกัน เพราะมัน random นะ
```dart
List<int> list = [1, 2, 3, 4, 5];

list.shuffle();
// list is [4, 2, 1, 5, 3]

list.shuffle(); 
// call again!
// list is [3, 4, 5, 2, 1]
```
แต่ถ้าอยากให้ผลการสลับออกมาเหมือนเดิมเราสามารถใส่ **seed** ซึ่งเป็นอ็อบเจก `Random` ลงไปได้ โดยถ้า seed เป็นเลขเดิมจะได้ผลลัพธ์ออกมาเหมือนเดิม
```dart
import 'dart:math';

List<int> list = [1, 2, 3, 4, 5];

list.shuffle(Random(100));
// list is [2, 5, 1, 3, 4]

list.shuffle(Random(100));
// call again!
// list is [2, 5, 1, 3, 4] same result!
```


### `reversed`
😤 Immutable | 🍒 Iterable

คำสั่งสำหรับสร้างลิสต์ตัวใหม่ที่กลับหัว item ทั้งหมดแทน
```dart
List<int> list = [1, 2, 3, 4, 5];
var re = list.reversed;

// list is List [1, 2, 3, 4, 5]
// re is Iterable [5, 4, 3, 2, 1]
```