Skip to content

Commit 1fde1da

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 5c34d2c commit 1fde1da

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

lib/model/algorithms.dart

Lines changed: 59 additions & 0 deletions
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
///
@@ -57,3 +59,60 @@ bool isSortedWithoutDuplicates(List<int> items) {
5759
}
5860
return true;
5961
}
62+
63+
/// The union of sets, represented as sorted lists.
64+
///
65+
/// The inputs must be sorted (by `<`) and without duplicates (by `==`).
66+
///
67+
/// The output will contain all the elements found in either input, again
68+
/// sorted and without duplicates.
69+
// When implementing this, it was convenient to have it return a [QueueList].
70+
// We can make it more general if needed:
71+
// https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/flutter.3A.20unreads.20model/near/1647754
72+
QueueList<int> setUnion(Iterable<int> xs, Iterable<int> ys) {
73+
// This will overshoot by the number of elements that occur in both lists.
74+
// That may make this optimization less effective, but it will not cause
75+
// incorrectness.
76+
final capacity = xs is List && ys is List // [List]s should have efficient `.length`
77+
? xs.length + ys.length
78+
: null;
79+
final result = QueueList<int>(capacity);
80+
81+
final iterX = xs.iterator;
82+
final iterY = ys.iterator;
83+
late bool xHasElement;
84+
void moveX() => xHasElement = iterX.moveNext();
85+
late bool yHasElement;
86+
void moveY() => yHasElement = iterY.moveNext();
87+
88+
moveX();
89+
moveY();
90+
while (true) {
91+
if (!xHasElement || !yHasElement) {
92+
break;
93+
}
94+
95+
int x = iterX.current;
96+
int y = iterY.current;
97+
if (x < y) {
98+
result.add(x);
99+
moveX();
100+
} else if (x != y) {
101+
result.add(y);
102+
moveY();
103+
} else { // x == y
104+
result.add(x);
105+
moveX();
106+
moveY();
107+
}
108+
}
109+
while (xHasElement) {
110+
result.add(iterX.current);
111+
moveX();
112+
}
113+
while (yHasElement) {
114+
result.add(iterY.current);
115+
moveY();
116+
}
117+
return result;
118+
}

test/model/algorithms_test.dart

Lines changed: 19 additions & 0 deletions
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)