diff --git a/std/src/std/array.inko b/std/src/std/array.inko index 87ff8017..d1ac6ee0 100644 --- a/std/src/std/array.inko +++ b/std/src/std/array.inko @@ -103,15 +103,13 @@ class builtin Array[T] { let @capacity: Int let @buffer: Pointer[T] - # Returns a new `Array` with enough space for at least `amount` values. - # - # The actual capacity may be greater than the given value. + # Returns a new `Array` with enough space for at least `size` values. # # # Panics # # This method panics of `size` if less than zero. fn pub static with_capacity(size: Int) -> Array[T] { - if size < 0 { panic('The capacity must be greater than or equal to zero') } + if size < 0 { panic('the capacity must be greater than or equal to zero') } Array(size: 0, capacity: size, buffer: alloc.resize(0 as Pointer[T], size)) } @@ -381,8 +379,7 @@ class builtin Array[T] { }) } - # Returns an `Iter` that iterates over all values in `self`, returning them - # by value. + # Returns an iterator that moves the values out of `self`. # # # Examples # diff --git a/std/src/std/deque.inko b/std/src/std/deque.inko new file mode 100644 index 00000000..301a09da --- /dev/null +++ b/std/src/std/deque.inko @@ -0,0 +1,384 @@ +# A double-ended queue (= "deque"). +import std.alloc +import std.cmp (max) +import std.drop (Drop) +import std.iter (Iter, Stream) +import std.ptr + +# A double-ended queue (= "deque"). +# +# This type is implemented as a growable ring buffer, and supports fast inserts +# and removals at the head and tail of the queue. +# +# The implementation is currently based on Rust's `VecDeque` type, which in turn +# is based on a comment by dizzy57 on [this +# article](https://www.snellman.net/blog/archive/2016-12-13-ring-buffers/). +class pub Deque[T] { + # The number of values stored in the deque. + let pub @size: Int + + # The number of values that can be stored in the deque before a resize is + # needed. + let @capacity: Int + + # The index at which to perform operations that act on the head/start of the + # deque. + let @head: Int + + # The buffer storing the values. + let @buffer: Pointer[T] + + # Returns a `Deque` with enough space for at least `size` values. + # + # # Panics + # + # This method panics of `size` if less than zero. + # + # # Examples + # + # ```inko + # import std.deque (Deque) + # + # Deque.with_capacity(42) + # ``` + fn pub static with_capacity(size: Int) -> Deque[T] { + if size < 0 { panic('the capacity must be greater than or equal to zero') } + + Deque( + size: 0, + capacity: size, + head: 0, + buffer: alloc.resize(0 as Pointer[T], size), + ) + } + + # Returns a `Deque` with a capacity of zero. + # + # See `Deque.with_capacity` for more details. + # + # # Examples + # + # ```inko + # import std.deque (Deque) + # + # Deque.new + # ``` + fn pub static new -> Deque[T] { + with_capacity(0) + } + + # Pushes a value to the front of `self`. + # + # # Examples + # + # ```inko + # import std.deque (Deque) + # + # let q = Deque.new + # + # q.push_front(42) + # ``` + fn pub mut push_front(value: T) { + reserve(1) + + @head = @head.wrapping_sub(1).wrapping_add(@capacity) % @capacity + @size += 1 + write_to(@head, value) + } + + # Removes a value from the front of `self`, returning the removed value. + # + # If no value was found, a None is returned. + # + # # Examples + # + # ```inko + # import std.deque (Deque) + # + # let q = Deque.new + # + # q.push_front(10) + # q.push_front(20) + # + # q.pop_front # => Option.Some(20) + # q.pop_front # => Option.Some(10) + # q.pop_front # => Option.None + # ``` + fn pub mut pop_front -> Option[T] { + if @size == 0 { return Option.None } + + let read = @head := to_buffer_index(1) + + @size -= 1 + Option.Some(read_from(read)) + } + + # Pushes a value to the back of `self`. + # + # # Examples + # + # ```inko + # import std.deque (Deque) + # + # let q = Deque.new + # + # q.push_back(42) + # ``` + fn pub mut push_back(value: T) { + reserve(1) + write_to(to_buffer_index(@size), value) + @size += 1 + } + + # Removes a value from the back of `self`, returning the removed value. + # + # If no value was found, a None is returned. + # + # # Examples + # + # ```inko + # import std.deque (Deque) + # + # let q = Deque.new + # + # q.push_back(10) + # q.push_back(20) + # + # q.pop_back # => Option.Some(20) + # q.pop_back # => Option.Some(10) + # q.pop_back # => Option.None + # ``` + fn pub mut pop_back -> Option[T] { + if @size == 0 { return Option.None } + + @size -= 1 + Option.Some(read_from(to_buffer_index(@size))) + } + + # Reserves space for `size` additional values. + # + # The actual space reserved may be greater to prevent frequent reallocations. + # After calling this method, the capacity will be greater than or equal to + # `self.size + size`. + # + # If the capacity is great enough or the given size is less than zero, this + # method does nothing. + fn pub mut reserve(size: Int) { + if @capacity - @size >= size { return } + + let old_cap = @capacity + + @capacity = max(@capacity * 2, @capacity + size) + @buffer = alloc.resize(@buffer, @capacity) + handle_increase(old_cap) + } + + # Removes all values in `self`. + # + # # Examples + # + # ```inko + # import std.deque (Deque) + # + # let q = Deque.new + # + # q.push_back(10) + # q.push_back(20) + # q.clear + # q.size # => 0 + # ``` + fn pub mut clear { + loop { + match pop_back { + case Some(_) -> {} + case _ -> break + } + } + + @head = 0 + } + + # Returns an iterator that yields immutable borrows of the values in `self`. + # + # # Examples + # + # ```inko + # import std.deque (Deque) + # + # let q = Deque.new + # + # q.push_back(10) + # q.push_front(20) + # q.push_back(30) + # + # q.iter.to_array # => [20, 10, 30] + # ``` + fn pub iter -> Stream[ref T] { + indexes.map(fn (i) { get_unchecked(i) }) + } + + # Returns an iterator that moves the values out of `self`, yielding from the + # front to the back of the deque. + # + # # Examples + # + # ```inko + # import std.deque (Deque) + # + # let q = Deque.new + # + # q.push_back(10) + # q.push_back(20) + # q.into_iter.to_array # => [10, 20] + # ``` + fn pub move into_iter -> IntoIter[T] { + IntoIter(indexes: indexes, deque: self) + } + + fn indexes -> Stream[Int] { + let start = to_buffer_index(0) + let head_size = @capacity - start + let mut idx = 0 + let mut iter_tail = false + let mut max = 0 + + if head_size >= @size { + max = start + @size + } else { + iter_tail = true + idx = start + max = @capacity + } + + Stream.new(fn move { + loop { + if idx < max { + return Option.Some(idx := idx + 1) + } else if iter_tail { + iter_tail = false + idx = 0 + max = @size - head_size + } else { + return Option.None + } + } + }) + } + + fn read_from(index: Int) -> T { + address_of(index).0 + } + + fn mut write_to(index: Int, value: T) { + address_of(index).0 = value + } + + fn address_of(index: Int) -> Pointer[T] { + @buffer as Int + (index * _INKO.size_of_type_parameter(T)) as Pointer[T] + } + + fn get_unchecked(index: Int) -> ref T { + let val = read_from(index) + let ret = ref val + + _INKO.moved(val) + ret + } + + fn to_buffer_index(index: Int) -> Int { + @head.wrapping_add(index) % @capacity + } + + fn mut handle_increase(old_capacity: Int) { + if @head <= (old_capacity - @size) { + return + # Nothing to do for this case. + } + + let head_size = old_capacity - @head + let tail_size = @size - head_size + + if head_size > tail_size and @capacity - old_capacity >= tail_size { + alloc.copy( + from: @buffer, + to: ptr.add(@buffer, old_capacity), + size: tail_size, + ) + return + } + + let new_head = @capacity - head_size + + alloc.copy( + from: ptr.add(@buffer, @head), + to: ptr.add(@buffer, new_head), + size: head_size, + ) + @head = new_head + } +} + +impl Deque if T: mut { + # Returns an iterator that yields mutable borrows of the values in `self`. + # + # # Examples + # + # ```inko + # import std.deque (Deque) + # + # let q = Deque.new + # + # q.push_back(10) + # q.push_front(20) + # q.push_back(30) + # + # q.iter_mut.to_array # => [20, 10, 30] + # ``` + fn pub mut iter_mut -> Stream[mut T] { + indexes.map(fn (i) { get_unchecked_mut(i) }) + } + + fn mut get_unchecked_mut(index: Int) -> mut T { + let val = read_from(index) + let ret = mut val + + _INKO.moved(val) + ret + } +} + +impl Drop for Deque { + fn mut drop { + clear + alloc.free(@buffer) + } +} + +# An iterator that moves values out of a `Deque`. +# +# When this iterator is dropped, any values not yet moved out of the `Deque` are +# dropped. +class pub IntoIter[T] { + let @deque: Deque[T] + let @indexes: Stream[Int] +} + +impl Iter[T] for IntoIter { + fn pub mut next -> Option[T] { + @indexes.next.map(fn (i) { @deque.read_from(i) }) + } +} + +impl Drop for IntoIter { + fn mut drop { + loop { + match @indexes.next { + case Some(i) -> @deque.read_from(i) + case _ -> break + } + } + + # This is needed to prevent `Deque.clear` from dropping the data again. + @deque.size = 0 + } +}