Skip to content

Commit

Permalink
add remaining methods and tests
Browse files Browse the repository at this point in the history
Signed-off-by: Alvydas Vitkauskas <[email protected]>
  • Loading branch information
avitkauskas committed Nov 9, 2024
1 parent 485b49b commit 81a16eb
Show file tree
Hide file tree
Showing 5 changed files with 703 additions and 4 deletions.
13 changes: 13 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,19 @@ what we publish.
allocate additional capacity.
([PR #3755](https://github.com/modularml/mojo/pull/3755) by [@thatstoasty](https://github.com/thatstoasty)).

- Introduced a new `Deque` (double-ended queue) collection type, based on a
dynamically resizing circular buffer for efficient O(1) additions and removals
at both ends as well as O(1) direct access to all elements.

The `Deque` supports the full Python `collections.deque` API, ensuring that all
expected deque operations perform as in Python.

Enhancements to the standard Python API include `peek()` and `peekleft()`
methods for non-destructive access to the last and first elements, and advanced
constructor options (`capacity`, `min_capacity`, and `shrink`) for customizing
memory allocation and performance. These options allow for optimized memory usage
and reduced buffer reallocations, providing flexibility based on application requirements.

### 🦋 Changed

- More things have been removed from the auto-exported set of entities in the `prelude`
Expand Down
22 changes: 21 additions & 1 deletion stdlib/src/builtin/reversed.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
These are Mojo built-ins, so you don't need to import them.
"""

from collections import Dict
from collections import Deque, Dict
from collections.deque import _DequeIter
from collections.dict import _DictEntryIter, _DictKeyIter, _DictValueIter
from collections.list import _ListIter

Expand Down Expand Up @@ -97,6 +98,25 @@ fn reversed[
return value.__reversed__()


fn reversed[
T: CollectionElement
](ref [_]value: Deque[T]) -> _DequeIter[T, __origin_of(value), False]:
"""Get a reversed iterator of the deque.
**Note**: iterators are currently non-raising.
Parameters:
T: The type of the elements in the deque.
Args:
value: The deque to get the reversed iterator of.
Returns:
The reversed iterator of the deque.
"""
return value.__reversed__()


fn reversed[
K: KeyElement,
V: CollectionElement,
Expand Down
271 changes: 271 additions & 0 deletions stdlib/src/collections/deque.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,28 @@ struct Deque[ElementType: CollectionElement](
self._head = 0
self._tail = 0

fn count[
EqualityElementType: EqualityComparableCollectionElement, //
](self: Deque[EqualityElementType], value: EqualityElementType) -> Int:
"""Counts the number of occurrences of a `value` in the deque.
Parameters:
EqualityElementType: The type of the elements in the deque.
Must implement the trait `EqualityComparableCollectionElement`.
Args:
value: The value to count.
Returns:
The number of occurrences of the value in the deque.
"""
count = 0
for i in range(len(self)):
offset = self._physical_index(self._head + i)
if (self._data + offset)[] == value:
count += 1
return count

fn extend(inout self, owned values: List[ElementType]):
"""Extends the right side of the deque by consuming elements of the list argument.
Expand Down Expand Up @@ -603,6 +625,255 @@ struct Deque[ElementType: CollectionElement](
self._head = self._physical_index(self._head - 1)
(src + i).move_pointee_into(self._data + self._head)

fn index[
EqualityElementType: EqualityComparableCollectionElement, //
](
self: Deque[EqualityElementType],
value: EqualityElementType,
start: Int = 0,
stop: Optional[Int] = None,
) raises -> Int:
"""Returns the index of the first occurrence of a `value` in a deque
restricted by the range given the `start` and `stop` bounds.
Args:
value: The value to search for.
start: The starting index of the search, treated as a slice index
(defaults to 0).
stop: The ending index of the search, treated as a slice index
(defaults to None, which means the end of the deque).
Parameters:
EqualityElementType: The type of the elements in the deque.
Must implement the `EqualityComparableCollectionElement` trait.
Returns:
The index of the first occurrence of the value in the deque.
Raises:
ValueError: If the value is not found in the deque.
"""
start_normalized = start

if stop is None:
stop_normalized = len(self)
else:
stop_normalized = stop.value()

if start_normalized < 0:
start_normalized += len(self)
if stop_normalized < 0:
stop_normalized += len(self)

start_normalized = max(0, min(start_normalized, len(self)))
stop_normalized = max(0, min(stop_normalized, len(self)))

for idx in range(start_normalized, stop_normalized):
offset = self._physical_index(self._head + idx)
if (self._data + offset)[] == value:
return idx
raise "ValueError: Given element is not in deque"

fn insert(inout self, idx: Int, owned value: ElementType) raises:
"""Inserts the `value` into the deque at position `idx`.
Args:
idx: The position to insert the value into.
value: The value to insert.
Raises:
IndexError: If deque is already at its maximum size.
"""
deque_len = len(self)

if deque_len == self._maxlen:
raise "IndexError: Deque is already at its maximum size"

normalized_idx = idx

if normalized_idx < -deque_len:
normalized_idx = 0

if normalized_idx > deque_len:
normalized_idx = deque_len

if normalized_idx < 0:
normalized_idx += deque_len

if normalized_idx <= deque_len // 2:
for i in range(normalized_idx):
src = self._physical_index(self._head + i)
dst = self._physical_index(src - 1)
(self._data + src).move_pointee_into(self._data + dst)
self._head = self._physical_index(self._head - 1)
else:
for i in range(deque_len - normalized_idx):
dst = self._physical_index(self._tail - i)
src = self._physical_index(dst - 1)
(self._data + src).move_pointee_into(self._data + dst)
self._tail = self._physical_index(self._tail + 1)

offset = self._physical_index(self._head + normalized_idx)
(self._data + offset).init_pointee_move(value^)

if self._head == self._tail:
self._realloc(self._capacity << 1)

fn remove[
EqualityElementType: EqualityComparableCollectionElement, //
](
inout self: Deque[EqualityElementType],
value: EqualityElementType,
) raises:
"""Removes the first occurrence of the `value`.
Args:
value: The value to remove.
Raises:
ValueError: If the value is not found in the deque.
"""
deque_len = len(self)
for idx in range(deque_len):
offset = self._physical_index(self._head + idx)
if (self._data + offset)[] == value:
(self._data + offset).destroy_pointee()

if idx < deque_len // 2:
for i in reversed(range(idx)):
src = self._physical_index(self._head + i)
dst = self._physical_index(src + 1)
(self._data + src).move_pointee_into(self._data + dst)
self._head = self._physical_index(self._head + 1)
else:
for i in range(idx + 1, deque_len):
src = self._physical_index(self._head + i)
dst = self._physical_index(src - 1)
(self._data + src).move_pointee_into(self._data + dst)
self._tail = self._physical_index(self._tail - 1)

if (
self._shrink
and self._capacity > self._min_capacity
and self._capacity // 4 >= len(self)
):
self._realloc(self._capacity >> 1)

return

raise "ValueError: Given element is not in deque"

fn peek(self) raises -> ElementType:
"""Inspect the last (rightmost) element of the deque without removing it.
Returns:
The the last (rightmost) element of the deque.
Raises:
IndexError: If the deque is empty.
"""
if self._head == self._tail:
raise "IndexError: Deque is empty"

return (self._data + self._physical_index(self._tail - 1))[]

fn peekleft(self) raises -> ElementType:
"""Inspect the first (leftmost) element of the deque without removing it.
Returns:
The the first (leftmost) element of the deque.
Raises:
IndexError: If the deque is empty.
"""
if self._head == self._tail:
raise "IndexError: Deque is empty"

return (self._data + self._head)[]

fn pop(inout self) raises -> ElementType as element:
"""Removes and returns the element from the right side of the deque.
Returns:
The popped value.
Raises:
IndexError: If the deque is empty.
"""
if self._head == self._tail:
raise "IndexError: Deque is empty"

self._tail = self._physical_index(self._tail - 1)
element = (self._data + self._tail).take_pointee()

if (
self._shrink
and self._capacity > self._min_capacity
and self._capacity // 4 >= len(self)
):
self._realloc(self._capacity >> 1)

return

fn popleft(inout self) raises -> ElementType as element:
"""Removes and returns the element from the left side of the deque.
Returns:
The popped value.
Raises:
IndexError: If the deque is empty.
"""
if self._head == self._tail:
raise "IndexError: Deque is empty"

element = (self._data + self._head).take_pointee()
self._head = self._physical_index(self._head + 1)

if (
self._shrink
and self._capacity > self._min_capacity
and self._capacity // 4 >= len(self)
):
self._realloc(self._capacity >> 1)

return

fn reverse(inout self):
"""Reverses the elements of the deque in-place."""
last = self._head + len(self) - 1
for i in range(len(self) // 2):
src = self._physical_index(self._head + i)
dst = self._physical_index(last - i)
tmp = (self._data + dst).take_pointee()
(self._data + src).move_pointee_into(self._data + dst)
(self._data + src).init_pointee_move(tmp^)

fn rotate(inout self, n: Int = 1):
"""Rotates the deque by `n` steps.
If `n` is positive, rotates to the right.
If `n` is negative, rotates to the left.
Args:
n: Number of steps to rotate the deque
(defaults to 1).
"""
if n < 0:
for _ in range(-n):
(self._data + self._head).move_pointee_into(
self._data + self._tail
)
self._tail = self._physical_index(self._tail + 1)
self._head = self._physical_index(self._head + 1)
else:
for _ in range(n):
self._tail = self._physical_index(self._tail - 1)
self._head = self._physical_index(self._head - 1)
(self._data + self._tail).move_pointee_into(
self._data + self._head
)

fn _compute_pop_and_move_counts(
self, len_self: Int, len_values: Int
) -> (Int, Int, Int, Int, Int):
Expand Down
11 changes: 10 additions & 1 deletion stdlib/test/builtin/test_reversed.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# ===----------------------------------------------------------------------=== #
# RUN: %mojo %s

from collections import Dict
from collections import Deque, Dict
from testing import assert_equal


Expand All @@ -25,6 +25,15 @@ def test_reversed_list():
check -= 1


def test_reversed_deque():
var deque = Deque[Int](1, 2, 3, 4, 5, 6)
var check: Int = 6

for item in reversed(deque):
assert_equal(item[], check, "item[], check")
check -= 1


def test_reversed_dict():
var dict = Dict[String, Int]()
dict["a"] = 1
Expand Down
Loading

0 comments on commit 81a16eb

Please sign in to comment.