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
6 changes: 3 additions & 3 deletions wurst/data/ArrayList.wurst
Original file line number Diff line number Diff line change
Expand Up @@ -745,15 +745,15 @@ public interface Comparator<T:>
// SPECIALIZED SORT FUNCTIONS
// ============================================================================

constant Comparator<int> intComparator = (i1, i2) -> i1 < i2 ? -1 : (i1 > i2 ? 1 : 0)
public constant Comparator<int> intComparator = (i1, i2) -> i1 < i2 ? -1 : (i1 > i2 ? 1 : 0)
public function ArrayList<int>.sort()
this.sortWith(intComparator)

constant Comparator<real> realComparator = (r1, r2) -> r1 < r2 ? -1 : (r1 > r2 ? 1 : 0)
public constant Comparator<real> realComparator = (r1, r2) -> r1 < r2 ? -1 : (r1 > r2 ? 1 : 0)
public function ArrayList<real>.sort()
this.sortWith(realComparator)

constant Comparator<string> stringComparator = (s1, s2) -> stringCompare(s1, s2)
public constant Comparator<string> stringComparator = (s1, s2) -> stringCompare(s1, s2)
public function ArrayList<string>.sort()
this.sortWith(stringComparator)

Expand Down
117 changes: 117 additions & 0 deletions wurst/data/PriorityQueue.wurst
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package PriorityQueue

import ArrayList

/*
* Usage:
* let queue = new PriorityQueue<int>(intComparator)
* queue.add(5)
* queue.add(1)
* let next = queue.poll() // 1
* destroy queue
*/
/**
* A binary heap priority queue.
*
* The comparator defines priority: a negative result means that the first
* value has higher priority, so ArrayList.intComparator creates a min-heap.
* Reverse the comparator to create a max-heap. Equal-priority elements are
* not stable.
*
* Adding and removing the highest-priority element are O(log n). Peeking is
* O(1). The queue does not support changing an element's priority in place;
* remove and re-add it after changing any fields used by the comparator.
*/
public class PriorityQueue<T:>
private ArrayList<T> values
private Comparator<T> comparator

construct(Comparator<T> comparator)
this(comparator, 16)

construct(Comparator<T> comparator, int initialCapacity)
this.comparator = comparator
values = new ArrayList<T>(initialCapacity > 0 ? initialCapacity : 16)

/** Adds an element to the queue. */
function add(T value)
values.add(value)
siftUp(values.size() - 1)

/** Alias for add(). */
function offer(T value)
add(value)

/** Returns the highest-priority element, or null if empty. */
function peek() returns T
if values.size() == 0
return null
return values.get(0)

/** Removes and returns the highest-priority element, or null if empty. */
function poll() returns T
if values.size() == 0
return null

let result = values.get(0)
let lastIndex = values.size() - 1
if lastIndex == 0
values.removeAtUnordered(0)
return result

let last = values.removeAtUnordered(lastIndex)
values.set(0, last)
siftDown(0)
return result

/** Alias for poll(). */
function remove() returns T
return poll()

/** Returns the number of queued elements. */
function size() returns int
return values.size()

/** Returns whether the queue is empty. */
function isEmpty() returns boolean
return values.size() == 0

/** Removes all elements while retaining the backing storage. */
function clear()
values.clear()

private function isHigherPriority(int left, int right) returns boolean
return comparator.compare(values.get(left), values.get(right)) < 0

private function siftUp(int index)
var current = index
while current > 0
let parent = (current - 1) div 2
if not isHigherPriority(current, parent)
return
swap(current, parent)
current = parent

private function siftDown(int index)
var current = index
let count = values.size()
while true
let left = current * 2 + 1
if left >= count
return
let right = left + 1
var child = left
if right < count and isHigherPriority(right, left)
child = right
if not isHigherPriority(child, current)
return
swap(current, child)
current = child

private function swap(int left, int right)
let value = values.get(left)
values.set(left, values.get(right))
values.set(right, value)

ondestroy
destroy values
63 changes: 63 additions & 0 deletions wurst/data/PriorityQueueTests.wurst
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package PriorityQueueTests

import ArrayList
import PriorityQueue

constant Comparator<int> maxIntComparator = (a, b) -> a > b ? -1 : (a < b ? 1 : 0)

@Test
function priorityQueueReturnsSmallestFirst()
let queue = new PriorityQueue<int>(intComparator)
queue.add(7)
queue.add(2)
queue.add(5)
queue.add(2)

queue.peek().assertEquals(2)
queue.size().assertEquals(4)
queue.poll().assertEquals(2)
queue.poll().assertEquals(2)
queue.poll().assertEquals(5)
queue.poll().assertEquals(7)
queue.isEmpty().assertTrue()
destroy queue

@Test
function priorityQueueSupportsMaxHeapComparator()
let queue = new PriorityQueue<int>(maxIntComparator)
queue.offer(3)
queue.offer(9)
queue.offer(1)

queue.remove().assertEquals(9)
queue.remove().assertEquals(3)
queue.remove().assertEquals(1)
destroy queue

@Test
function priorityQueueClearRetainsUsability()
let queue = new PriorityQueue<int>(intComparator, 2)
queue.add(4)
queue.add(1)
queue.clear()
queue.isEmpty().assertTrue()
queue.add(6)
queue.peek().assertEquals(6)
queue.size().assertEquals(1)
destroy queue

@Test
function priorityQueueHandlesEmptyAndGrowth()
let queue = new PriorityQueue<int>(intComparator, 1)
queue.isEmpty().assertTrue()
queue.size().assertEquals(0)
queue.peek()
queue.poll()

for i = 0 to 31
queue.add((i * 17) mod 32)
queue.size().assertEquals(32)
for expected = 0 to 31
queue.poll().assertEquals(expected)
queue.isEmpty().assertTrue()
destroy queue
155 changes: 155 additions & 0 deletions wurst/data/SparseSet.wurst
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package SparseSet

import ArrayList
import ErrorHandling
import Table
import public TypeCasting

/** Supplies the stable, unique integer key used by a SparseSet. */
public interface SparseSetKey<T:>
function getKey(T value) returns int

/**
* A set with O(1) membership checks, insertion, and unordered removal.
*
* Elements are stored in a dense typed array. The sparse index maps the key
* supplied by SparseSetKey to the element's dense index. Keys must be unique
* and stable for the lifetime of an element in the set. A key may be reused
* after its old value is gone; add() validates the stored value before
* accepting the new one.
*
* Removal swaps the last element into the removed element's slot, so dense
* iteration order is not preserved.
*/
public class SparseSet<T:>
private ArrayList<T> dense
private ArrayList<int> denseKeys
private Table sparse
private SparseSetKey<T> keyProvider

construct(SparseSetKey<T> keyProvider)
this.keyProvider = keyProvider
dense = new ArrayList<T>()
denseKeys = new ArrayList<int>()
sparse = new Table()

/** Adds an element and returns whether it was newly inserted. */
function add(T value) returns boolean
let key = keyProvider.getKey(value)
let existingIndex = indexForKey(key)
if existingIndex >= 0
if dense.get(existingIndex) == value
return false
// The key was reused by a different value. This is valid for
// handles whose old value has been destroyed or deindexed.
removeAt(existingIndex)

dense.add(value)
denseKeys.add(key)
sparse.saveInt(key, dense.size())
return true

/** Adds every element from another set. */
function addAll(SparseSet<T> other)
for i = 0 to other.size() - 1
add(other.get(i))

/** Returns whether the set contains the given element. */
function has(T value) returns boolean
return indexOf(value) >= 0

/** Returns whether the set contains an element under the given key. */
function hasKey(int key) returns boolean
return sparse.hasInt(key)

/** Returns the dense index of an element, or -1 when it is absent. */
function indexOf(T value) returns int
let key = keyProvider.getKey(value)
let index = indexForKey(key)
if index < 0 or dense.get(index) != value
return -1
return index

/** Removes an element and returns whether it was present. */
function remove(T value) returns boolean
let index = indexOf(value)
if index < 0
return false
removeAt(index)
return true

/** Removes the element at a dense index without preserving order. */
function removeAt(int index) returns T
if index < 0 or index >= dense.size()
error("SparseSet: Index out of bounds: " + index.toString())

let lastIndex = dense.size() - 1
let removed = dense.get(index)
let removedKey = denseKeys.get(index)

if index != lastIndex
let moved = dense.get(lastIndex)
let movedKey = denseKeys.get(lastIndex)
dense.set(index, moved)
denseKeys.set(index, movedKey)
sparse.saveInt(movedKey, index + 1)

dense.removeAtUnordered(lastIndex)
denseKeys.removeAtUnordered(lastIndex)
sparse.removeInt(removedKey)
return removed

/** Removes all elements while retaining the set object. */
function clear()
dense.clear()
denseKeys.clear()
sparse.flush()

/** Returns the number of elements in the set. */
function size() returns int
return dense.size()

/** Returns whether the set contains no elements. */
function isEmpty() returns boolean
return dense.isEmpty()

/** Returns an element by its dense index. */
function get(int index) returns T
return dense.get(index)

/** Returns an element by sparse key, or null when the key is absent. */
function getByKey(int key) returns T
if not hasKey(key)
return null
return dense.get(indexForKey(key))

/** Returns a shallow copy of this set. */
function copy() returns SparseSet<T>
let result = new SparseSet<T>(keyProvider)
result.addAll(this)
return result

private function indexForKey(int key) returns int
if not sparse.hasInt(key)
return -1
return sparse.loadInt(key) - 1

ondestroy
destroy dense
destroy denseKeys
destroy sparse

/**
* Key provider for unit sets.
*
* This deliberately uses the native handle identity rather than UnitIndexer
* IDs. That matches native groups: membership is not automatically removed
* when a unit is deindexed. SparseSet validates the stored unit when a key is
* reused, so a new unit cannot silently inherit stale membership.
*/
public class UnitSparseSetKey implements SparseSetKey<unit>
override function getKey(unit value) returns int
return value.getTCHandleId()

/** Reusable key provider for SparseSet<unit>. */
public constant SparseSetKey<unit> UNIT_SPARSE_SET_KEY = new UnitSparseSetKey()
Loading
Loading