String s = 'en_US';
s.split('_').last;
当不明确目标对象的类型时,使用:
var data = Map<String, dynamic>.from(info); //实际创建了一个新对象
当明确目标对象的类型时,使用:
var data = Map<String, dynamic>.of(info); //实际还是一个对象
举例按年聚类,借助package:collection算法扩展:
List<int> days = [202201, 202202, 202301, 202304, 202305];
Map<int, List<int>> groupRes = groupBy<int, int>(days, (d) => d ~/ 100);
除了通过groupBy函数,实际上该库对原生类型做了强大的扩展,为List扩展了groupListsBy、groupSetsBy、groupFoldBy三个分组聚类函数。前两个比较好理解,跟上面的例子一样,只是一个返回Map<K, List<T>>,一个返回Map<K, Set<T>>:
days.groupListsBy((d) => d ~/ 100)
而groupFoldBy则可以实现自定义的聚类逻辑,其原型为:
(K Function(T) keyOf, G Function(G?, T) combine) → Map<K, G>
当第一次调用combine时,第一个参数为null,当第二次调用combine时,第一个参数就是上次调用的返回值。由此可以实现任何自己想定制的容器,或者特殊逻辑,比如聚类结果要求排好序,那么就可以结合插入排序,而不需要聚类后再遍历排序。
举例用groupFoldBy来实现groupSetsBy逻辑就是:
iterable.groupFoldBy(keyOf, (Set<T>? previous, T element) => (previous ?? <T>{})..add(element));
假如我们要将数组转Map,元素只保留最新一个,那么可以写成:
Map<int, int> m = days.groupFoldBy((d) => d ~/ 100, (_, v) => v));
假如我们要求聚类结果是求和,那么可以写成:
Map<int, int> m = days.groupFoldBy((d) => d ~/ 100, (int? p, int v) => (p ?? 0) + v);
任何语言都存在这样的问题,即复杂容器如何比较是否相等的问题,dart默认的==只有当两个变量其实是一个容器时,才为true。一种不太安全的做法就是将对象json序列化后再比较字符串,但Map里的元素是没有次序保证,尽管多数情况下没问题,但语言层面并没有予以保证,除非json库可以指定sort key参数。
collection库提供了各种复杂容器其具体值是否相等的实现,如MapEquality、ListEquality、SetEquality等等。List就是每个元素是否相等,Map就是每个key和val是否相等,举例如下:
var info = {'day': 202201, 'name': '张三', 'age': 20};
var info2 = {'day': 202201, 'name': '张三', 'age': 20};
print(info == info2); // false
print(MapEquality().equals(info, info2)); // true
上面的例子key和value都是简单类型,无递归比较要求,默认比较函数就是使用==,而如果value也是一个对象,就不能这样了,需要指定value的比较函数:
var info = {'day': 202201, 'name': '张三', 'age': 20, 'school': {'name': 'hust'}};
var info2 = {'day': 202201, 'name': '张三', 'age': 20, 'school': {'name': 'hust'}};
print(MapEquality().equals(info, info2)); // false
print(MapEquality(values: DeepCollectionEquality()).equals(info, info2)); //true
collection对List扩展了各种二分查找函数:
List.binarySearch
List.binarySearchBy
List.binarySearchByCompare
List.lowerBound
List.lowerBoundBy
List.lowerBoundByCompare
days.shuffle();
有两种写法:
List datas = [
{'id': 1, 'day': 202201, 'name': '张三', 'age': 20},
{'id': 2, 'day': 202201, 'name': '张三', 'age': 20},
{'id': 3, 'day': 202201, 'name': '张三', 'age': 20},
];
print(Map.fromEntries(datas.map((ele) => MapEntry(ele['id'], ele))));
print(datas.groupFoldBy<int, Map>((ele) => ele['id'], (_, v) => v));
使用BoolList 来代替List<bool>可以节省内存空间:
var bl = BoolList(10, fill: true, growable: true);
print(bl);
var arr = [1, 2, 3, 4];
print(arr.sum);
print(arr.average);
print(arr.max);
print(arr.min);
如下面的例子,当使用get返回一个只读属性时,只能确保本对象的该属性不被修改,但如果该属性就是一个复杂对象,并不能确保该属性对象被修改,此时可以使用collection提供的丰富的UnmodifiableXXView系列类型:
class A {
static Map<String, String> _info = {'name': 'zhangsan'};
//static Map<String, String> get info => _info;
static UnmodifiableMapView<String, String> get info => UnmodifiableMapView(_info);
}
int main() {
A.info['name'] = 'lisi';
print(A.info);
return 0;
}
上面的例子会抛出异常:Unsupported operation: Cannot modify unmodifiable map。
有常规的ceil() floor() round()之类的函数,但有些有用的函数可能会被忽略,比如reminder()是取余数,否则你可能需要写成(正数情况下):
val - (val ~/ other) * other
truncate()是取整数部分,这在正数时跟floor()是一样的,但是在负数时就跟ceil()一样了,如果要自己实现,你可能需要写成:
val > 0 ? int(val) : -int(val)
还有非常有用的clamp()函数,简直是代码简化利器,英文意思是夹子,就是将一个数限制在某个范围内,val.clamp(a, b)等同于:
if (val < a) {
val = a;
} else if (val > b) {
val = b;
}