Skip to content

Commit 33b5232

Browse files
authored
Add separated, separatedList and separate to iterables and lists. (#919)
1 parent 20ed966 commit 33b5232

File tree

4 files changed

+1002
-0
lines changed

4 files changed

+1002
-0
lines changed

pkgs/collection/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## 1.20.0-wip
22

3+
- Adds `separated` and `separatedList` extension methods to `Iterable`.
4+
- Adds `separate` extension method to `List`
35
- Add `IterableMapEntryExtension` for working on `Map` as a list of pairs, using
46
`Map.entries`.
57
- Explicitly mark `BoolList` as `abstract interface`

pkgs/collection/lib/src/iterable_extensions.dart

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:math' show Random;
66

7+
import '../collection.dart' show DelegatingIterable;
78
import 'algorithms.dart';
89
import 'functions.dart' as functions;
910
import 'utils.dart';
@@ -56,6 +57,99 @@ extension IterableExtension<T> on Iterable<T> {
5657
return chosen;
5758
}
5859

60+
/// The elements of this iterable separated by [separator].
61+
///
62+
/// Emits the same elements as this iterable, and also emits
63+
/// a [separator] between any two of those elements.
64+
///
65+
/// If [before] is set to `true`, a [separator] is also
66+
/// emitted before the first element.
67+
/// If [after] is set to `true`, a [separator] is also
68+
/// emitted after the last element.
69+
///
70+
/// If this iterable is empty, [before] and [after] have no effect.
71+
///
72+
/// Example:
73+
/// ```dart
74+
/// print(([1, 2, 3] as Iterable<int>).separated(-1)); // (1, -1, 2, -1, 3)
75+
/// print(([1] as Iterable<int>).separated(-1)); // (1)
76+
/// print(([] as Iterable<int>).separated(-1)); // ()
77+
///
78+
/// print(([1, 2, 3] as Iterable<int>).separated(
79+
/// -1,
80+
/// before: true,
81+
/// )); // (-1, 1, -1, 2, -1, 3)
82+
///
83+
/// print(([1] as Iterable<int>).separated(
84+
/// -1,
85+
/// before: true,
86+
/// after: true,
87+
/// )); // (-1, 1, -1)
88+
///
89+
/// print(([] as Iterable<int>).separated(
90+
/// -1,
91+
/// before: true,
92+
/// after: true,
93+
/// )); // ()
94+
/// ```
95+
Iterable<T> separated(T separator,
96+
{bool before = false, bool after = false}) =>
97+
_SeparatedIterable<T>(this, separator, before, after);
98+
99+
/// Creates list with the elements of this iterable separated by [separator].
100+
///
101+
/// Returns a new list which contains the same elements as this iterable,
102+
/// with a [separator] between any two of those elements.
103+
///
104+
/// If [before] is set to `true`, a [separator] is also
105+
/// added before the first element.
106+
/// If [after] is set to `true`, a [separator] is also
107+
/// added after the last element.
108+
///
109+
/// If this iterable is empty, [before] and [after] have no effect.
110+
///
111+
/// Example:
112+
/// ```dart
113+
/// print([1, 2, 3].separatedList(-1)); // [1, -1, 2, -1, 3]
114+
/// print([1].separatedList(-1)); // [1]
115+
/// print([].separatedList(-1)); // []
116+
///
117+
/// print([1, 2, 3].separatedList(
118+
/// -1,
119+
/// before: true,
120+
/// )); // [-1, 1, -1, 2, -1, 3]
121+
///
122+
/// print([1].separatedList(
123+
/// -1,
124+
/// before: true,
125+
/// after: true,
126+
/// )); // [-1, 1, -1]
127+
///
128+
/// print([].separatedList(
129+
/// -1,
130+
/// before: true,
131+
/// after: true,
132+
/// )); // []
133+
/// ```
134+
List<T> separatedList(T separator,
135+
{bool before = false, bool after = false}) {
136+
var result = <T>[];
137+
var iterator = this.iterator;
138+
if (iterator.moveNext()) {
139+
if (before) result.add(separator);
140+
while (true) {
141+
result.add(iterator.current);
142+
if (iterator.moveNext()) {
143+
result.add(separator);
144+
} else {
145+
break;
146+
}
147+
}
148+
if (after) result.add(separator);
149+
}
150+
return result;
151+
}
152+
59153
/// The elements that do not satisfy [test].
60154
Iterable<T> whereNot(bool Function(T element) test) =>
61155
where((element) => !test(element));
@@ -1056,3 +1150,206 @@ extension ComparatorExtension<T> on Comparator<T> {
10561150
return result;
10571151
};
10581152
}
1153+
1154+
/// Implementation of [IterableExtension.separated].
1155+
///
1156+
/// Optimizes direct accesses.
1157+
class _SeparatedIterable<T> extends Iterable<T> {
1158+
final T _separator;
1159+
final Iterable<T> _elements;
1160+
1161+
static const int _afterFlag = 1 << 0;
1162+
static const int _beforeFlag = 1 << 1;
1163+
1164+
/// Two bit-flags, for whether the `before` and `after` arguments were `true`.
1165+
final int _flags;
1166+
1167+
_SeparatedIterable(this._elements, this._separator, bool before, bool after)
1168+
: _flags = (before ? _beforeFlag : 0) + (after ? _afterFlag : 0);
1169+
1170+
@override
1171+
bool get isEmpty => _elements.isEmpty;
1172+
@override
1173+
bool get isNotEmpty => _elements.isNotEmpty;
1174+
@override
1175+
int get length {
1176+
var length = _elements.length;
1177+
if (length != 0) {
1178+
length = length * 2 - 1 + (_flags & 1) + (_flags >> 1);
1179+
}
1180+
return length;
1181+
}
1182+
1183+
@override
1184+
T elementAt(int index) {
1185+
RangeError.checkNotNegative(index, 'index');
1186+
// Figure out which element must exist in [_elements] for this index
1187+
// to exist in the separated output.
1188+
var indexWithoutBefore = index - (_flags >> 1);
1189+
var elementIndex = indexWithoutBefore ~/ 2; // Rounds both -1 and 1 to 0.
1190+
if (indexWithoutBefore.isEven) {
1191+
// It's an element.
1192+
return _elements.elementAt(elementIndex);
1193+
}
1194+
// It's a separator after that element (or before the first element).
1195+
// Check if that element exists, unless the `_afterFlag` is set,
1196+
// in which case to check if the next element exists by adding 1
1197+
// to elementIndex.
1198+
// (But if `index` is zero, it's the before separator, so it should
1199+
// check that a first element exists.)
1200+
if (index != 0) {
1201+
assert(_afterFlag == 1);
1202+
elementIndex += (_flags ^ _afterFlag) & _afterFlag;
1203+
}
1204+
_elements.elementAt(elementIndex); // If throws, not an element.
1205+
return _separator;
1206+
}
1207+
1208+
@override
1209+
T get first {
1210+
if (_flags & _beforeFlag == 0) return _elements.first;
1211+
if (_elements.isNotEmpty) return _separator;
1212+
throw StateError('No element');
1213+
}
1214+
1215+
@override
1216+
T get last {
1217+
if (_flags & _afterFlag == 0) return _elements.last;
1218+
if (_elements.isNotEmpty) return _separator;
1219+
throw StateError('No element');
1220+
}
1221+
1222+
@override
1223+
Iterable<T> take(int count) {
1224+
if (count == 0) return Iterable<T>.empty();
1225+
var beforeCount = _flags >> 1;
1226+
if (count == 1) {
1227+
if (beforeCount == 0) {
1228+
return _elements.take(1);
1229+
}
1230+
// return Iterable<T>.value(_separator); // Why you no exist?!
1231+
return DelegatingIterable<T>([_separator]);
1232+
}
1233+
var countWithoutBefore = count - beforeCount;
1234+
var elementCount = (countWithoutBefore + 1) >> 1;
1235+
return _SeparatedIterable<T>(
1236+
_elements.take(elementCount),
1237+
_separator,
1238+
beforeCount != 0,
1239+
countWithoutBefore.isEven,
1240+
);
1241+
}
1242+
1243+
@override
1244+
Iterable<T> skip(int count) {
1245+
if (count == 0) return this;
1246+
var beforeCount = _flags >> 1;
1247+
var countWithoutBefore = count - beforeCount;
1248+
var hasAfter = _flags & _afterFlag != 0;
1249+
if (countWithoutBefore.isOdd && hasAfter) {
1250+
// Remainder could be just the final separator, which cannot
1251+
// be created by a `_SeparatedIterable`.
1252+
// (Unlike `take`, cannot see that without iterating.)
1253+
return super.skip(count);
1254+
}
1255+
// Starts or ends on an element, not a separator,
1256+
// so remainder cannot be a single separator.
1257+
var elementCount = (countWithoutBefore + 1) >> 1;
1258+
return _SeparatedIterable<T>(
1259+
elementCount == 0 ? _elements : _elements.skip(elementCount),
1260+
_separator,
1261+
countWithoutBefore.isOdd,
1262+
hasAfter,
1263+
);
1264+
}
1265+
1266+
@override
1267+
Iterator<T> get iterator =>
1268+
_SeparatedIterator<T>(_elements.iterator, _separator, _flags);
1269+
}
1270+
1271+
/// Iterator for [_SeparatedIterable].
1272+
class _SeparatedIterator<T> implements Iterator<T> {
1273+
final T _separator;
1274+
final Iterator<T> _elements;
1275+
1276+
// Flags set in [_state].
1277+
1278+
/// Set if adding a separator after the last element.
1279+
///
1280+
/// State never changes, just storing a boolean as a bit.
1281+
static const _noAddAfterFlag = 1 << 0;
1282+
1283+
// Set when the next element to emit is a separator.
1284+
//
1285+
// Otherwise the element to emit is [_elements.current].
1286+
static const _separatorFlag = 1 << 1;
1287+
1288+
// Set when next step should check if there is a next element.
1289+
//
1290+
// If there is no next element, iteration ends.
1291+
static const _ifHasNextFlag = 1 << 2;
1292+
1293+
/// Current state.
1294+
///
1295+
/// A combination of the [_noAddAfterFlag], [_separatorFlag]
1296+
/// and [_ifHasNextFlag].
1297+
///
1298+
/// Transitions:
1299+
/// * If `_ifHasNextFlag`:
1300+
/// - if `!_elements.moveNext()`, then end.
1301+
/// (No state change, next call will do the same).
1302+
/// - otherwise continue.
1303+
/// * If `_separatorFlag`:
1304+
/// - emit `_separator`,
1305+
/// - clear `_separatorFlag` (next is an element),
1306+
/// - toggle `_ifHasNextFlag`.
1307+
/// * else:
1308+
/// - emit `_elements.current`,
1309+
/// - set `_separatorFlag` (next will be a separator),
1310+
/// - set `_ifHasNextFlag` if `_noAddAfterFlag` is set.
1311+
///
1312+
/// Starts with `ifHasNextFlag` set,
1313+
/// with `_separatorFlag` set if the `before` parameter of the iterable
1314+
/// was `true`, and with `noAddAfterFlag` set if the `after` parameter
1315+
/// of the iterable was `false`.
1316+
int _state;
1317+
1318+
T? _current;
1319+
1320+
_SeparatedIterator(this._elements, this._separator, int flags)
1321+
: assert(_noAddAfterFlag == _SeparatedIterable._afterFlag),
1322+
assert(_separatorFlag == _SeparatedIterable._beforeFlag),
1323+
// `_separatorFlag` set if `_beforeFlag` was set.
1324+
// `_noAddAfterFlag` set if `_afterFlag` was not.
1325+
// `_ifHasNextFlag` always set at the start.
1326+
_state = (flags ^ _noAddAfterFlag) | _ifHasNextFlag;
1327+
1328+
@override
1329+
T get current => _current as T;
1330+
1331+
@override
1332+
bool moveNext() {
1333+
var state = _state;
1334+
if (state & _ifHasNextFlag == 0 || _elements.moveNext()) {
1335+
if (state & _separatorFlag != 0) {
1336+
_current = _separator;
1337+
// Next is not separator.
1338+
// Check if there is a next if this call didn't.
1339+
state ^= _separatorFlag | _ifHasNextFlag;
1340+
} else {
1341+
_current = _elements.current;
1342+
// Next is separator.
1343+
// Check if there is a next if not adding separator after last element.
1344+
state = (state & _noAddAfterFlag) * (_noAddAfterFlag | _ifHasNextFlag) +
1345+
_separatorFlag;
1346+
}
1347+
_state = state;
1348+
return true;
1349+
}
1350+
// Next call will check `_elements.moveNext()` again.
1351+
assert(state & _ifHasNextFlag != 0);
1352+
_current = null;
1353+
return false;
1354+
}
1355+
}

0 commit comments

Comments
 (0)