363 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			363 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
--- A double queue.
 | 
						|
-- Taken from ***Programming in Lua*** [Queues and Double Queues](http://www.lua.org/pil/11.4.html)
 | 
						|
-- and modified to not allow nil values, and returns nil if @{pop_first} or @{pop_last} is used when the queue is empty.
 | 
						|
-- @module Misc.Queue
 | 
						|
-- @usage local Queue = require('__stdlib__/stdlib/misc/queue')
 | 
						|
-- local q = Queue() -- create a new empty queue
 | 
						|
-- q('my value') -- push a value onto the queue
 | 
						|
-- q() -- pop the last value off the queue
 | 
						|
-- game.print(#q) -- print the number of items in the queue
 | 
						|
 | 
						|
local Queue = {
 | 
						|
    __class = 'Queue',
 | 
						|
    __index = require('__stdlib__/stdlib/core')
 | 
						|
}
 | 
						|
setmetatable(Queue, Queue)
 | 
						|
 | 
						|
local table = require('__stdlib__/stdlib/utils/table')
 | 
						|
local t_size = table_size
 | 
						|
 | 
						|
local Inspect = require('__stdlib__/stdlib/vendor/inspect')
 | 
						|
 | 
						|
local meta = {}
 | 
						|
 | 
						|
function Queue.__call(_, ...)
 | 
						|
    local queue = { first = 1, last = 0, objects = {} }
 | 
						|
    setmetatable(queue, meta)
 | 
						|
    for _, push in pairs { ... } do
 | 
						|
        queue(push)
 | 
						|
    end
 | 
						|
    return queue
 | 
						|
end
 | 
						|
 | 
						|
--- Constructs a new Queue object.
 | 
						|
-- @param ... mixed, values to push into the queue
 | 
						|
-- @treturn @{Queue} a new queue
 | 
						|
function Queue.new(...)
 | 
						|
    return Queue.__call(nil, ...)
 | 
						|
end
 | 
						|
 | 
						|
--- Load global.queue or queues during on_load, as metatables are not persisted.
 | 
						|
-- <p>This is only needed if you are using the queue as an object and storing it in global.
 | 
						|
-- @tparam table queue (<span class="types">@{Queue}</span>,...)
 | 
						|
-- @usage global.myqueue1 = Queue.new()
 | 
						|
-- script.on_load(function() Queue.load(global.myqueue))
 | 
						|
function Queue.load(queue)
 | 
						|
    if type(queue) == 'table' and queue.first then
 | 
						|
        return setmetatable(queue, meta)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
--- Push a new element to the front of the queue.
 | 
						|
-- @tparam Queue queue the queue to push an element to
 | 
						|
-- @tparam Mixed value the element to push
 | 
						|
function Queue.push_first(queue, ...)
 | 
						|
    for _, value in pairs { ... } do
 | 
						|
        queue.first = queue.first - 1
 | 
						|
        queue.objects[queue.first] = value
 | 
						|
    end
 | 
						|
    return queue
 | 
						|
end
 | 
						|
 | 
						|
--- Push a new element to the back of the queue.
 | 
						|
-- @tparam Queue queue the queue to push an element to
 | 
						|
-- @tparam Mixed ... the element(s) to push
 | 
						|
function Queue.push_last(queue, ...)
 | 
						|
    for _, value in pairs { ... } do
 | 
						|
        queue.last = queue.last + 1
 | 
						|
        queue.objects[queue.last] = value
 | 
						|
    end
 | 
						|
    return queue
 | 
						|
end
 | 
						|
 | 
						|
--- Shortcut for @{Queue.push_last}
 | 
						|
-- @function Queue.push
 | 
						|
Queue.push = Queue.push_last
 | 
						|
 | 
						|
--- Push a new element to a specific location of the queue.
 | 
						|
-- @tparam Queue queue the queue to push an element to
 | 
						|
-- @tparam number index the index to push to.
 | 
						|
-- @tparam Mixed value the element to push.
 | 
						|
function Queue.push_at(queue, index, value)
 | 
						|
    if index < queue.first then
 | 
						|
        return Queue.push_first(queue, value)
 | 
						|
    elseif index > queue.last then
 | 
						|
        return Queue.push_last(queue, value)
 | 
						|
    else
 | 
						|
        table.insert(queue.objects, index, value)
 | 
						|
        queue.last = queue.last + 1
 | 
						|
    end
 | 
						|
    return queue
 | 
						|
end
 | 
						|
 | 
						|
function Queue.wrapper(self, func_name, ...)
 | 
						|
    if Queue[func_name] then
 | 
						|
        Queue[func_name](self, ...)
 | 
						|
    end
 | 
						|
    return self
 | 
						|
end
 | 
						|
 | 
						|
--- Retrieve the element at the front of the queue and remove it from the queue.
 | 
						|
-- @tparam Queue queue the queue to retrieve the element from
 | 
						|
-- @treturn Mixed value the element at the front of the queue
 | 
						|
function Queue.pop_first(queue)
 | 
						|
    if Queue.is_empty(queue) then
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
    local first = queue.first
 | 
						|
    local value = queue.objects[first]
 | 
						|
    queue.objects[first] = nil -- to allow garbage collection
 | 
						|
    queue.first = first + 1
 | 
						|
    return value
 | 
						|
end
 | 
						|
 | 
						|
--- Shortcut for @{Queue.pop_first}
 | 
						|
-- @function Queue.pop
 | 
						|
Queue.pop = Queue.pop_first
 | 
						|
 | 
						|
local function remove(queue, index)
 | 
						|
    local ret = queue.objects[index]
 | 
						|
    if ret ~= nil then
 | 
						|
        for i = index + 1, queue.last do
 | 
						|
            queue.objects[i - 1] = queue.objects[i]
 | 
						|
        end
 | 
						|
        queue.objects[queue.last] = nil
 | 
						|
        queue.last = queue.last - 1
 | 
						|
    end
 | 
						|
    return ret
 | 
						|
end
 | 
						|
 | 
						|
--- Pop an element at a specific location of the queue.
 | 
						|
-- @tparam Queue queue the queue to push an element to
 | 
						|
-- @tparam number index the index to push to.
 | 
						|
-- @treturn Mixed value the popped element.
 | 
						|
function Queue.pop_at(queue, index)
 | 
						|
    return remove(queue, index)
 | 
						|
end
 | 
						|
 | 
						|
--- Peek at an element in the queue without disturbing the queue.
 | 
						|
-- @tparam Queue queue the queue to peek at
 | 
						|
-- @tparam number index the index in the queue to peek at
 | 
						|
-- @treturn Mixed the value of the peeked element
 | 
						|
function Queue.peek_at(queue, index)
 | 
						|
    return queue.objects[index]
 | 
						|
end
 | 
						|
 | 
						|
--- Return the element at the front of the queue and remove it from the queue.
 | 
						|
-- @tparam Queue queue the queue to retrieve the element from
 | 
						|
-- @treturn Mixed the element at the front of the queue
 | 
						|
function Queue.peek_first(queue)
 | 
						|
    return queue.objects[queue.first]
 | 
						|
end
 | 
						|
 | 
						|
--- Shortcut for @{Queue.peek_first}
 | 
						|
-- @function Queue.peek
 | 
						|
Queue.peek = Queue.peek_first
 | 
						|
 | 
						|
--- Retrieve the element at the back of the queue and remove it from the queue.
 | 
						|
-- @tparam Queue queue the queue to retrieve the element from
 | 
						|
-- @treturn Mixed the element at the back of the queue
 | 
						|
function Queue.pop_last(queue)
 | 
						|
    if queue.is_empty(queue) then
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
 | 
						|
    local last = queue.last
 | 
						|
    local value = queue.objects[last]
 | 
						|
    queue.objects[last] = nil -- to allow garbage collection
 | 
						|
    queue.last = last - 1
 | 
						|
    return value
 | 
						|
end
 | 
						|
 | 
						|
--- Return the element at the back of the queue.
 | 
						|
-- @tparam Queue queue the queue to retrieve the element from
 | 
						|
-- @treturn Mixed the element at the back of the queue
 | 
						|
function Queue.peek_last(queue)
 | 
						|
    return queue.objects[queue.last]
 | 
						|
end
 | 
						|
 | 
						|
--- Returns the popped value and pushes back into the queue.
 | 
						|
-- @tparam Queue queue the queue
 | 
						|
-- @return Mixed the value that was popped.
 | 
						|
function Queue.pop_and_push(queue)
 | 
						|
    local ret = queue.pop(queue)
 | 
						|
    queue.push(queue, ret)
 | 
						|
    return ret
 | 
						|
end
 | 
						|
 | 
						|
--- Returns the queue after popping the last element and pushing it to the top.
 | 
						|
-- @tparam Queue queue the queue
 | 
						|
-- @treturn @{Queue} the queue
 | 
						|
function Queue.cycle(queue)
 | 
						|
    return queue.push(queue, queue.pop(queue))
 | 
						|
end
 | 
						|
 | 
						|
--- Gets the first index which matches the stored data. does not compare inside tables.
 | 
						|
function Queue.find(queue, find)
 | 
						|
    for i, v in pairs(queue) do
 | 
						|
        if v == find then
 | 
						|
            return i
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function _sort_func(a, b)
 | 
						|
    local lhs = type(a) == 'table' and '{' or tostring(a)
 | 
						|
    local rhs = type(b) == 'table' and '{' or tostring(b)
 | 
						|
    return lhs < rhs
 | 
						|
end
 | 
						|
 | 
						|
--- sort and reorder the queue
 | 
						|
function Queue.sort(queue, func)
 | 
						|
    local sorted = {}
 | 
						|
    for _, v in pairs(queue) do
 | 
						|
        if v ~= nil then
 | 
						|
            sorted[#sorted + 1] = v
 | 
						|
        end
 | 
						|
    end
 | 
						|
    table.sort(sorted, func or _sort_func)
 | 
						|
    queue.objects = sorted
 | 
						|
    queue.first, queue.last = 1, #queue.objects
 | 
						|
    return queue
 | 
						|
end
 | 
						|
 | 
						|
--- Returns true if the given queue is empty.
 | 
						|
-- @tparam Queue queue the queue to check
 | 
						|
-- @treturn boolean true if empty, false otherwise
 | 
						|
function Queue.is_empty(queue)
 | 
						|
    return queue.first > queue.last
 | 
						|
end
 | 
						|
 | 
						|
--- Returns the number of items in the queue.
 | 
						|
-- @tparam Queue queue the queue to check
 | 
						|
-- @treturn number the number of items in the queue
 | 
						|
function Queue.size(queue)
 | 
						|
    return t_size(queue.objects)
 | 
						|
end
 | 
						|
 | 
						|
--- Shortcut for @{Queue.size}
 | 
						|
-- @function Queue.count
 | 
						|
Queue.count = Queue.size
 | 
						|
 | 
						|
--- Return the next element in the queue
 | 
						|
-- @tparam Queue queue the queue to check
 | 
						|
-- @tparam number|nil index if nil return the first value, else return the next index value
 | 
						|
-- @tparam boolean pop pop the value off the queue
 | 
						|
-- @treturn number|nil the index
 | 
						|
-- @treturn Mixed|nil the value at queue index
 | 
						|
function Queue.next(queue, index, pop)
 | 
						|
    index = not index and queue.first or index + (pop and 0 or 1)
 | 
						|
    for i = index, queue.last do
 | 
						|
        local v = queue.objects[i]
 | 
						|
        if v ~= nil then
 | 
						|
            return i, pop and Queue.pop_at(queue, i) or v
 | 
						|
        end
 | 
						|
    end
 | 
						|
    return nil, nil
 | 
						|
end
 | 
						|
 | 
						|
--- Return the previous element in the queue
 | 
						|
-- @tparam Queue queue the queue to check
 | 
						|
-- @tparam number|nil index if nil return the last value, else return the previous index value
 | 
						|
-- @tparam boolean pop pop the value off the queue
 | 
						|
-- @treturn number|nil the index
 | 
						|
-- @treturn Mixed|nil the value at queue index
 | 
						|
function Queue.rnext(queue, index, pop)
 | 
						|
    -- next returns index of next or nil and data,
 | 
						|
    index = not index and queue.last or (index < queue.first and queue.first or index) - 1
 | 
						|
    for i = index, queue.first, -1 do
 | 
						|
        local v = queue.objects[i]
 | 
						|
        if v ~= nil then
 | 
						|
            return i, pop and Queue.pop_at(queue, i) or v
 | 
						|
        end
 | 
						|
    end
 | 
						|
    return nil, nil
 | 
						|
end
 | 
						|
 | 
						|
local function next_pop(queue, index)
 | 
						|
    return Queue.next(queue, index, true)
 | 
						|
end
 | 
						|
 | 
						|
local function rnext_pop(queue, index)
 | 
						|
    return Queue.rnext(queue, index, true)
 | 
						|
end
 | 
						|
 | 
						|
--- Iterate the queue forward
 | 
						|
function Queue.pairs(queue, pop)
 | 
						|
    return pop and next_pop or Queue.next, queue, nil
 | 
						|
end
 | 
						|
 | 
						|
--- Iterate the queue backwards
 | 
						|
function Queue.rpairs(queue, pop)
 | 
						|
    return pop and rnext_pop or Queue.rnext, queue, nil
 | 
						|
end
 | 
						|
 | 
						|
do
 | 
						|
    meta.__class = 'queue'
 | 
						|
    meta.__len = Queue.size
 | 
						|
    meta.__unm = Queue.pop
 | 
						|
    meta.__parent = Queue
 | 
						|
    meta.__debugline = [[<Queue>{[}first={first},last={last}{]}]]
 | 
						|
 | 
						|
    -- Allows queue[3] to return the item at queue.objects[3]
 | 
						|
    meta.__index = function(self, k)
 | 
						|
        if type(k) == 'number' then
 | 
						|
            return self:peek_at(k)
 | 
						|
        else
 | 
						|
            local v = rawget(self, k)
 | 
						|
            if v == nil then
 | 
						|
                return Queue[k]
 | 
						|
            end
 | 
						|
            return v
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    meta.__newindex = function(self, k, v)
 | 
						|
        if type(k) == 'number' then
 | 
						|
            if v ~= nil then
 | 
						|
                self:push_at(k, v)
 | 
						|
            else
 | 
						|
                error('Attempt to modify Queue structure')
 | 
						|
            end
 | 
						|
        else
 | 
						|
            rawset(self, k, v)
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    -- Allows queue() to pop_first and queue(data) to push_last
 | 
						|
    meta.__call = function(self, ...)
 | 
						|
        if ... then
 | 
						|
            return self:push(...)
 | 
						|
        else
 | 
						|
            return self:pop()
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    meta.__tostring = function(self)
 | 
						|
        return Inspect({ first = self.first, last = self.last, objects = self.objects }, { arraykeys = true })
 | 
						|
    end
 | 
						|
 | 
						|
    meta.__add = function(queue1, queue2)
 | 
						|
        local new = Queue.new()
 | 
						|
        local lhs = getmetatable(queue1) == meta and true
 | 
						|
        local rhs = getmetatable(queue2) == meta and true
 | 
						|
        if lhs then
 | 
						|
            for _, v in pairs(queue1.objects) do
 | 
						|
                new:push(v)
 | 
						|
            end
 | 
						|
        else
 | 
						|
            new:push(queue1)
 | 
						|
        end
 | 
						|
        if rhs then
 | 
						|
            for _, v in pairs(queue2.objects) do
 | 
						|
                new:push(v)
 | 
						|
            end
 | 
						|
        else
 | 
						|
            new:push(queue2)
 | 
						|
        end
 | 
						|
        return new
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
return Queue
 |