DEV Community

Ta for tamemo

Posted on • Updated on • Originally published at tamemo.com

[Dart] รวมคำสั่งดีๆ เอาไว้ใช้กับ List

ในบทความที่เรา เราแนะนำการใช้งาน 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

List<int> list = List<int>.generate(10, (i) => i + 1);
// List [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Enter fullscreen mode Exit fullscreen mode

List Comprehension

😤 Immutable | 🍇 List

ซึ่งแทนที่จะใช้ .generate() เราสามารถสร้างลิสต์โดยใช้การวน for แทนก็ได้

List<int> list = [ for(var i=0; i<10; i++) i + 1 ];
// List [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Enter fullscreen mode Exit fullscreen mode

หมวดการคัดลอก/เพิ่ม/ลบ

form(), of()

😤 Immutable | 🍇 List

เป็น 2 คำสั่งที่ใช้สร้างลิสต์จากลิสต์อีกตัวหนึ่ง ซึ่งมีข้อแตกต่างกันเล็กน้อยนั่นคือถ้าเราสั่งด้วย from ผลที่ได้จะเป็นไทป์แบบ dynamic แต่ถ้าใช้ of ผลที่ได้จะได้ไทป์ของลิสต์ต้นฉบับมาด้วย

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!
Enter fullscreen mode Exit fullscreen mode

add(), addAll()

😱 Mutable | 🥚 void

ใช้เพื่อเพิ่ม item ลงไปในลิสต์ โดยใช้ add สำหรับเพิ่ม item แบบทีละตัวและใช้ addAll สำหรับการเพิ่มหลายๆ ตัวในรูปแบบของ List หรือ Iterable

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]
Enter fullscreen mode Exit fullscreen mode

followedBy()

😤 Immutable | 🍒 Iterable

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

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)
Enter fullscreen mode Exit fullscreen mode

แต่ใน Dart การต่อ List สามารถใช้การ + หรือ ... (spread operator) แทนได้

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]
Enter fullscreen mode Exit fullscreen mode

หมวด map/filter/reduce

forEach()

😤 Immutable | 🥚 void

ใช้ในการวนลูป item ทุกตัวในลิสต์ในรูปแบบการสร้างฟังก์ชันขึ้นมา (ซึ่งมาผลเทียบกับการวน for แบบธรรมดานั่นแหละ)

var list = [1, 2, 3];
list.forEach((item) => print(item));

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

map()

😤 Immutable| 🍒 Iterable

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

เช่น ในตัวอย่างข้างล่าง เราต้องการคูณเลขทุกตัวในลิสต์ด้วย 10 ก็ให้สร้าง (x) => x * 10 ฟังก์ชันที่รับตัวเลขเข้าไปหนึ่งตัวแล้วตอบค่านั้นเอาไปคูณด้วย 10 กลับมา

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);
}
Enter fullscreen mode Exit fullscreen mode

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

where() (หรือ filter)

😤 Immutable | 🍒 Iterable

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

เช่นหากเราต้องการเลือกเฉพาะตัวเลขที่เป็นเลขคู่ ก็ต้องสร้าง predicate function แบบนี้ (x) => x % 2 == 0

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

เช่นเดียวกับ 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

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
);
Enter fullscreen mode Exit fullscreen mode

reduce(), fold()

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

เช่นหากเราต้องการเอา item ทุกตัวในลิสต์มา + กัน ให้เขียน combine function ว่า (x, y) => x + y นั่นคือหากเรามีตัวเลขสองตัว ให้เอาสองตัวนั้นมาบวกกัน

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;
}
Enter fullscreen mode Exit fullscreen mode

แต่สังเกตดูว่ามันจะเท่ากับการเขียนลูป โดย accumulator (หมายถึงตัวแปรที่เอาไว้สะสมค่า ในกรณีนี้คือ num) จะต้องเริ่มต้นที่ item ตัวแรกเป็นตัวตั้ง แต่ถ้าเราต้องการกำหนดค่าเริ่มต้นเอง เราสามารถเขียนแบบนี้ได้

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
Enter fullscreen mode Exit fullscreen mode

แต่ก็อย่างที่เห็นค่ามันไม่สวยเลย! ในเคสนี้เราสามารถเปลี่ยนจาก reduce ไปใช้ fold แทนได้ แบบนี้

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
Enter fullscreen mode Exit fullscreen mode

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

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

import 'dart:math';

List<int> list = [1, 2, 3, 4, 5];
int maxNum = list.reduce((x, y) => max(x, y));
Enter fullscreen mode Exit fullscreen mode

และไม่จำเป็นจะต้องใช้กับลิสต์ของ int เท่านั้น สามารถเอาไปแอพพลายใช้งานกับ String ก็ยังได้ เช่นต้องการจะเชื่อมสตริง (concat) เข้าด้วยกันด้วย - ก็เขียนแบบนี้ได้

List<String> list = ['A', 'B', 'C'];
String str = list.reduce((x, y) => '$x-$y');
// str is "A-B-C"
Enter fullscreen mode Exit fullscreen mode

หมวด Utility

length, isEmpty

🍐 Scala

length ใช้เพื่อเช็กว่าตอนนี้ในลิสต์มี item อยู่กี่ตัว ส่วน isEmpty ใช้เช็กว่าตอนนี้ item ในลิสต์ไม่มีเลยใช่หรือไม่

List<int> list = [1, 2, 3, 4, 5];
print(list.length);     // 5
print(list.isEmpty);    // false
Enter fullscreen mode Exit fullscreen mode

sublist(), getRange()

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

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

List<int> list = [1, 2, 3, 4, 5];
list.sublist(1, 3);     // [2, 3]
list.getRange(1, 3);    // (2, 3)
Enter fullscreen mode Exit fullscreen mode

first, last

🍐 Scala

ใช้ขอ item ตัวแรกสุดและตัวท้ายสุดของลิสต์ มีค่าเท่ากับ list[0] และ list[list.length - 1]

List<int> list = [1, 2, 3, 4, 5];
print(list.first);      // 1
print(list.last);       // 5
Enter fullscreen mode Exit fullscreen mode

take(), skip(), takeWhile(), skipWhile()

😤 Immutable | 🍒 Iterable

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

สำหรับ takeWhile กับ skipWhile จะเป็นการสร้างฟังก์ชันเงื่อนไขขึ้นมาเช็กแทน

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)
Enter fullscreen mode Exit fullscreen mode

contains(), any(), every()

🍐 Scala

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

สำหรับ any (ข้อแค่ตรงเงื่อนไขอย่างน้อย 1 ตัว) และ every (ไอเทมทุกตัวในลิสต์จะต้องตรงเงื่อนไขทั้งหมด) จะต้องสร้างฟังก์ชัน test ขึ้นมา

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
Enter fullscreen mode Exit fullscreen mode

sort()

😱 Mutable | 🥚 void

ใช้สำหรับเรียงข้อมูลในลิสต์

List<int> list = [4, 2, 1, 5, 3];
list.sort();

// list is [1, 2, 3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

ในกรณีที่ต้องการเรียงแบบอื่นเช่น descending list หรือลิสต์ที่เรียงจาก มาก -> น้อย แทนหรือต้องการ sort ลิสต์ของ object ที่ไม่ใช่ Primitive Type เราจะต้องสร้างฟังก์ชัน comparator ขึ้นมา โดยให้ยึดหลักการว่า ตัวแรก-ตัวที่สองเสมอ เช่น

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);
Enter fullscreen mode Exit fullscreen mode

shuffle()

😱 Mutable | 🥚 void

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

ซึ่งการสั่ง shuffle แต่ละครั้งก็จะให้ผลออกมาไม่เหมือนกัน เพราะมัน random นะ

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]
Enter fullscreen mode Exit fullscreen mode

แต่ถ้าอยากให้ผลการสลับออกมาเหมือนเดิมเราสามารถใส่ seed ซึ่งเป็นอ็อบเจก Random ลงไปได้ โดยถ้า seed เป็นเลขเดิมจะได้ผลลัพธ์ออกมาเหมือนเดิม

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!
Enter fullscreen mode Exit fullscreen mode

reversed

😤 Immutable | 🍒 Iterable

คำสั่งสำหรับสร้างลิสต์ตัวใหม่ที่กลับหัว item ทั้งหมดแทน

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]
Enter fullscreen mode Exit fullscreen mode

Top comments (0)