Skip to content

Commit 57adf31

Browse files
committed
Add separated, separatedList and separate to iterables and lists.
Each new function separates the elements of an iterable or list with a separator value of the same type. They allow optionally adding the separator before a first element and/or after a last element. * `separated`: Returns a (lazy) iterable backed by the original elements. * `separatedList`: Creates an (eager) list with the same elements that separated would have returned. (But more efficiently.) (Also has a version specialized for a `List` input.) * `separate`: Modifies a list in-place to add separators around the existing elements.
1 parent 018e1dc commit 57adf31

File tree

4 files changed

+478
-0
lines changed

4 files changed

+478
-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: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,102 @@ extension IterableExtension<T> on Iterable<T> {
5656
return chosen;
5757
}
5858

59+
/// The elements of this iterable separated by [separator].
60+
///
61+
/// Emits the same elements as this iterable, and also emits
62+
/// a [separator] between any two of those elements.
63+
///
64+
/// If [before] is set to `true`, a [separator] is also
65+
/// emitted before the first element.
66+
/// If [after] is set to `true`, a [separator] is also
67+
/// emitted after the last element.
68+
///
69+
/// If this iterable is empty, [before] and [after] have no effect.
70+
///
71+
/// Example:
72+
/// ```dart
73+
/// print([1, 2, 3].separated(-1)); // (1, -1, 2, -1, 3)
74+
/// print([1].separated(-1)); // (1)
75+
/// print([].separated(-1)); // ()
76+
///
77+
/// print([1, 2, 3].separated(
78+
/// -1
79+
/// before: true,
80+
/// )); // (-1, 1, -1, 2, -1, 3)
81+
///
82+
/// print([1].separated(
83+
/// -1
84+
/// before: true,
85+
/// after: true,
86+
/// )); // (-1, 1, -1)
87+
///
88+
/// print([].separated(
89+
/// -1
90+
/// before: true,
91+
/// after: true,
92+
/// )); // ()
93+
/// ```
94+
Iterable<T> separated(T separator,
95+
{bool before = false, bool after = false}) sync* {
96+
const emitBefore = 1;
97+
const emitAfter = 2;
98+
var state = before ? emitBefore : 0;
99+
final afterState = emitBefore | (after ? emitAfter : 0);
100+
for (var element in this) {
101+
if (state & emitBefore != 0) yield separator;
102+
state = afterState;
103+
yield element;
104+
}
105+
if (state & emitAfter != 0) yield separator;
106+
}
107+
108+
/// Creates new list with the elements of this list separated by [separator].
109+
///
110+
/// Returns a new list which contains the same elements as this list,
111+
/// with a [separator] between any two of those elements.
112+
///
113+
/// If [before] is set to `true`, a [separator] is also
114+
/// added before the first element.
115+
/// If [after] is set to `true`, a [separator] is also
116+
/// added after the last element.
117+
///
118+
/// If this list is empty, [before] and [after] have no effect.
119+
///
120+
/// Example:
121+
/// ```dart
122+
/// print([1, 2, 3].separatedList(-1)); // [1, -1, 2, -1, 3]
123+
/// print([1].separatedList(-1)); // [1]
124+
/// print([].separatedList(-1)); // []
125+
///
126+
/// print([1, 2, 3].separatedList(
127+
/// -1
128+
/// before: true,
129+
/// )); // [-1, 1, -1, 2, -1, 3]
130+
///
131+
/// print([1].separatedList(
132+
/// -1
133+
/// before: true,
134+
/// after: true,
135+
/// )); // [-1, 1, -1]
136+
///
137+
/// print([].separatedList(
138+
/// -1
139+
/// before: true,
140+
/// after: true,
141+
/// )); // []
142+
/// ```
143+
List<T> separatedList(T separator,
144+
{bool before = false, bool after = false}) {
145+
var hasElement = false;
146+
return [
147+
for (var element in this) ...[
148+
if (hasElement || (hasElement = true) && before) separator,
149+
element,
150+
],
151+
if (hasElement && after) separator
152+
];
153+
}
154+
59155
/// The elements that do not satisfy [test].
60156
Iterable<T> whereNot(bool Function(T element) test) =>
61157
where((element) => !test(element));

pkgs/collection/lib/src/list_extensions.dart

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,114 @@ extension ListExtensions<E> on List<E> {
327327
yield slice(i, min(i + length, this.length));
328328
}
329329
}
330+
331+
/// Creates new list with the elements of this list separated by [separator].
332+
///
333+
/// Returns a new list which contains the same elements as this list,
334+
/// with a [separator] between any two of those elements.
335+
///
336+
/// If [before] is set to `true`, a [separator] is also
337+
/// added before the first element.
338+
/// If [after] is set to `true`, a [separator] is also
339+
/// added after the last element.
340+
///
341+
/// If this list is empty, [before] and [after] have no effect.
342+
///
343+
/// Example:
344+
/// ```dart
345+
/// print([1, 2, 3].separatedList(-1)); // [1, -1, 2, -1, 3]
346+
/// print([1].separatedList(-1)); // [1]
347+
/// print([].separatedList(-1)); // []
348+
///
349+
/// print([1, 2, 3].separatedList(
350+
/// -1
351+
/// before: true,
352+
/// )); // [-1, 1, -1, 2, -1, 3]
353+
///
354+
/// print([1].separatedList(
355+
/// -1
356+
/// before: true,
357+
/// after: true,
358+
/// )); // [-1, 1, -1]
359+
///
360+
/// print([].separatedList(
361+
/// -1
362+
/// before: true,
363+
/// after: true,
364+
/// )); // []
365+
/// ```
366+
List<E> separatedList(E separator,
367+
{bool before = false, bool after = false}) =>
368+
isEmpty
369+
? []
370+
: [
371+
if (!before) this[0],
372+
for (var i = before ? 0 : 1; i < length; i++) ...[
373+
separator,
374+
this[i],
375+
],
376+
if (after) separator
377+
];
378+
379+
/// Inserts [separator] between elements of this list.
380+
///
381+
/// Afterwards, the list will contains all the original elements,
382+
/// with a [separator] between any two of those elements.
383+
///
384+
/// If [before] is set to `true`, a [separator] is also
385+
/// inserted before the first element.
386+
/// If [after] is set to `true`, a [separator] is also
387+
/// added after the last element.
388+
///
389+
/// If this list is empty, [before] and [after] have no effect.
390+
///
391+
/// Example:
392+
/// ```dart
393+
/// print([1, 2, 3]..insertSeparator(-1)); // [1, -1, 2, -1, 3]
394+
/// print([1]..insertSeparator(-1)); // [1]
395+
/// print([]..insertSeparator(-1)); // []
396+
///
397+
/// print([1, 2, 3]..insertSeparator(
398+
/// -1
399+
/// before: true,
400+
/// )); // [-1, 1, -1, 2, -1, 3]
401+
///
402+
/// print([1]..insertSeparator(
403+
/// -1
404+
/// before: true,
405+
/// after: true,
406+
/// )); // [-1, 1, -1]
407+
///
408+
/// print([]..insertSeparator(
409+
/// -1
410+
/// before: true,
411+
/// after: true,
412+
/// )); // []
413+
/// ```
414+
void separate(E separator, {bool before = false, bool after = false}) {
415+
var length = this.length;
416+
if (length == 0) return;
417+
var newLength = length * 2 - 1;
418+
var offset = 0;
419+
if (before) {
420+
newLength++;
421+
offset = 1;
422+
}
423+
if (after) newLength++;
424+
E newElementAt(int index) {
425+
index -= offset;
426+
if (index.isOdd) return separator;
427+
return this[index >> 1];
428+
}
429+
430+
for (var i = length; i < newLength; i++) {
431+
add(newElementAt(i));
432+
}
433+
for (var i = length, firstChanged = offset ^ 1; i > firstChanged;) {
434+
--i;
435+
this[i] = newElementAt(i);
436+
}
437+
}
330438
}
331439

332440
/// Various extensions on lists of comparable elements.

0 commit comments

Comments
 (0)