Sorted Iteration |
|
table.sort()
with the default function. Using a custom sort algorithm is trivial--just modify the __genOrderedIndex.
--[[ Ordered table iterator, allow to iterate on the natural order of the keys of a table. Example: ]] function __genOrderedIndex( t ) local orderedIndex = {} for key in pairs(t) do table.insert( orderedIndex, key ) end table.sort( orderedIndex ) return orderedIndex end function orderedNext(t, state) -- Equivalent of the next function, but returns the keys in the alphabetic -- order. We use a temporary ordered key table that is stored in the -- table being iterated. local key = nil --print("orderedNext: state = "..tostring(state) ) if state == nil then -- the first time, generate the index t.__orderedIndex = __genOrderedIndex( t ) key = t.__orderedIndex[1] else -- fetch the next value for i = 1,table.getn(t.__orderedIndex) do if t.__orderedIndex[i] == state then key = t.__orderedIndex[i+1] end end end if key then return key, t[key] end -- no more value to return, cleanup t.__orderedIndex = nil return end function orderedPairs(t) -- Equivalent of the pairs() function on tables. Allows to iterate -- in order return orderedNext, t, nil end
Example usage:
t = { ['a'] = 'xxx', ['b'] = 'xxx', ['c'] = 'xxx', ['d'] = 'xxx', ['e'] = 'xxx', } print("Normal iterating with pairs") for key, val in pairs(t) do print(key.." : "..val) end print("Ordered iterating") for key, val in orderedPairs(t) do print(key.." : "..val) end --[[ Result: Normal iterating with pairs a : xxx c : xxx b : xxx e : xxx d : xxx Ordered iterating a : xxx b : xxx c : xxx d : xxx e : xxx ]]
There is also a compressed version, but it will replace pairs with orderedPairs.
_10=pairs function _1(_6)local _2={}for _4 in _10(_6)do table.insert(_2,_4)end table.sort(_2)return _2 end function _3(_6,_5)if _5==nil then _6._7=_1(_6)_4=_6._7[1]return _4,_6[_4]end _4=nil for _8 = 1,table.getn(_6._7)do if _6._7[_8]==_5 then _4=_6._7[_8+1]end end if _4 then return _4,_6[_4]end _6._7=nil return end function _9(_6)return _3,_6,nil end pairs=_9
When multiple type of key exists in a table, you can use the following comparison function:
function cmp_multitype(op1, op2) local type1, type2 = type(op1), type(op2) if type1 ~= type2 then --cmp by type return type1 < type2 elseif type1 == "number" or type1 == "string" then --type2 is equal to type1 return op1 < op2 --comp by default elseif type1 == "boolean" then return op1 == true else return tostring(op1) < tostring(op2) --cmp by address end end function __genOrderedIndex( t ) local orderedIndex = {} for key in pairs(t) do table.insert( orderedIndex, key ) end table.sort( orderedIndex, cmp_multitype ) --### CANGE ### return orderedIndex end
Example usage:
t = { b="xxx", a="xxx", 100, [-5]=100 } print("Ordered iterating") for key, val in orderedPairs(t) do print(key.." : "..val) end --[[ Result: Ordered iterating -5 : 100 1 : 100 a : xxx b : xxx ]]
Alternate Implementation (by BobC, Lua newbie, using wxLua 2.6.3):
-------------------------------------- -- Insert value of any type into array -------------------------------------- local function arrayInsert( ary, val, idx ) -- Needed because table.insert has issues -- An "array" is a table indexed by sequential -- positive integers (no empty slots) local lastUsed = #ary + 1 local nextAvail = lastUsed + 1 -- Determine correct index value local index = tonumber(idx) -- Don't use idx after this line! if (index == nil) or (index > nextAvail) then index = nextAvail elseif (index < 1) then index = 1 end -- Insert the value if ary[index] == nil then ary[index] = val else -- TBD: Should we try to allow for skipped indices? for j = nextAvail,index,-1 do ary[j] = ary[j-1] end ary[index] = val end end -------------------------------- -- Compare two items of any type -------------------------------- local function compareAnyTypes( op1, op2 ) -- Return the comparison result -- Inspired by http://lua-users.org/wiki/SortedIteration local type1, type2 = type(op1), type(op2) local num1, num2 = tonumber(op1), tonumber(op2) if ( num1 ~= nil) and (num2 ~= nil) then -- Number or numeric string return num1 < num2 -- Numeric compare elseif type1 ~= type2 then -- Different types return type1 < type2 -- String compare of type name -- From here on, types are known to match (need only single compare) elseif type1 == "string" then -- Non-numeric string return op1 < op2 -- Default compare elseif type1 == "boolean" then return op1 -- No compare needed! -- Handled above: number, string, boolean else -- What's left: function, table, thread, userdata return tostring(op1) < tostring(op2) -- String representation end end ------------------------------------------- -- Iterate over a table in sorted key order ------------------------------------------- local function pairsByKeys (tbl, func) -- Inspired by http://www.lua.org/pil/19.3.html -- and http://lua-users.org/wiki/SortedIteration if func == nil then func = compareAnyTypes end -- Build a sorted array of the keys from the passed table -- Use an insertion sort, since table.sort fails on non-numeric keys local ary = {} local lastUsed = 0 for key --[[, val--]] in pairs(tbl) do if (lastUsed == 0) then ary[1] = key else local done = false for j=1,lastUsed do -- Do an insertion sort if (func(key, ary[j]) == true) then arrayInsert( ary, key, j ) done = true break end end if (done == false) then ary[lastUsed + 1] = key end end lastUsed = lastUsed + 1 end -- Define (and return) the iterator function local i = 0 -- iterator variable local iter = function () -- iterator function i = i + 1 if ary[i] == nil then return nil else return ary[i], tbl[ary[i]] end end return iter end
--------------------------------------------- -- Return indentation string for passed level --------------------------------------------- local function tabs(i) return string.rep(".",i).." " -- Dots followed by a space end ----------------------------------------------------------- -- Return string representation of parameter's value & type ----------------------------------------------------------- local function toStrType(t) local function fttu2hex(t) -- Grab hex value from tostring() output local str = tostring(t); if str == nil then return "tostring() failure! \n" else local str2 = string.match(str,"[ :][ (](%x+)") if str2 == nil then return "string.match() failure: "..str.."\n" else return "0x"..str2 end end end -- Stringify a value of a given type using a table of functions keyed -- by the name of the type (Lua's version of C's switch() statement). local stringify = { -- Keys are all possible strings that type() may return, -- per http://www.lua.org/manual/5.1/manual.html#pdf-type. ["nil"] = function(v) return "nil (nil)" end, ["string"] = function(v) return '"'..v..'" (string)' end, ["number"] = function(v) return v.." (number)" end, ["boolean"] = function(v) return tostring(v).." (boolean)" end, ["function"] = function(v) return fttu2hex(v).." (function)" end, ["table"] = function(v) return fttu2hex(v).." (table)" end, ["thread"] = function(v) return fttu2hex(v).." (thread)" end, ["userdata"] = function(v) return fttu2hex(v).." (userdata)" end } return stringify[type(t)](t) end ------------------------------------- -- Count elements in the passed table ------------------------------------- local function lenTable(t) -- What Lua builtin does this simple thing? local n=0 -- '#' doesn't work with mixed key types if ("table" == type(t)) then for key in pairs(t) do -- Just count 'em n = n + 1 end return n else return nil end end -------------------------------- -- Pretty-print the passed table -------------------------------- local function do_dumptable(t, indent, seen) -- "seen" is an initially empty table used to track all tables -- that have been dumped so far. No table is dumped twice. -- This also keeps the code from following self-referential loops, -- the need for which was found when first dumping "_G". if ("table" == type(t)) then -- Dump passed table seen[t] = 1 if (indent == 0) then print ("The passed table has "..lenTable(t).." entries:") indent = 1 end for f,v in pairsByKeys(t) do if ("table" == type(v)) and (seen[v] == nil) then -- Recurse print( tabs(indent)..toStrType(f).." has "..lenTable(v).." entries: {") do_dumptable(v, indent+1, seen) print( tabs(indent).."}" ) else print( tabs(indent)..toStrType(f).." = "..toStrType(v)) end end else print (tabs(indent).."Not a table!") end end -------------------------------- -- Wrapper to handle persistence -------------------------------- function dumptable(t) -- Only global declaration in the package -- This wrapper exists only to set the environment for the first run: -- The second param is the indentation level. -- The third param is the list of tables dumped during this call. -- Getting this list allocated and freed was a pain, and this -- wrapper was the best solution I came up with... return do_dumptable(t, 0, {}) end
-- The Whole Enchillada print("\ndumptable(_G=", _G, "):") dumptable(_G) -- Empty table -- print("\ndumptable({}):") dumptable({}) -- Confusing table -- null={} null[0]=[[0]] null[ [[0]]]=0 -- Space after first open bracket is required null[{}]={} print("\ndumptable(null=", null, "):") dumptable(null) -- Module table -- print("\ndumptable(os=", os, "):") dumptable(os)
dumptable(_G= table: 00324F88 ): The passed table has 41 entries: < WAY too long - snipped > dumptable({}): The passed table has 0 entries: dumptable(null= table: 00413F90 ): The passed table has 3 entries: . 0 (number) = "0" (string) . "0" (string) = 0 (number) . 0x00413FB8 (table) has 0 entries: { . } dumptable(os= table: 00327F60 ): The passed table has 11 entries: . "clock" (string) = 0x00328190 (function) . "date" (string) = 0x003281D0 (function) . "difftime" (string) = 0x00328210 (function) . "execute" (string) = 0x00328660 (function) . "exit" (string) = 0x003286A0 (function) . "getenv" (string) = 0x003286E0 (function) . "remove" (string) = 0x00328720 (function) . "rename" (string) = 0x00328740 (function) . "setlocale" (string) = 0x00328780 (function) . "time" (string) = 0x003287C8 (function) . "tmpname" (string) = 0x00328808 (function)
This sorted iteration function is used on English Wiktionary ([Module:table]; [examples]). The default key sorting function assumes keys are numbers or strings, and that has not caused problems because few tables on Wiktionary use other types of keys. But it is easy to input your own key-sorting function.
-- Warning! This function is only guaranteed to work if all keys are strings or numbers. local function defaultKeySort(key1, key2) -- "number" < "string", so numbers will be sorted before strings. local type1, type2 = type(key1), type(key2) if type1 ~= type2 then return type1 < type2 else return key1 < key2 end end local function keysToList(t, keySort) local list = {} local index = 1 for key in pairs(t) do list[index] = key index = index + 1 end keySort = keySort or defaultKeySort table.sort(list, keySort) return list end -- Input a custom keySort function in the second parameter, or use the default one. -- Creates a new table and closure every time it is called. local function sortedPairs(t, keySort) local list = keysToList(t, keySort, true) local i = 0 return function() i = i + 1 local key = list[i] if key ~= nil then return key, t[key] else return nil, nil end end end