在Dart學習的第02天,我們通過基礎語法說明和樣例代碼的方式,學習了Dart的16個基礎語法,這些基礎語法給我們後面編寫的Flutter程式打下來堅實基礎。今天,我們繼續深入學習Dart乃至所有編程語言都非常重要的部分:可迭代的集合…… ...
Dart官網代碼實驗室:https://dart.dev/codelabs/iterables
重要說明:本博客基於Dart官網代碼實驗室,但並不是簡單的對官網文章進行翻譯,我會根據個人研發經驗,在覆蓋官網文章核心內容情況下,加入自己的一些擴展問題和問題演示和總結,包括名稱解釋、使用場景說明、代碼樣例覆蓋等。
可迭代集合說明
什麼是集合?集合代表一組對象的組合,集合中的對象一般稱為元素,元素的數量可以是0個(即空集合),也可以有多個。
什麼是迭代?迭代即順序訪問,即這個集合中的元素可從頭到尾進行順序訪問(一般在迴圈遍歷中使用)。在Java中,我們知道有個Iterable迭代類,在Dart中也有這個類(https://api.dart.dev/stable/3.1.3/dart-core/Iterable-class.html),我們用的最多的就是List和Set介面,他們是迭代集合的基礎,也是一個應用程式的基礎。
Map是可迭代集合嗎?Map類代表了一組元素,因此它是一個集合。但Map類沒有實現Iterable類(https://api.dart.dev/stable/3.1.3/dart-core/Map-class.html),因此它不可迭代,也就是說:Map是不可迭代的集合。但是它的元素集合(Map#entries)、鍵集合(Map#keys)和值集合(Map#values)都是可迭代集合。
迭代和集合訪問元素的不同方式:迭代通過elementAt(index)
方法訪問元素,而集合可以通過[index]
下標的方法訪問元素:
void main() {
// 1. 迭代和集合訪問元素
final List<int> alist = [1, 2, 3];
final Iterable<int> iterable = alist;
print('1. 迭代和集合訪問元素: ${iterable.elementAt(2)} <-> ${alist[2]}');
// 結果:1. 迭代和集合訪問元素: 3 <-> 3
}
可迭代集合元素訪問方法
可迭代集合元素的訪問方式有很多種,包括for迴圈,集合的第1個元素,集合的最後1個元素,尋找符合條件的第1個元素等。
for-in迴圈訪問集合元素
這種方式使用最多了,各種編程語言基本類似:
void main() {
final List<int> alist = [1, 2, 3];
// 2.1. for迴圈訪問集合元素
for (final element in alist) {
print('2.1. for迴圈訪問集合元素: $element');
}
// 結果:
// 2.1. for迴圈訪問集合元素: 1
// 2.1. for迴圈訪問集合元素: 2
// 2.1. for迴圈訪問集合元素: 3
}
first第一個和last最後一個元素:空集合異常
通過first和last屬性,可直接訪問集合的第1個和最後1個元素:
void main() {
final List<int> alist = [1, 2, 3];
// 2.2 first第一個和last最後一個元素
print('2.2. first第一個和last最後一個元素: first=${alist.first}, last=${alist.last}');
// 結果:2.2. first第一個和last最後一個元素: first=1, last=3
}
擴展問題:如果集合只有1個元素,或者集合是空集合,first和last返回的內容是什麼呢?
void main() {
final List<int> oneList = [1];
print('2.2. first第一個和last最後一個元素: one.first=${oneList.first}, one.last=${oneList.last}');
// 結果:2.2. first第一個和last最後一個元素: one.first=1, one.last=1
final List<int> emptyList = [];
print('2.2. first第一個和last最後一個元素: empty.first=${emptyList.first}, empty.last=${emptyList.last}');
// 結果:Bad state: No element
}
// 異常如下:
Unhandled exception:
Bad state: No element
#0 List.first (dart:core-patch/growable_array.dart:343:5)
結論:只有1個元素的集合,first和last返回值相同,均為唯一的那個元素;對於空集合,first或者last均拋出異常!
firstWhere()/orElse符合條件的第1個元素
斷言:一個返回true/false的表達式、方法或者代碼塊。firstWhere()的本質就是遍歷集合,對每個元素進行斷言,然後返回第一個斷言為true的元素。
void main() {
final List<int> alist = [1, 2, 3];
// 2.3. firstWhere()符合條件的第1個元素
final int firstWhere = alist.firstWhere((element) => element > 1);
print('2.3. firstWhere()符合條件的第1個元素: $firstWhere');
// 結果:2.3. firstWhere()符合條件的第1個元素: 2
}
擴展問題:如果過濾條件均不滿足(即每個元素斷言均返回false),則返回結果是什麼呢?
void main() {
final List<int> alist = [1, 2, 3];
// 2.3. firstWhere()符合條件的第1個元素
final int firstWhere2 = alist.firstWhere((element) => element > 3);
print('2.3. firstWhere()符合條件的第1個元素2: $firstWhere2');
// 結果:Bad state: No element
}
// 異常如下:
Unhandled exception:
Bad state: No element
#0 ListBase.firstWhere (dart:collection/list.dart:132:5)
結論:和空集合一樣,當firstWhere()無法匹配到任何元素時,會拋出異常!
那麼,當無法匹配到任何元素時,有沒有辦法不拋出異常,而是返回一個預設值呢?
答案是有的:firstWhere()斷言之後,增加orElse預設值的命名參數,它是一個函數!
void main() {
final List<int> alist = [1, 2, 3];
// 2.3. firstWhere()符合條件的第1個元素
final int firstWhere2 = alist.firstWhere(
(element) => element > 3,
orElse: () => -1,
);
print('2.3. firstWhere()符合條件的第1個元素2: $firstWhere2');
// 結果:2.3. firstWhere()符合條件的第1個元素2: -1
}
any()/every()集合檢測(有趣的結果和源代碼)
當我們需要檢測集合中是否存在符合某個條件的元素,或者所有元素是否符合某個條件。在Dart語言中,我們可以使用any()和every()這兩個集合條件檢測方法,來達到我們的目的。
- any()方法:集合中存在任一一個元素符合條件
- every()方法:集合中的所有元素均符合條件
void main() {
final List<int> alist = [1, 2, 3];
// 2.4. any()/every()集合條件檢測
final bool anyGtTwo = alist.any((element) => element > 2);
final bool everyGtZero = alist.every((element) => element > 0);
final bool everyGtTwo = alist.every((element) => element > 2);
print('2.4. any()/every()集合條件檢測: anyGtTwo=$anyGtTwo, everyGtZero=$everyGtZero, everyGtTwo=$everyGtTwo');
// 結果:2.4. any()/every()集合條件檢測: anyGtTwo=true, everyGtZero=true, everyGtTwo=false
}
擴展問題:如果是個空集合,any()和every()的結果如何,會拋出異常嗎?
void main() {
final elist = <int>[];
final bool anyGtZero = elist.any((element) => element > 0);
final bool anyLtEqZero = elist.any((element) => element <= 0);
print('2.4. any()-空集合條件檢測: anyGtZero=$anyGtZero, anyLtEqZero=$anyLtEqZero');
// 結果:2.4. any()-空集合條件檢測: anyGtZero=false, anyLtEqZero=false
final bool evyGtZero = elist.every((element) => element > 0);
final bool evyLtEqZero = elist.every((element) => element <= 0);
print('2.4. every()-空集合條件檢測: evyGtZero=$evyGtZero, evyLtEqZero=$evyLtEqZero');
// 結果:2.4. every()-空集合條件檢測: evyGtZero=true, evyLtEqZero=true
}
有趣的結論:
- 針對空集合,any()和every()這2個集合條件檢測方法,不會拋出異常!
- 針對空集合,any()不論斷言結果如何,返回值均為false(這個結果比較容易理解)
- 針對空集合,every()不論斷言結果如何,返回值均為true(這個結果有的奇怪!)
我們打開源代碼,看看any()和every()的發現:any()的預設返回值為false,every()的預設返回值true
abstract mixin class Iterable<E> {
// 預設返回值:false
bool any(bool test(E element)) {
for (E element in this) {
if (test(element)) return true;
}
return false;
}
// 預設返回值:true
bool every(bool test(E element)) {
for (E element in this) {
if (!test(element)) return false;
}
return true;
}
}
where()/takeWhile()/skipWhile()篩選子集合
上面的any()/every()只是一個條件判斷,本節來看看,通過條件來篩選子集合:
- where()方法:遍歷集合每個元素,返回所有符合條件的子集合;若所有元素均不符合條件,則返回空集合,而不是拋出異常!
- takeWhile()方法:遍歷集合元素,構成新子集合元素,直到不符合條件的元素為止。
- skipWhile()方法:與takeWhile()相反,遍歷集合元素,過濾掉前面所有符合條件元素,取結合後面元素。
void main() {
final List<int> alist = [1, 2, 3];
// 2.5. where()/takeWhile()/skipWhile()篩選子集合
final gtOneList = alist.where((element) => element > 1);
print('2.5. where()-篩選子集合: gtOneList=$gtOneList');
// 結果:2.5. where()-篩選子集合: gtOneList=(2, 3)
final takeLtThreeList = alist.takeWhile((element) => element < 3);
print('2.5. takeWhile()-篩選子集合: takeLtTwoList=$takeLtThreeList');
// 結果:2.5. takeWhile()-篩選子集合: takeLtTwoList=(1, 2)
final skipNeqTwoList = alist.skipWhile((element) => element != 2);
print('2.5. skipWhile()-篩選子集合: skipNeqTwoList=$skipNeqTwoList');
// 結果:2.5. skipWhile()-篩選子集合: skipNeqTwoList=(2, 3)
}
map()轉換集合元素
對集合的每個元素,通過map()函數進行一次計算,可把一個結合轉換為另一個元素的集合。
void main() {
final List<int> alist = [1, 2, 3];
// 2.6. map()轉換集合元素
final alistPlus3 = alist.map((e) => 3 + e);
print('2.6. map()轉換集合元素: $alist -> $alistPlus3');
// 結果:2.6. map()轉換集合元素: [1, 2, 3] -> (4, 5, 6)
final intToStringList = alist.map((e) => 'value:$e');
print('2.6. map()轉換集合元素: $alist -> $intToStringList');
// 結果:2.6. map()轉換集合元素: [1, 2, 3] -> (value:1, value:2, value:3)
}
最後總結
特別註意:
- first/last/firstWhere()這些返回值為單個元素方法,當為空集合或者無法找到元素時,會拋出異常。
- where()/takeWhile()/skipWhile()這些返回值為集合的方法,當為空集合或者無法匹配到元素,返回空集合,不會拋出異常。
- any()預設返回值為false,every()預設返回值為true
我的本博客原地址:https://ntopic.cn/p/2023092701/
本文作者:奔跑的蝸牛,轉載請註明原文鏈接:https://ntopic.cn