Skip to content

Commit f4685a5

Browse files
committed
algorithms: Add setUnion, operating on sets implemented as sorted lists
Transcribed from the same-named function in zulip-mobile: see src/immutableUtils.js.
1 parent 099728e commit f4685a5

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

lib/model/algorithms.dart

+59
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11

2+
import 'package:collection/collection.dart';
3+
24
/// Returns the index in [sortedList] of an element matching the given [key],
35
/// if there is one.
46
///
@@ -41,3 +43,60 @@ int binarySearchByKey<E, K>(
4143
}
4244
return -1;
4345
}
46+
47+
/// The union of sets, represented as sorted lists.
48+
///
49+
/// The inputs must be sorted (by `<`) and without duplicates (by `==`).
50+
///
51+
/// The output will contain all the elements found in either input, again
52+
/// sorted and without duplicates.
53+
// When implementing this, it was convenient to have it return a [QueueList].
54+
// We can make it more general if needed:
55+
// https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/flutter.3A.20unreads.20model/near/1647754
56+
QueueList<int> setUnion(Iterable<int> xs, Iterable<int> ys) {
57+
// This will overshoot by the number of elements that occur in both lists.
58+
// That may make this optimization less effective, but it will not cause
59+
// incorrectness.
60+
final capacity = xs is List && ys is List // [List]s should have efficient `.length`
61+
? xs.length + ys.length
62+
: null;
63+
final result = QueueList<int>(capacity);
64+
65+
final iterX = xs.iterator;
66+
final iterY = ys.iterator;
67+
late bool xHasElement;
68+
void moveX() => xHasElement = iterX.moveNext();
69+
late bool yHasElement;
70+
void moveY() => yHasElement = iterY.moveNext();
71+
72+
moveX();
73+
moveY();
74+
while (true) {
75+
if (!xHasElement || !yHasElement) {
76+
break;
77+
}
78+
79+
int x = iterX.current;
80+
int y = iterY.current;
81+
if (x < y) {
82+
result.add(x);
83+
moveX();
84+
} else if (x != y) {
85+
result.add(y);
86+
moveY();
87+
} else { // x == y
88+
result.add(x);
89+
moveX();
90+
moveY();
91+
}
92+
}
93+
while (xHasElement) {
94+
result.add(iterX.current);
95+
moveX();
96+
}
97+
while (yHasElement) {
98+
result.add(iterY.current);
99+
moveY();
100+
}
101+
return result;
102+
}

test/model/algorithms_test.dart

+19
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,23 @@ void main() {
3636
check(search(7)).equals(-1);
3737
});
3838
});
39+
40+
group('setUnion', () {
41+
for (final (String desc, Iterable<int> xs, Iterable<int> ys) in [
42+
('empty', [], []),
43+
('nonempty, empty', [1, 2], []),
44+
('empty, nonempty', [], [1, 2]),
45+
('in order', [1, 2], [3, 4]),
46+
('reversed', [3, 4], [1, 2]),
47+
('interleaved', [1, 3], [2, 4]),
48+
('all dupes', [1, 2], [1, 2]),
49+
('some dupes', [1, 2], [2, 3]),
50+
('comparison is numeric, not lexicographic', [11], [2]),
51+
]) {
52+
test(desc, () {
53+
final expected = Set.of(xs.followedBy(ys)).toList()..sort();
54+
check(setUnion(xs, ys)).deepEquals(expected);
55+
});
56+
}
57+
});
3958
}

0 commit comments

Comments
 (0)