Skip to content

Commit

Permalink
feat(stdlib): Mutable queue and stack
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-snezhko committed Nov 12, 2022
1 parent 0fe8aa6 commit 33cb48c
Show file tree
Hide file tree
Showing 7 changed files with 904 additions and 0 deletions.
79 changes: 79 additions & 0 deletions compiler/test/stdlib/mutablequeue.test.gr
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import Queue from "mutablequeue"
import List from "list"

let empty = Queue.make()
assert Queue.isEmpty(empty)
assert Queue.size(empty) == 0
assert Queue.peek(empty) == None
assert Queue.pop(empty) == None
assert Queue.size(empty) == 0

let queue = Queue.make()
Queue.push(1, queue)
Queue.push(2, queue)
Queue.push(3, queue)

assert !Queue.isEmpty(queue)
assert Queue.size(queue) == 3
assert Queue.peek(queue) == Some(1)

assert Queue.pop(queue) == Some(1)
assert Queue.peek(queue) == Some(2)
assert Queue.size(queue) == 2

Queue.push(4, queue)
assert Queue.size(queue) == 3
assert Queue.peek(queue) == Some(2)
let copy = Queue.copy(queue)
Queue.pop(copy)
assert Queue.size(copy) == 2
assert Queue.size(queue) == 3
Queue.clear(queue)
assert Queue.size(queue) == 0
assert Queue.peek(queue) == None

// test that expansion works
let queue = Queue.makeSized(3)
Queue.push(0, queue)
Queue.push(1, queue)
Queue.push(2, queue)
Queue.push(3, queue)
assert Queue.pop(queue) == Some(0)
assert Queue.pop(queue) == Some(1)
assert Queue.pop(queue) == Some(2)
assert Queue.pop(queue) == Some(3)
assert Queue.pop(queue) == None

// test that the "circular" behavior of the circular queue works as expected
let queue = Queue.makeSized(4)
let push = x => () => Queue.push(x, queue)
let pop = () => ignore(Queue.pop(queue))
let actions = [
push(1),
push(2),
push(3),
push(4),
pop,
pop,
pop,
push(5),
push(6),
pop,
pop,
push(7),
push(8),
push(9),
]
List.forEach(action => action(), actions)

assert Queue.size(queue) == 4
assert Queue.peek(queue) == Some(6)

Queue.push(10, queue)
assert Queue.size(queue) == 5
assert Queue.pop(queue) == Some(6)
assert Queue.pop(queue) == Some(7)
assert Queue.pop(queue) == Some(8)
assert Queue.pop(queue) == Some(9)
assert Queue.pop(queue) == Some(10)
assert Queue.pop(queue) == None
47 changes: 47 additions & 0 deletions compiler/test/stdlib/mutablestack.test.gr
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Stack from "mutablestack"

let empty = Stack.make()
assert Stack.isEmpty(empty)
assert Stack.size(empty) == 0
assert Stack.peek(empty) == None
assert Stack.pop(empty) == None
assert Stack.size(empty) == 0

let stack = Stack.make()
Stack.push(1, stack)
Stack.push(2, stack)
Stack.push(3, stack)

assert !Stack.isEmpty(stack)
assert Stack.size(stack) == 3
assert Stack.peek(stack) == Some(3)

assert Stack.pop(stack) == Some(3)
assert Stack.peek(stack) == Some(2)
assert Stack.size(stack) == 2

Stack.push(4, stack)
assert Stack.size(stack) == 3
assert Stack.peek(stack) == Some(4)
let copy = Stack.copy(stack)
Stack.pop(copy)
assert Stack.size(copy) == 2
assert Stack.size(stack) == 3
Stack.clear(stack)
assert Stack.size(stack) == 0
assert Stack.peek(stack) == None

let stack = Stack.makeSized(4)

Stack.push(1, stack)
Stack.push(2, stack)
Stack.push(3, stack)
Stack.push(4, stack)
Stack.push(5, stack)
assert Stack.size(stack) == 5
assert Stack.pop(stack) == Some(5)
assert Stack.pop(stack) == Some(4)
assert Stack.pop(stack) == Some(3)
assert Stack.pop(stack) == Some(2)
assert Stack.pop(stack) == Some(1)
assert Stack.pop(stack) == None
2 changes: 2 additions & 0 deletions compiler/test/suites/stdlib.re
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,14 @@ describe("stdlib", ({test, testSkip}) => {
assertStdlib("option.test");
assertStdlib("pervasives.test");
assertStdlib("queue.test");
assertStdlib("mutablequeue.test");
assertStdlib("range.test");
assertStdlib("result.test");
assertStdlib("set.test");
assertStdlib("immutableset.test");
assertStdlib("regex.test");
assertStdlib("stack.test");
assertStdlib("mutablestack.test");
assertStdlib("priorityqueue.test");
assertStdlib("immutablepriorityqueue.test");
assertStdlib("string.test");
Expand Down
162 changes: 162 additions & 0 deletions stdlib/mutablequeue.gr
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/**
* @module MutableQueue: A mutable queue implementation. A queue is a FIFO (first-in-first-out) data structure where new values are added to the end and retrieved or removed from the beginning.
* @example import MutableQueue from "mutablequeue"
* @since v0.5.5
*/

import Array from "array"

// a circular array-based queue implementation

/**
* @section Types: Type declarations included in the MutableQueue module.
*/

// tail points to where the next element should be inserted

/**
* A mutable FIFO (first-in-first-out) data structure.
*/
record Queue<a> {
mut size: Number,
mut array: Array<Option<a>>,
mut headI: Number,
mut tailI: Number,
}

/**
* Creates a new queue with an initial storage of the given size. As values are
* added or removed, the internal storage may grow or shrink. Generally, you
* won’t need to care about the storage size of your map and can use
* `MutableQueue.make()` instead.
*
* @param size: The initial storage size of the queue
* @returns An empty queue
*
* @since v0.5.5
*/
export let makeSized = size => {
{ size: 0, array: Array.make(size, None), headI: 0, tailI: 0 }
}

/**
* Creates a new queue.
*
* @returns An empty queue
*
* @since v0.5.5
*/
export let make = () => {
makeSized(16)
}

/**
* Checks if the given queue contains no items.
*
* @param queue: The queue to check
* @returns `true` if the queue has no items or `false` otherwise
*
* @since v0.5.5
*/
export let isEmpty = queue => queue.size == 0

/**
* Computes the size of the input queue.
*
* @param queue: The queue to inspect
* @returns The count of the items in the queue
*
* @since v0.5.5
*/
export let size = queue => queue.size

/**
* Provides the value at the beginning of the queue, if it exists.
*
* @param queue: The queue to inspect
* @returns `Some(value)` containing the value at the beginning of the queue or `None` otherwise.
*
* @since v0.5.5
*/
export let peek = queue => {
if (queue.size == 0) None else queue.array[queue.headI]
}

/**
* Adds a new item to the end of the queue.
*
* @param value: The item to be added
* @param queue: The queue being updated
*
* @since v0.5.5
*/
export let push = (value, queue) => {
let arrLen = Array.length(queue.array)
// expand the array if needed
if (queue.size == arrLen) {
let newArray = Array.make(arrLen * 2, None)

newArray[0] = queue.array[queue.headI]
let mut insertI = 1
let mut currI = (queue.headI + 1) % arrLen
while (currI != queue.tailI) {
newArray[insertI] = queue.array[currI]
insertI += 1
currI = (currI + 1) % arrLen
}

queue.headI = 0
queue.tailI = arrLen
queue.array = newArray
}
queue.array[queue.tailI] = Some(value)
queue.tailI = (queue.tailI + 1) % Array.length(queue.array)
queue.size += 1
}

/**
* Removes the item at the beginning of the queue.
*
* @param queue: The queue being updated
* @returns The element removed from the queue
*
* @since v0.5.5
*/
export let pop = queue => {
if (queue.size == 0) {
None
} else {
let elem = queue.array[queue.headI]
queue.array[queue.headI] = None
queue.headI = (queue.headI + 1) % Array.length(queue.array)
queue.size -= 1
elem
}
}

/**
* Clears the queue by removing all of its elements
*
* @param queue: The queue to clear
*
* @since v0.5.5
*/
export let clear = queue => {
queue.size = 0
Array.fill(None, queue.array)
queue.headI = 0
queue.tailI = 0
}

/**
* Produces a shallow copy of the input queue.
*
* @param queue: The queue to copy
* @returns A new queue containing the elements from the input
*
* @since v0.5.5
*/
export let copy = queue => {
let { size, array, headI, tailI } = queue
{ size, array: Array.copy(array), headI, tailI }
}
Loading

0 comments on commit 33cb48c

Please sign in to comment.