--[[ Copyright (c) 2014, Robert 'Bobby' Zenz All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --]] --- A simple list that keeps the order in which the items are added to it. -- -- It is a thin wrapper around a simple, number indexed table providing -- various convenience methods. List = { --- An accept function that only accepts non nil values. -- -- @param value The value that is checked. -- @return true if the given value is not nil. ACCEPT_NON_NIL = function(value) return value ~= nil end, --- An accept function that only accepts non empty string values. -- -- @param value The value that is checked. -- @return true if the given value is a not empty string value. ACCEPT_NON_EMPTY_STRING = function(value) return type(value) == "string" and #value > 0 end } --- Creates a new instance of List. -- -- @param ... Optional. A list of values to add. -- @return A new instance of List. function List:new(...) local instance = { counter = 1 } setmetatable(instance, self) self.__index = self instance:add(...) return instance end --- Adds the given items to the list. -- -- @param ... The items to add. function List:add(...) for index, value in ipairs({...}) do self[self.counter] = value self.counter = self.counter + 1 end end --- Adds the given List to the list. -- -- @param ... The Lists to add. function List:add_list(...) for index, list in ipairs({...}) do list:foreach(function(value, value_index) self:add(value) end) end end --- Clears all entries from the list. function List:clear() for index = 1, self.counter - 1, 1 do self[index] = nil end self.counter = 1 end --- Checks if this list contains the given item. -- -- @param item The item to search for. -- @return true if this list contains the given item. function List:contains(item) return self:index(item) >= 0 end --- Culls/removes all duplicates from this list. -- -- @param comparator Optional. The comparator to be used. Accepts two values -- and returns true if they can be considered equal. Defaults -- to testing the identity. function List:cull_duplicates(comparator) comparator = comparator or function(a, b) return a == b end -- Find duplicates. for index = 1, self.counter - 1, 1 do local value = self[index] if value ~= nil then for sec_index = index + 1, self.counter - 1, 1 do if comparator(self[sec_index], value) then self[sec_index] = nil end end end end -- Compact the list. local last_index = 1 for index = 1, self.counter - 1, 1 do local value = self[index] if value ~= nil then self[last_index] = value last_index = last_index + 1 end end -- Remove trailing elements. for index = last_index, self.counter - 1, 1 do self[index] = nil end self.counter = last_index end --- Iterates over all items in the list and invokes the given action on them. -- -- @param action The function to invoke on the item, the first parameter will be -- the item itself, the second (optional) parameter is the index. -- The function can return true to stop iterating over the items. function List:foreach(action) for index = 1, self.counter - 1, 1 do if action(self[index], index) == true then return end end end --- Gets the item at the given index. Returns nil if there is no item. -- Note that there is no different between "no item" and "nil is the item", -- in both cases nil is returned. -- -- @param index The index of the item to get. -- @return The item at the given index. nil if there is no item. function List:get(index) return self[index] end --- Gets the first value in this List that is accepted by the given function. -- -- @param accept Optional. The function to accept values. Accepts the value and -- returns a boolean, true if the value is accepted. If nil -- the first value in the list will be returned. -- @return The first accepted value, or if none was accepted, nil. function List:get_first(accept) for index = 1, self.counter - 1, 1 do if accept == nil or accept(self[index]) then return self[index] end end return nil end --- Gets the last value in this List that is accepted by the given function. -- -- @param accept Optional. The function to accept values. Accepts the value and -- returns a boolean, true if the value is accepted. If nil -- the last value in the list will be returned. -- @return The last accepted value, or if none was accepted, nil. function List:get_last(accept) local value = nil for index = 1, self.counter - 1, 1 do if accept == nil or accept(self[index]) then value = self[index] end end return value end --- Returns the index of the given item. -- -- @param item The item for which to get the index. -- @param equals Optional. The equals function to use. -- @return The index of the given item. -1 if this item is not in this list. function List:index(item, equals) equals = equals or function(a, b) return a == b end for index = 1, self.counter - 1, 1 do if equals(self[index], item) then return index end end return -1 end --- If the List contains functions, this invokes all items with the given -- parameters. -- -- @param ... The parameters to invoke the functions. function List:invoke(...) for index = 1, self.counter - 1, 1 do self[index](...) end end --- Gets if this List is empty. -- -- @return true if this List is empty. function List:is_empty() return self.counter == 1 end --- Returns a List with all items that match the given condition. -- -- @param condition The condition, a function that accepts one parameter, -- the item, and returns a boolean. -- @return The List of matching items. function List:matching(condition) local found = List:new() for index = 1, self.counter - 1, 1 do local item = self[index] if condition(item) then found:add(item) end end return found end --- Removes the given values from the list. -- -- @param ... The values to remove. function List:remove(...) for index, value_to_remove in ipairs({...}) do self:remove_index(self:index(value_to_remove)) end end --- Removes the given index from the list. -- -- @param ... The index to remove. function List:remove_index(...) local to_remove = {...} table.sort(to_remove) for index = #to_remove, 1, -1 do local index_to_remove = to_remove[index] if index_to_remove > 0 and index_to_remove < self.counter then for index_walk = index_to_remove, self.counter, 1 do self[index_walk] = self[index_walk + 1] end self.counter = self.counter - 1 end end end --- Invokes the contained functions and returns the first value that is accepted -- by the given function. -- -- @param accept The function to accept values, takes the value and returns -- a boolean, true if the value is accepted. -- @param ... Optional. The parameters to invoke the functions with. -- @return The first accepted return value, nil if none was accepted. function List:return_first(accept, ...) for index = 1, self.counter - 1, 1 do local value = self[index](...) if accept(value) then return value end end return nil end --- Invokes the contained functions and returns the last value that is accepted -- by the given function. -- -- @param accept The function to accept values, takes the value and returns -- a boolean, true if the value is accepted. -- @param ... Optional. The parameters to invoke the functions with. -- @return The last accepted return value, nil if none was accepted. function List:return_last(accept, ...) local value = nil for index = 1, self.counter - 1, 1 do local returned_value = self[index](...) if accept(returned_value) then value = returned_value end end return value end --- Gets the size of the list. -- -- @return The size of the list. function List:size() return self.counter - 1 end --- Sorts this List. -- -- @param comparator Optional. The comparator to be used. Accepts two values -- and returns a boolean, true if the first is parameter is -- less than the second. function List:sort(comparator) table.sort(self, comparator or tableutil.comparator) end --- Gets a sub list starting from the given index and the given number of items. -- -- @param from The starting index. -- @param count The count of items to get. -- @return A List containing the items starting by the given index. The List -- will be empty if the starting index is out of range, if there are not -- as many items as specified with count, all items that there are will -- be returned. function List:sub_list(from, count) local sub = List:new() for index = math.max(from, 1), math.min(from + count - 1, self.counter - 1), 1 do sub:add(self[index]) end return sub end --- Turns this list into a table, the return table will be a one indexed array, -- and can freely be modified as it is not the table used by this instance. -- However the items in the returned table are not copies. -- -- @return This list as table. function List:to_table() local table = {} self:foreach(function(item, index) table[index] = item end) return table end