extension IterableExt on Iterable { Iterable separated(E separator) sync* { final iterator = this.iterator; if (!iterator.moveNext()) return; yield iterator.current; while (iterator.moveNext()) { yield separator; yield iterator.current; } } Iterable> chunks(int size) sync* { if (length == 0) return; final iterator = this.iterator; while (iterator.moveNext()) { final chunk = [iterator.current]; for (var i = 1; i < size && iterator.moveNext(); i++) { chunk.add(iterator.current); } yield chunk; } } Iterable fill(int length, {required E Function(int count) filler}) sync* { int count = 0; for (final item in this) { yield item; count++; if (count >= length) return; } while (count < length) { yield filler(count); count++; } } Iterable takeLast({int count = 50}) { if (count <= 0) return Iterable.empty(); return count >= length ? this : toList().skip(length - count); } } extension ListExt on List { void truncate(int maxLength) { if (maxLength == 0) { return; } if (length > maxLength) { removeRange(0, length - maxLength); } } List intersection(List list) { return where((item) => list.contains(item)).toList(); } List> batch(int maxConcurrent) { final batches = (length / maxConcurrent).ceil(); final List> res = []; for (int i = 0; i < batches; i++) { if (i != batches - 1) { res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1))); } else { res.add(sublist(i * maxConcurrent, length)); } } return res; } List copyAndPut(T data, bool Function(T element) test) { final newList = List.from(this); final index = newList.indexWhere(test); if (index != -1) { newList[index] = data; } else { newList.insert(0, data); } return newList; } List copyAndReorder(int oldIndex, int newIndex) { final newList = List.from(this); final item = newList.removeAt(oldIndex); newList.insert(newIndex, item); return newList; } List safeSublist(int start, [int? end]) { if (start <= 0) return this; if (start > length) return []; if (end != null) { return sublist(start, end.clamp(start, length)); } return sublist(start); } T? safeGet(int index, {T? defaultValue}) { if (index < 0 || index >= length) { return defaultValue; } return this[index]; } T safeLast(T defaultValue) { if (isNotEmpty) { return last; } return defaultValue; } void addOrRemove(T value) { if (contains(value)) { remove(value); } else { add(value); } } } extension SetExt on Set { void addOrRemove(T value) { if (contains(value)) { remove(value); } else { add(value); } } } extension DoubleListExt on List { int findInterval(num target) { if (isEmpty) return -1; if (target < first) return -1; if (target >= last) return length - 1; int left = 0; int right = length - 1; while (left <= right) { final int mid = left + (right - left) ~/ 2; if (mid == length - 1 || (this[mid] <= target && target < this[mid + 1])) { return mid; } else if (target < this[mid]) { right = mid - 1; } else { left = mid + 1; } } return -1; } } extension MapExt on Map { V updateCacheValue(K key, V Function() callback) { if (this[key] == null) { this[key] = callback(); } return this[key]!; } Map copyWitUpdate(K key, V? value) { final newMap = Map.from(this); if (value == null) { newMap.remove(key); } else { newMap[key] = value; } return newMap; } }