Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions deque/deque.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,125 @@ pub fn[A] append(self : Deque[A], other : Deque[A]) -> Unit {
}
}

///|
/// Inserts an element at the specified position in the deque. The elements are
/// shifted in-place to make room for the new element, modifying the original deque.
///
/// Parameters:
///
/// * `self` : The deque in which the element will be inserted.
/// * `index` : The position at which to insert the element. Must satisfy `0 <= index <= self.length()`.
/// * `value` : The element to insert.
///
/// Panics:
///
/// * If `index` is out of bounds, the function will abort with an error message.
///
/// Example:
///
/// ```moonbit
/// let v1 = @deque.of([1, 2, 3])
/// v1.insert(0, 0) // insert at the front
/// inspect(v1, content="@deque.of([0, 1, 2, 3])")
/// let v2 = @deque.of([1, 2, 4])
/// v2.insert(2, 3) // insert in the middle
/// inspect(v2, content="@deque.of([1, 2, 3, 4])")
/// let v3 = @deque.of([2, 3, 4])
/// v3.insert(3, 5) // insert at the end
/// inspect(v3, content="@deque.of([2, 3, 4, 5])")
/// ```
pub fn[A] insert(self : Deque[A], index : Int, value : A) -> Unit {
guard index >= 0 && index <= self.length() else {
abort(
"index out of bounds: the len is from 0 to \{self.length()} but the index is \{index}",
)
}
if self.buf.length() - self.len == 0 {
self.realloc()
}
let cap = self.buf.length()
if index < self.len / 2 {
let new_head = (self.head - 1 + cap) % cap
for i = 0; i < index; i = i + 1 {
let to = (new_head + i) % cap
let from = (self.head + i) % cap
self.buf[to] = self.buf[from]
}
self.head = new_head
} else {
let new_tail = (self.tail + 1) % cap
for i = self.len; i > index; i = i - 1 {
let from = (self.head + i - 1) % cap
let to = (self.head + i) % cap
self.buf[to] = self.buf[from]
}
self.tail = new_tail
}
self.buf[(self.head + index) % cap] = value
self.len += 1
}

///|
/// Removes and returns the element at the specified position in the deque. The
/// remaining elements are shifted in-place to fill the gap, modifying the original deque.
///
/// Parameters:
///
/// * `self` : The deque from which the element will be removed.
/// * `index` : The position of the element to remove. Must satisfy `0 <= index < self.length()`.
///
/// Returns:
///
/// * The element that was removed from the deque.
///
/// Panics:
///
/// * If `index` is out of bounds, the function will abort with an error message.
///
/// Example:
///
/// ```moonbit
/// let v1 = @deque.of([0, 1, 2, 3])
/// let x = v1.remove(0) // remove from the front
/// inspect((x, v1), content="(0, @deque.of([1, 2, 3]))")
/// let v2 = @deque.of([1, 2, 3, 4])
/// let y = v2.remove(2) // remove from the middle
/// inspect((y, v2), content="(3, @deque.of([1, 2, 4]))")
/// let v3 = @deque.of([2, 3, 4, 5])
/// let z = v3.remove(3) // remove from the end
/// inspect((z, v3), content="(5, @deque.of([2, 3, 4]))")
/// ```
pub fn[A] remove(self : Deque[A], index : Int) -> A {
guard index >= 0 && index < self.length() else {
abort(
"index out of bounds: the len is from 0 to \{self.length()} but the index is \{index}",
)
}
let res = self[index]
let cap = self.buf.length()
if index < self.len / 2 {
let new_head = (self.head + 1) % cap
for i = index - 1; i >= 0; i = i - 1 {
let to = (self.head + i + 1) % cap
let from = (self.head + i) % cap
self.buf[to] = self.buf[from]
}
set_null(self.buf, self.head)
self.head = new_head
} else {
let new_tail = (self.tail - 1 + cap) % cap
for i = index + 1; i < self.len; i = i + 1 {
let to = (self.head + i - 1) % cap
let from = (self.head + i) % cap
self.buf[to] = self.buf[from]
}
set_null(self.buf, self.tail)
self.tail = new_tail
}
self.len -= 1
res
}

///|
/// Creates a new deque from a fixed array, preserving the order of elements.
///
Expand Down
118 changes: 118 additions & 0 deletions deque/deque_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -1508,3 +1508,121 @@ test "append_folded_but_has_space" {
d1.append(@deque.of([9, 10, 11]))
inspect(d1.as_views(), content="([4, 5, 6, 7, 9], [10, 11])")
}

///|
/// Test insert at the front
test "insert_front" {
let d = @deque.of([1, 2, 3])
d.insert(0, 99)
assert_eq(d.to_array(), [99, 1, 2, 3])
inspect(d.as_views(), content="([99], [1, 2, 3])")
}

///|
/// Test insert in the middle
test "insert_middle" {
let d = @deque.of([1, 2, 3, 4])
d.insert(2, 99)
assert_eq(d.to_array(), [1, 2, 99, 3, 4])
inspect(d.as_views(), content="([1, 2, 99, 3, 4], [])")
}

///|
/// Test insert at the back (same as push_back)
test "insert_back" {
let d = @deque.of([1, 2, 3])
d.insert(d.length(), 99)
assert_eq(d.to_array(), [1, 2, 3, 99])
inspect(d.as_views(), content="([1, 2, 3, 99], [])")
}

///|
/// Test insert triggers reallocation
test "insert_realloc" {
let d = @deque.of([])
for i = 0; i < 8; i = i + 1 {
d.push_back(i)
}
inspect(d.as_views(), content="([0, 1, 2, 3, 4, 5, 6, 7], [])")
d.insert(4, 99)
assert_eq(d.to_array()[4], 99)
assert_eq(d.length(), 9)
inspect(d.as_views(), content="([0, 1, 2, 3, 99, 4, 5, 6, 7], [])")
}

///|
/// Test insert into folded buffer
test "insert_folded" {
let d = @deque.of([1, 2, 3, 4, 5, 6, 7, 8])
d.unsafe_pop_front()
d.unsafe_pop_front()
d.unsafe_pop_front()
d.unsafe_pop_front()
for i = 9; i < 13; i = i + 1 {
d.push_back(i)
}
inspect(d.as_views(), content="([5, 6, 7, 8], [9, 10, 11, 12])")
d.insert(2, 99)
assert_eq(d.to_array()[2], 99)
inspect(d.to_array(), content="[5, 6, 99, 7, 8, 9, 10, 11, 12]")
inspect(d.as_views(), content="([5], [6, 99, 7, 8, 9, 10, 11, 12])")
}

///|
/// Test remove from the front
test "remove_front" {
let d = @deque.of([99, 1, 2, 3])
let x = d.remove(0)
assert_eq(x, 99)
assert_eq(d.to_array(), [1, 2, 3])
inspect(d.as_views(), content="([1, 2, 3], [])")
}

///|
/// Test remove from the middle
test "remove_middle" {
let d = @deque.of([1, 2, 99, 3, 4])
let x = d.remove(2)
assert_eq(x, 99)
assert_eq(d.to_array(), [1, 2, 3, 4])
inspect(d.as_views(), content="([1, 2, 3, 4], [])")
}

///|
/// Test remove from the back (same as pop_back)
test "remove_back" {
let d = @deque.of([1, 2, 3, 99])
let x = d.remove(d.length() - 1)
assert_eq(x, 99)
assert_eq(d.to_array(), [1, 2, 3])
inspect(d.as_views(), content="([1, 2, 3], [])")
}

///|
/// Test remove triggers shifting
test "remove_shift" {
let d = @deque.of([0, 1, 2, 3, 99, 4, 5, 6, 7])
let x = d.remove(4)
assert_eq(x, 99)
assert_eq(d.length(), 8)
assert_eq(d.to_array(), [0, 1, 2, 3, 4, 5, 6, 7])
inspect(d.as_views(), content="([0, 1, 2, 3, 4, 5, 6, 7], [])")
}

///|
/// Test remove from folded buffer
test "remove_folded" {
let d = @deque.of([1, 2, 3, 4, 5, 6, 7, 8])
d.unsafe_pop_front()
d.unsafe_pop_front()
d.unsafe_pop_front()
d.unsafe_pop_front()
for i = 9; i < 13; i = i + 1 {
d.push_back(i)
}
inspect(d.as_views(), content="([5, 6, 7, 8], [9, 10, 11, 12])")
let x = d.remove(2)
assert_eq(x, 7)
assert_eq(d.to_array(), [5, 6, 8, 9, 10, 11, 12])
inspect(d.as_views(), content="([5, 6, 8], [9, 10, 11, 12])")
}
2 changes: 2 additions & 0 deletions deque/pkg.generated.mbti
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fn[A] Deque::from_array(Array[A]) -> Self[A]
fn[A] Deque::from_iter(Iter[A]) -> Self[A]
fn[A] Deque::front(Self[A]) -> A?
fn[A] Deque::get(Self[A], Int) -> A?
fn[A] Deque::insert(Self[A], Int, A) -> Unit
fn[A] Deque::is_empty(Self[A]) -> Bool
fn[A] Deque::iter(Self[A]) -> Iter[A]
fn[A] Deque::iter2(Self[A]) -> Iter2[Int, A]
Expand All @@ -54,6 +55,7 @@ fn[A] Deque::pop_front(Self[A]) -> A?
fn[A] Deque::pop_front_exn(Self[A]) -> Unit
fn[A] Deque::push_back(Self[A], A) -> Unit
fn[A] Deque::push_front(Self[A], A) -> Unit
fn[A] Deque::remove(Self[A], Int) -> A
fn[A] Deque::reserve_capacity(Self[A], Int) -> Unit
fn[A] Deque::retain(Self[A], (A) -> Bool) -> Unit
fn[A] Deque::retain_map(Self[A], (A) -> A?) -> Unit
Expand Down
Loading