Skip to content

Commit 64ec3bb

Browse files
committed
add generic lazy_segment_tree
1 parent 83c6a3c commit 64ec3bb

File tree

5 files changed

+360
-0
lines changed

5 files changed

+360
-0
lines changed

lazy_segment_tree/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Lazy Segment Tree
2+
3+
A segment tree implementation that supports range updates with lazy propagation.
4+
5+
The tree is built from an initial array and accepts user-defined merge, apply,
6+
and compose functions. This makes it flexible enough to model scenarios such as
7+
range addition with range sum queries or range assignment with range minimum
8+
queries.
9+
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
///|
2+
pub enum LazySegmentTreeError {
3+
IndexOutOfBounds
4+
InvalidRange
5+
} derive(Show, Eq)
6+
7+
///|
8+
pub struct LazySegmentTree[T, L] {
9+
size : Int
10+
tree : Array[T]
11+
tag : Array[L]
12+
merge : (T, T) -> T
13+
apply : (T, L, Int) -> T
14+
compose : (L, L) -> L
15+
identity : L
16+
} derive(Show)
17+
18+
///|
19+
fn[T, L] build(
20+
self : LazySegmentTree[T, L],
21+
arr : Array[T],
22+
node : Int,
23+
start : Int,
24+
end : Int,
25+
) -> Unit {
26+
if start == end {
27+
self.tree[node] = arr[start]
28+
return
29+
}
30+
let mid = (start + end) / 2
31+
let left = node * 2
32+
let right = left + 1
33+
self.build(arr, left, start, mid)
34+
self.build(arr, right, mid + 1, end)
35+
self.tree[node] = (self.merge)(self.tree[left], self.tree[right])
36+
}
37+
38+
///|
39+
fn[T, L] push(
40+
self : LazySegmentTree[T, L],
41+
node : Int,
42+
start : Int,
43+
end : Int,
44+
) -> Unit {
45+
if start == end {
46+
self.tag[node] = self.identity
47+
return
48+
}
49+
let tag = self.tag[node]
50+
let mid = (start + end) / 2
51+
let left = node * 2
52+
let right = left + 1
53+
let left_len = mid - start + 1
54+
let right_len = end - mid
55+
self.tree[left] = (self.apply)(self.tree[left], tag, left_len)
56+
self.tree[right] = (self.apply)(self.tree[right], tag, right_len)
57+
self.tag[left] = (self.compose)(self.tag[left], tag)
58+
self.tag[right] = (self.compose)(self.tag[right], tag)
59+
self.tag[node] = self.identity
60+
}
61+
62+
///|
63+
fn[T, L] range_update_internal(
64+
self : LazySegmentTree[T, L],
65+
node : Int,
66+
start : Int,
67+
end : Int,
68+
left : Int,
69+
right : Int,
70+
value : L,
71+
) -> Unit {
72+
if left > end || right < start {
73+
return
74+
}
75+
if left <= start && end <= right {
76+
let length = end - start + 1
77+
self.tree[node] = (self.apply)(self.tree[node], value, length)
78+
self.tag[node] = (self.compose)(self.tag[node], value)
79+
return
80+
}
81+
self.push(node, start, end)
82+
let mid = (start + end) / 2
83+
let left_node = node * 2
84+
let right_node = left_node + 1
85+
self.range_update_internal(left_node, start, mid, left, right, value)
86+
self.range_update_internal(right_node, mid + 1, end, left, right, value)
87+
self.tree[node] = (self.merge)(self.tree[left_node], self.tree[right_node])
88+
}
89+
90+
///|
91+
fn[T, L] range_query_internal(
92+
self : LazySegmentTree[T, L],
93+
node : Int,
94+
start : Int,
95+
end : Int,
96+
left : Int,
97+
right : Int,
98+
) -> T? {
99+
if left > end || right < start {
100+
return None
101+
}
102+
if left <= start && end <= right {
103+
return Some(self.tree[node])
104+
}
105+
self.push(node, start, end)
106+
let mid = (start + end) / 2
107+
let left_node = node * 2
108+
let right_node = left_node + 1
109+
let left_res = self.range_query_internal(left_node, start, mid, left, right)
110+
let right_res = self.range_query_internal(
111+
right_node,
112+
mid + 1,
113+
end,
114+
left,
115+
right,
116+
)
117+
match (left_res, right_res) {
118+
(None, None) => None
119+
(Some(value), None) => Some(value)
120+
(None, Some(value)) => Some(value)
121+
(Some(a), Some(b)) => Some((self.merge)(a, b))
122+
}
123+
}
124+
125+
///|
126+
///Creates a new `LazySegmentTree` from the provided slice of elements.
127+
///
128+
/// # Parameters
129+
///
130+
/// - `arr` : A slice of elements of type `T` to initialize the segment tree.(0-based)
131+
/// - `merge` : Function to merge two nodes of the segment tree.
132+
/// - `apply` : Function that applies a lazy value `L` to a node value `T` over a segment length.
133+
/// - `compose` : Function describing how to compose two lazy values.
134+
/// - `identity` : Identity lazy value representing "do nothing".
135+
///
136+
/// # Returns
137+
///
138+
/// - `LazySegmentTree` : A new `LazySegmentTree` instance populated with the given elements.
139+
///
140+
/// # Example
141+
/// ```
142+
/// let a1 = [1, 2, 3, 4, 5]
143+
/// let _ = LazySegmentTree::from_array(
144+
/// a1,
145+
/// fn(a, b) { a + b },
146+
/// fn(value, tag, length) { value + tag * length },
147+
/// fn(old, new) { old + new },
148+
/// 0,
149+
/// )
150+
/// ```
151+
pub fn[T : Default, L] LazySegmentTree::from_array(
152+
arr : Array[T],
153+
merge : (T, T) -> T,
154+
apply : (T, L, Int) -> T,
155+
compose : (L, L) -> L,
156+
identity : L,
157+
) -> LazySegmentTree[T, L] {
158+
let size = arr.length()
159+
let capacity = if size == 0 { 1 } else { 4 * size }
160+
let tree = Array::make(capacity, T::default())
161+
let tag = Array::make(capacity, identity)
162+
let seg = LazySegmentTree::{
163+
size,
164+
tree,
165+
tag,
166+
merge,
167+
apply,
168+
compose,
169+
identity,
170+
}
171+
if size > 0 {
172+
seg.build(arr, 1, 0, size - 1)
173+
}
174+
seg
175+
}
176+
177+
///|
178+
///Performs a range update on the segment tree using the provided lazy value.
179+
///
180+
/// # Parameters
181+
///
182+
/// - `self` : `LazySegmentTree`
183+
/// - `left` : The left boundary of the range (inclusive).
184+
/// - `right` : The right boundary of the range (inclusive).
185+
/// - `value` : The lazy value to apply over the range.
186+
///
187+
/// # Returns
188+
///
189+
/// - `Ok(())` if the update was successful.
190+
/// - `Err(LazySegmentTreeError::InvalidRange)` if the range is invalid or the tree is empty.
191+
/// - `Err(LazySegmentTreeError::IndexOutOfBounds)` if the range is outside the array or the tree is empty.
192+
///
193+
/// # Example
194+
/// ```
195+
/// let a1 = [1, 2, 3, 4, 5]
196+
/// let seg = LazySegmentTree::from_array(
197+
/// a1,
198+
/// fn(a, b) { a + b },
199+
/// fn(value, tag, length) { value + tag * length },
200+
/// fn(old, new) { old + new },
201+
/// 0,
202+
/// )
203+
/// inspect(seg.range_update(0, 2, 5).unwrap(), content="()")
204+
/// ```
205+
pub fn[T, L] LazySegmentTree::range_update(
206+
self : LazySegmentTree[T, L],
207+
left : Int,
208+
right : Int,
209+
value : L,
210+
) -> Result[Unit, LazySegmentTreeError] {
211+
if left < 0 || right < 0 {
212+
return Err(LazySegmentTreeError::IndexOutOfBounds)
213+
}
214+
if self.size == 0 {
215+
return Err(LazySegmentTreeError::InvalidRange)
216+
}
217+
if left >= self.size || right >= self.size {
218+
return Err(LazySegmentTreeError::IndexOutOfBounds)
219+
}
220+
if left > right {
221+
return Err(LazySegmentTreeError::InvalidRange)
222+
}
223+
self.range_update_internal(1, 0, self.size - 1, left, right, value)
224+
Ok(())
225+
}
226+
227+
///|
228+
///Queries the segment tree for the merged value within the given range.
229+
///
230+
/// # Parameters
231+
///
232+
/// - `self` : `LazySegmentTree`
233+
/// - `left` : The left boundary of the range (inclusive).
234+
/// - `right` : The right boundary of the range (inclusive).
235+
///
236+
/// # Returns
237+
///
238+
/// - `Ok(Some(result))` if the query was successful and the range is non-empty.
239+
/// - `Ok(None)` if `left > right`.
240+
/// - `Err(LazySegmentTreeError::IndexOutOfBounds)` if the range is outside the array or the tree is empty.
241+
///
242+
/// # Example
243+
/// ```
244+
/// let a1 = [1, 2, 3, 4, 5]
245+
/// let seg = LazySegmentTree::from_array(
246+
/// a1,
247+
/// fn(a, b) { a + b },
248+
/// fn(value, tag, length) { value + tag * length },
249+
/// fn(old, new) { old + new },
250+
/// 0,
251+
/// )
252+
/// inspect(seg.range_query(1, 3).unwrap(), content="Some(9)")
253+
/// ```
254+
pub fn[T, L] LazySegmentTree::range_query(
255+
self : LazySegmentTree[T, L],
256+
left : Int,
257+
right : Int,
258+
) -> Result[T?, LazySegmentTreeError] {
259+
if left < 0 || right < 0 {
260+
return Err(LazySegmentTreeError::IndexOutOfBounds)
261+
}
262+
if self.size == 0 {
263+
return Err(LazySegmentTreeError::IndexOutOfBounds)
264+
}
265+
if left >= self.size || right >= self.size {
266+
return Err(LazySegmentTreeError::IndexOutOfBounds)
267+
}
268+
if left > right {
269+
return Ok(None)
270+
}
271+
let result = self.range_query_internal(1, 0, self.size - 1, left, right)
272+
Ok(result)
273+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
///|
2+
fn apply_sum(value : Int, tag : Int, length : Int) -> Int {
3+
value + tag * length
4+
}
5+
6+
///|
7+
fn compose_sum(old : Int, new_val : Int) -> Int {
8+
old + new_val
9+
}
10+
11+
///|
12+
test "lazy_segment_tree_range_add_sum" {
13+
let arr = [1, 2, 3, 4, 5, 6]
14+
let seg = LazySegmentTree::from_array(
15+
arr,
16+
fn(a, b) { a + b },
17+
apply_sum,
18+
compose_sum,
19+
0,
20+
)
21+
inspect(seg.range_query(0, 5).unwrap(), content="Some(21)")
22+
inspect(seg.range_update(1, 3, 2).unwrap(), content="()")
23+
inspect(seg.range_query(0, 2).unwrap(), content="Some(10)")
24+
inspect(seg.range_query(3, 5).unwrap(), content="Some(17)")
25+
inspect(seg.range_update(0, 5, -1).unwrap(), content="()")
26+
inspect(seg.range_query(0, 5).unwrap(), content="Some(21)")
27+
}
28+
29+
///|
30+
test "lazy_segment_tree_invalid_operations" {
31+
let arr = [1, 2, 3]
32+
let seg = LazySegmentTree::from_array(
33+
arr,
34+
fn(a, b) { a + b },
35+
apply_sum,
36+
compose_sum,
37+
0,
38+
)
39+
inspect(seg.range_query(-1, 2), content="Err(IndexOutOfBounds)")
40+
inspect(seg.range_query(0, 5), content="Err(IndexOutOfBounds)")
41+
inspect(seg.range_update(2, 1, 3), content="Err(InvalidRange)")
42+
inspect(seg.range_update(0, 4, 1), content="Err(IndexOutOfBounds)")
43+
inspect(seg.range_query(2, 1).unwrap(), content="None")
44+
}

lazy_segment_tree/moon.pkg.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Generated using `moon info`, DON'T EDIT IT
2+
package "Lampese/Algorithms-Moonbit/lazy_segment_tree"
3+
4+
// Values
5+
6+
// Errors
7+
8+
// Types and methods
9+
pub struct LazySegmentTree[T, L] {
10+
size : Int
11+
tree : Array[T]
12+
tag : Array[L]
13+
merge : (T, T) -> T
14+
apply : (T, L, Int) -> T
15+
compose : (L, L) -> L
16+
identity : L
17+
}
18+
fn[T : Default, L] LazySegmentTree::from_array(Array[T], (T, T) -> T, (T, L, Int) -> T, (L, L) -> L, L) -> Self[T, L]
19+
fn[T, L] LazySegmentTree::range_query(Self[T, L], Int, Int) -> Result[T?, LazySegmentTreeError]
20+
fn[T, L] LazySegmentTree::range_update(Self[T, L], Int, Int, L) -> Result[Unit, LazySegmentTreeError]
21+
impl[T : Show, L : Show] Show for LazySegmentTree[T, L]
22+
23+
pub enum LazySegmentTreeError {
24+
IndexOutOfBounds
25+
InvalidRange
26+
}
27+
impl Eq for LazySegmentTreeError
28+
impl Show for LazySegmentTreeError
29+
30+
// Type aliases
31+
32+
// Traits
33+

0 commit comments

Comments
 (0)