Won't pre-sorting the positions reduce the number of comparisons that must be made during unsubsumption?
YES! Pre-sorting positions by (errors, offset) enables significant optimizations through early termination.
Before: HashSet<UniversalPosition> (unordered)
After: BTreeSet<UniversalPosition> (sorted by custom Ord)
Positions are sorted by (errors, offset):
impl<V: PositionVariant> Ord for UniversalPosition<V> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// Sort by (errors, offset) to enable early termination
match (self, other) {
(INonFinal { errors: e1, offset: o1, .. }, INonFinal { errors: e2, offset: o2, .. }) => {
match e1.cmp(e2) {
Ordering::Equal => o1.cmp(o2), // Secondary sort by offset
other => other, // Primary sort by errors
}
}
// ... similar for MFinal
}
}
}
Position i#e subsumes j#f if:
f > e (subsumed position has MORE errors)|j - i| \le f - e$ (offset distance is within error difference)When adding position pos with errors=e, only positions with errors > e can be subsumed.
Before (HashSet):
self.positions.retain(|p| !subsumes(&pos, p, self.max_distance));
\mathcal{O}(n)$ - checks ALL n positionsAfter (BTreeSet with early termination):
self.positions.retain(|p| {
if p.errors() <= pos_errors {
true // Keep - cannot be subsumed (early termination)
} else {
!subsumes(&pos, p, self.max_distance) // Check full condition
}
});
\mathcal{O}(k)$ where k = positions with errors > e\le e$When checking if pos is subsumed, only positions with errors < e can subsume it.
Before (HashSet):
if !self.positions.iter().any(|p| subsumes(p, &pos, self.max_distance)) {
self.positions.insert(pos);
}
\mathcal{O}(n)$ - checks ALL n positionsAfter (BTreeSet with early termination):
let is_subsumed = self.positions.iter()
.take_while(|p| p.errors() < pos_errors) // Early termination!
.any(|p| subsumes(p, &pos, self.max_distance));
\mathcal{O}(k)$ where k = positions with errors < e\ge e$From the thesis, states have $\mathcal{O}(n^{2})$ positions maximum, where n is max_distance.
\le 1$ position\le 4$ positions\le 9$ positionsPositions tend to have sparse error distributions:
Example state after processing "test" → "text":
{I+0#1, I-1#2, I-2#2} (random order in HashSet)[I+0#1, I-1#2, I-2#2] (ascending by errors in BTreeSet)| Operation | HashSet (unordered) | BTreeSet (sorted) |
|---|---|---|
| Check Step 1 | $\mathcal{O}(n)$ | $\mathcal{O}(k_{1})$ where k₁ = positions with errors > e |
| Check Step 2 | $\mathcal{O}(n)$ | $\mathcal{O}(k_{2})$ where k₂ = positions with errors < e |
| Insert | $\mathcal{O}(1)$ expected | $\mathcal{O}(\log n)$ |
| Total per add_position | $\mathcal{O}(n)$ | $\mathcal{O}(k_{1} + k_{2} + \log n)$ |
Key insight: k₁ + k₂ << n in practice due to sparse error distribution!
For typical states with mixed error levels:
\mathcal{O}(\log n)$ insertFor best case (inserting position with minimum errors):
\mathcal{O}(\log n)$ insertPros:
Cons:
\mathcal{O}(\log n)$ instead of $\mathcal{O}(1)$Verdict: Benefits outweigh costs for typical small state sizes $(n \le 9$ positions).
All tests pass with optimization:
The optimization is semantically transparent:
Yes, pre-sorting positions by (errors, offset) significantly reduces subsumption comparisons through early termination:
\mathcal{O}(n)$ to $\mathcal{O}(k)$ where k << n\mathcal{O}(\log n)$ insert)The optimization is a clear win for typical workloads with small, sparse error distributions.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |