Dart 102: โครงสร้างข้อมูลพื้นฐาน List และ Map
Tags: beginners, programming, dart, flutter
Published: 2020-02-10 17:15:36

---
canonical_url: https://www.tamemo.com/post/173/dart-102-data-structure-list-and-map/
---

โครงสร้างข้อมูลหรือ Data Structure เป็นสิ่งที่ต้องมีในทุกภาษาโปรแกรม เพราะในการเขียนโปรแกรมจริงๆ การที่เรามีแค่ variable ไม่พอจะที่จัดการข้อมูลที่มากมายและซับซ้อนในแอพของเรา

### Data Structure: List และ Map
โครงสร้างข้อมูลใน Dart มี 2 ประเภทหลักๆ คือ
- List: เก็บข้อมูลแบบ linear, มีค่าเท่ากับ array ในบางภาษา
- Map: เก็บข้อมูลแบบ key-value, มีค่าเท่ากับ dictionary หรือ hash-table ในบางภาษา

# List
การสร้างลิสต์ของข้อมูลขึ้นมาทำได้โดยการประกาศตัวแปรชนิด `List`

List ใน Dart ไม่ใช่โครงสร้างแบบ fixed-length นั่นคือสามารถเพิ่ม/ลบ element ในลิสต์ได้ผ่านคำสั่ง เช่น `add()`, `remove()`, `removeAt()`

```dart
List myList = [10, 20, 30, 40];
```

ซึ่งจะได้ค่าดังนี้

```dart
print(myList);      // [10, 20, 30, 40]

print(myList[0]);   // 10
print(myList[1]);   // 20
print(myList[2]);   // 30
print(myList[3]);   // 40
```

แต่ก็ตามสไตล์ภาษามี type ทั่วๆ ไปคือการสร้าง List โดยที่ไม่ระบุอะไรนั้นมีความเสี่ยงอย่างมาก! เพราะ compiler จะไม่รู้ว่า element แต่ละตัวข้างในเป็นตัวแปรชนิดอะไร จึงช่วยเราเช็กค่าไม่ได้เลย --> ต้องไปเสี่ยงดวงตอน runtime กันเอง

```dart
List myList = [1, 'hi', true];

int x    = myList[0];  // ok!
String s = myList[1];  // ok!
bool b   = myList[2];  // ok!

int y    = myList[1];  // Runtime Error! TypeError: String is not a subtype of type int
```

ในภาษา Dart มีฟีเจอร์ที่เรียกว่า **Type Interface** หรือ **Generic** ให้ใช้ นั่นคือการส่ง type เข้าไปใน class หรือ function ได้โดยใช้สัญลักษณ์ `<>`

```dart
List<int> myList = [10, 20, 30, 40];  
// แบบนี้ ok!

var myList = <int>[10, 20, 30, 40];  
// แบบนี้ก็ ok!

//แต่...

List<int> myList = [10, 20, 'hi'];
//Error: A value of type 'String' can't be assigned to a variable of type 'int'.
//  List<int> myList = [10, 20, 'hi'];
//                               ^
```

การประกาศ `List` โดยไม่มี Generic จะถูกมองว่าเป็น `List<dynamic>` นั่นคืออนุญาตให้เก็บตัวแปรชนิดอะไรก็ได้

### loop

การวนลูปสามารถทำได้ด้วยวิธีใช้ตัว counter แบบดั้งเดิม และนับขนาดของลิสต์ด้วย `.length`
```dart
List<int> numbers = [10, 20, 30, 40];
for(var i = 0; i < numbers.length; i++){
    print(numbers[i]);
}
```
หรือใช้ `for-in` 
```dart
List<int> numbers = [10, 20, 30, 40];
for(var number in numbers){
    print(number);
}
```

### การเพิ่มหรือลบ item ออกจาก List
การเพิ่ม item ลงใน List จะใช้คำสั่ง 
- `add` - ใช้สำหรับเพิ่ม item ที่ตำแหน่ง**ท้ายสุดของลิสต์** 
- `insert` - ใช้สำหรับเพิ่ม item ลงที่ตำแหน่งตรงกลางลิสต์
```dart
var list = ['A', 'B', 'C'];

list.add('D');          // เพิ่ม D เข้าไปท้ายสุดของลิสต์
print(list);            // [A, B, C, D]

list.insert(0, 'E');    // เพิ่ม E เข้าไปที่ตำแหน่งที่ 0
print(list);            // [E, A, B, C, D]

list.insert(3, 'F');    // เพิ่ม F เข้าไปที่ตำแหน่งที่ 3
print(list);            // [E, A, B, F, C, D]

list.insert(6, 'G');    // เพิ่ม G เข้าไปที่ตำแหน่งที่ 6
print(list);            // [E, A, B, F, C, D, G]
```
ในภาษา Dart **ไม่**มีคำสั่ง `pop` ซึ่งเอาไว้เพิ่ม item เข้าไปที่ตำแหน่งแรกสุดของลิสต์ แต่เราสามารถใช้การ `insert` ในตำแหน่งที่ 0 แทนได้

แต่ถ้า item ที่เราต้องการเพิ่มลงไปเป็น List (หรือ Iterable) เราจะใช้คำสั่ง `addAll` และ `insertAll` แทน

```dart
var list = ['A'];

list.addAll(['B', 'C']);
print(list);            // [A, B, C]

list.insertAll(2, ['D', 'E']);
print(list);            // [A, B, D, E, C]
```
ส่วนการลบ item ออกจากลิสต์จะใช้คำสั่ง `remove`
```dart
List<String> list;

list = ['A', 'B', 'C', 'D', 'E'];
list.remove('A');
print(list);    // [B, C, D, E]

list = ['A', 'B', 'C', 'D', 'E'];
list.removeAt(0);
print(list);    // [B, C, D, E]

list = ['A', 'B', 'C', 'D', 'E'];
list.removeRange(1,3);
print(list);    // [A, D, E]

var numbers = [1, 2, 3, 4, 5];
numbers.removeWhere( (number) => number % 2 == 0 );
print(numbers);    // [1, 3, 5]
```

### final List และ const List
เราสามารถสร้างลิสต์ที่ไม่สามารถเปลี่ยนแปลงค่าได้ด้วยการใช้ `final` และ `const` ซึ่งมีข้อแตกต่างกันคือ
```dart
var list = const [1, 2, 3];

// const List ไม่สามารถแก้ไขหรือเพิ่ม/ลดข้อมูลได้
list.add(4);        // Error!

// แต่เราสามมรถกำหนดค่าให้ตัวแปร list ใหม่ได้
list = [5, 6, 7];
```
ส่วน `final` นั้นจะเป็นแบบนี้
```dart
final list = [1, 2, 3];

// final ถูกเซ็ตที่ตัวแปร List ไม่ใช่ค่าของลิสต์
list.add(4);        // Ok!

// แต่เราไม่สามมรถกำหนดค่าใหม่ให้ตัวแปร list ใหม่ได้
list = [5, 6, 7];   // Error!
```
และเราสามารถสร้างลิสต์ที่เป็นทั้ง final และ const ได้

```dart
final list = const [1, 2, 3];
```

### Spread Operator
เราสามารถใช้ `...` เพื่อแยกลิสต์ได้ หากใช้เขียน JavaScript มาก่อนน่าจะคุ้นกับคำสั่งนี้
```dart
var list1 = [1, 2, 3, 4];
var list2 = [0, ...list1, 5];
//list2 is [0, 1, 2, 3, 4, 5]
```
และเพราะตัวแปรใน Dart สามารถเป็น null ได้ เราสามารถใช้คำสั่ง **null-aware spread operator** ได้
```dart
var list1;
var list2 = [0, ...?list1, 5];
//list2 is [0, 5]
```
นอกจากนี้เรายังสามารถใช้คำสั่ง `if` และ `for` ในลิสต์ได้ด้วย
```dart
var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];

var listOfInts = [1, 2, 3];
var listOfStrings = [
  'number 0',
  for (var i in listOfInts) 'number $i'
];
```


# Map
map เป็น Data Structure อีกตัวหนึ่ง ซึ่งเป็นแบบ key-value (คล้ายๆ List ที่สามารถกำหนดชื่อของ index ได้)

```dart
Map data = {'id': 1, 'name': 'Ann'};
print(data['id']);      // 1
print(data['name']);    // Ann
```
และเหมือนกับ List คือถ้าเราไม่ได้กำหนดชนิดข้อมูลมันจะถูกกำหนดอัตโนมัติให้เป็น `Map<dynamic, dynamic>`

ดังนั้นคำแนะนำในการใช้ Map คือควรกำหนดชนิดของ key และ value ทุกครั้ง
```dart
Map<String, int> score = {'A': 100, 'B': 200};
//หรือ
var score = <String, int>{'A': 100, 'B': 200};
```

### การเพิ่มหรือลบ item ออกจาก Map
การเพิ่ม item ลงใน Map นั้นทำง่ายกว่า List เพราะเราสามารถกำหนดค่าลงไปตรงๆ ได้เลย
```dart
Map<String, int> data = {'A': 100};
data['B'] = 200;
```
ส่วนการลบข้อมูลออกเราจะใช้คำสั่ง `remove` ซึ่งจะใช้การกำหนดด้วย **key** ไม่ใช่ **value**
```dart
Map<String, int> data = {'A': 1, 'B': 2};
data.remove('A');
print(data);    // {B: 2}
```
หรือถ้าต้องการตั้งเงื่อนไขการลบเองก็สามารถใช้คำสั่ง `removeWhere`
```dart
Map<String, int> data = {
    'A': 1, 
    'B': 2, 
    'C': 3,
};
data.removeWhere((key, value){
    return value==2 || key=='A';
});
print(data);    // {C: 3}
```
### final Map และ const Map
เหมือนกับ List นั่นคือเราสามารถเราสามารถกำหนด const Map ซึ่งจะไม่สามารถเปลี่ยนแปลงค่าอะไรได้เลย
```dart
final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};
```

## .map() method
เมธอด `map` เป็นคนละตัวกันคลาส `Map` นะ เป็นเมธอดที่ใช้ได้กับทั้ง List และ Map เลย

วิธีการใช้ `map` คือหากเราต้องการจะแปลงค่าทุกค่าใน List หรือ Map ให้เป็นอีกค่าหนึ่ง เช่นถ้าเรามี items ตัวหนึ่งที่ต้องการเอาค่าทุกค่าไป x 10 

จากปกติที่เราเขียนกันแบบใช้ลูปธรรมดา
```dart
List<int> items = [1, 2, 3, 4];
List<int> items10;
for(var item in items){
    items10.add(item * 10);
}

// items10 is [10, 20, 30, 40]
```
ก็สามารถเขียนเป็นแบบนี้ได้
```dart
List<int> items = [1, 2, 3, 4];
List<int> items10 = items.map((item) => item * 10).toList();

// items10 is [10, 20, 30, 40]
```
เวลาเขียนให้สร้างฟังก์ชันสำหรับบอกว่า ถ้าเรามี item หนึ่ง จะแปลงให้มันเป็นค่าอะไรเท่านั้นพอ

แต่ `map` ของ List นั้นไม่ได้รีเทิร์นค่ากลับมาเป็น List แต่ให้ค่ากลับมาเป็น Iterable นั่นคือถ้าเราต้องการให้มันกลับมาเป็น List จะต้องใช้คำสั่ง `toList()` ต่ออีกที

ส่วนถ้าเป็น Map อาจจะใช้ยากขึ้นหน่อยเพราะจะต้องเขียนทั้ง key และ value และต้องตอบค่ากลับมาเป็น `MapEntry` แทน
```dart
Map<String, int> data = {
    'A': 1, 
    'B': 2, 
    'C': 3,
};

Map<String, int> data2 = data.map((key, value){
    return MapEntry('#$key', value * 10);
});

// data2 is {#A: 10, #B: 20, #C: 30}
```

## Convert List-Map
เราสามารถแปลง List --> Map ได้โดย
```dart
List<String> data = ['A', 'B', 'C'];

Map<int, String> m = data.asMap();
// m is {0: A, 1: B, 3: C}
```
ซึ่ง key ของ Map ที่ได้ออกมาจะเป็น `int` และได้ค่ามาจาก index ของ List นั้นๆ

ส่วนการแปลง Map --> List นั้นง่ายกว่าแต่เราจะต้องเลือกว่าจะเอา List of key หรือ List of value
```dart
Map<String, int> m = {'A': 10, 'B': 20, 'C': 30};

m.keys      // [A, B, C]
m.values    // [10, 20, 30]
```