โครงสร้างข้อมูลหรือ 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()
List myList = [10, 20, 30, 40];
ซึ่งจะได้ค่าดังนี้
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 กันเอง
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 ได้โดยใช้สัญลักษณ์ ``
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
นั่นคืออนุญาตให้เก็บตัวแปรชนิดอะไรก็ได้
loop
การวนลูปสามารถทำได้ด้วยวิธีใช้ตัว counter แบบดั้งเดิม และนับขนาดของลิสต์ด้วย .length
List<int> numbers = [10, 20, 30, 40];
for(var i = 0; i < numbers.length; i++){
print(numbers[i]);
}
หรือใช้ for-in
List<int> numbers = [10, 20, 30, 40];
for(var number in numbers){
print(number);
}
การเพิ่มหรือลบ item ออกจาก List
การเพิ่ม item ลงใน List จะใช้คำสั่ง
add
- ใช้สำหรับเพิ่ม item ที่ตำแหน่งท้ายสุดของลิสต์insert
- ใช้สำหรับเพิ่ม item ลงที่ตำแหน่งตรงกลางลิสต์
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
แทน
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
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
ซึ่งมีข้อแตกต่างกันคือ
var list = const [1, 2, 3];
// const List ไม่สามารถแก้ไขหรือเพิ่ม/ลดข้อมูลได้
list.add(4); // Error!
// แต่เราสามมรถกำหนดค่าให้ตัวแปร list ใหม่ได้
list = [5, 6, 7];
ส่วน final
นั้นจะเป็นแบบนี้
final list = [1, 2, 3];
// final ถูกเซ็ตที่ตัวแปร List ไม่ใช่ค่าของลิสต์
list.add(4); // Ok!
// แต่เราไม่สามมรถกำหนดค่าใหม่ให้ตัวแปร list ใหม่ได้
list = [5, 6, 7]; // Error!
และเราสามารถสร้างลิสต์ที่เป็นทั้ง final และ const ได้
final list = const [1, 2, 3];
Spread Operator
เราสามารถใช้ ...
เพื่อแยกลิสต์ได้ หากใช้เขียน JavaScript มาก่อนน่าจะคุ้นกับคำสั่งนี้
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 ได้
var list1;
var list2 = [0, ...?list1, 5];
//list2 is [0, 5]
นอกจากนี้เรายังสามารถใช้คำสั่ง if
และ for
ในลิสต์ได้ด้วย
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 ได้)
Map data = {'id': 1, 'name': 'Ann'};
print(data['id']); // 1
print(data['name']); // Ann
และเหมือนกับ List คือถ้าเราไม่ได้กำหนดชนิดข้อมูลมันจะถูกกำหนดอัตโนมัติให้เป็น Map
ดังนั้นคำแนะนำในการใช้ Map คือควรกำหนดชนิดของ key และ value ทุกครั้ง
Map<String, int> score = {'A': 100, 'B': 200};
//หรือ
var score = <String, int>{'A': 100, 'B': 200};
การเพิ่มหรือลบ item ออกจาก Map
การเพิ่ม item ลงใน Map นั้นทำง่ายกว่า List เพราะเราสามารถกำหนดค่าลงไปตรงๆ ได้เลย
Map<String, int> data = {'A': 100};
data['B'] = 200;
ส่วนการลบข้อมูลออกเราจะใช้คำสั่ง remove
ซึ่งจะใช้การกำหนดด้วย key ไม่ใช่ value
Map<String, int> data = {'A': 1, 'B': 2};
data.remove('A');
print(data); // {B: 2}
หรือถ้าต้องการตั้งเงื่อนไขการลบเองก็สามารถใช้คำสั่ง removeWhere
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 ซึ่งจะไม่สามารถเปลี่ยนแปลงค่าอะไรได้เลย
final constantMap = const {
2: 'helium',
10: 'neon',
18: 'argon',
};
.map() method
เมธอด map
เป็นคนละตัวกันคลาส Map
นะ เป็นเมธอดที่ใช้ได้กับทั้ง List และ Map เลย
วิธีการใช้ map
คือหากเราต้องการจะแปลงค่าทุกค่าใน List หรือ Map ให้เป็นอีกค่าหนึ่ง เช่นถ้าเรามี items ตัวหนึ่งที่ต้องการเอาค่าทุกค่าไป x 10
จากปกติที่เราเขียนกันแบบใช้ลูปธรรมดา
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]
ก็สามารถเขียนเป็นแบบนี้ได้
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
แทน
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 ได้โดย
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
Map<String, int> m = {'A': 10, 'B': 20, 'C': 30};
m.keys // [A, B, C]
m.values // [10, 20, 30]