dart语言技巧

获取下划线分隔字符串后面的一部分


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();

对象列表转Map

有两种写法:


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));

Bool列表空间优化

使用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。

double/num一些技巧函数

有常规的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;
}

 

发表于 2023年04月02日 22:17   修改于 2023年05月07日 23:28   评论:0   阅读:395  



回到顶部

首页 | 关于我 | 关于本站 | 站内留言 | rss
python logo   django logo   tornado logo