Is there any way to loop trough a table like the one below in the same order as it's written?
local tbl = {
["hello"] = 1,
[2] = 2,
[50] = 3,
["bye"] = 4,
[200] = 5
}
What I mean is that when I use "in pairs" I'll get a different order everytime I execute my code ...
I'm searching for something like this:
function get_keys(tbl)
local rtable = {}
for k,v in pairs(tbl) do
table.insert(rtable, k)
end
return rtable
end
local keys_of_tbl = get_keys(tbl)
for i = 1, table.getn(keys_of_tbl) do
--Do something with: tbl[keys_of_tbl[i]]
end
But because the function "get_keys" is based on "in pairs" again, it won't work ...
In Lua, the order that pairs iterates through the keys is unspecified. However you can save the order in which items are added in an array-style table and use ipairs (which has a defined order for iterating keys in an array). To help with that you can create your own ordered table using metatables so that the key order will be maintained when new keys are added.
EDIT (earlier code inserted multiple copies of the key on updates)
To do this you can use __newindex which we be called so long as the index is not added yet to the table. The ordered_add method updates, deletes, or stores the key in the hidden tables _keys and _values. Note that __newindex will always be called when we update the key too since we didn't store the value in the table but instead stored it in the "hidden" tables _keys and _values.
Note however that we cannot use any key in this table, the key name "_keys" will overwrite our hidden table so the safer alternative is to use the ordered_table.insert(t, key, value) ordered_table.index(t, key) and ordered_table.remove(t, key) methods.
ordered_table = {}
function ordered_table.insert(t, k, v)
if not rawget(t._values, k) then -- new key
t._keys[#t._keys + 1] = k
end
if v == nil then -- delete key too.
ordered_table.remove(t, k)
else -- update/store value
t._values[k] = v
end
end
local function find(t, value)
for i,v in ipairs(t) do
if v == value then
return i
end
end
end
function ordered_table.remove(t, k)
local v = t._values[k]
if v ~= nil then
table.remove(t._keys, find(t._keys, k))
t._values[k] = nil
end
return v
end
function ordered_table.index(t, k)
return rawget(t._values, k)
end
function ordered_table.pairs(t)
local i = 0
return function()
i = i + 1
local key = t._keys[i]
if key ~= nil then
return key, t._values[key]
end
end
end
function ordered_table.new(init)
init = init or {}
local t = {_keys={}, _values={}}
local n = #init
if n % 2 ~= 0 then
error"in ordered_table initialization: key is missing value"
end
for i=1,n/2 do
local k = init[i * 2 - 1]
local v = init[i * 2]
if t._values[k] ~= nil then
error("duplicate key:"..k)
end
t._keys[#t._keys + 1] = k
t._values[k] = v
end
return setmetatable(t,
{__newindex=ordered_table.insert,
__len=function(t) return #t._keys end,
__pairs=ordered_table.pairs,
__index=t._values
})
end
--- Example Usage:
local t = ordered_table.new{
"hello", 1, -- key, value pairs
2, 2,
50, 3,
"bye", 4,
200, 5
}
print(#t)
print("hello is", t.hello)
print()
for k, v in pairs(t) do --- Lua 5.2 __pairs metamethod
print(k, v)
end
t.bye = nil -- delete that
t[2] = 7 -- use integer keys
print(#t)
No. There's no "as written in the source" order to tables. (Consider that not all keys necessarily exist in the source.) lua has no concept of "in order" for non-contiguous integer keys.
If you want a specific order you get to keep that order yourself manually in some way.
If you don't have any integer keys in your table then you can use those as your order (and use ipairs to loop those keys and index the value as the key to get the real value).
If your original values are the order you want to sort in then you can loop and reverse map to get a table that you can iterate with ipairs once done.
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
What is the best way, in terms of code cleanness or efficiency, to find an "intersection" of two Lua numbered tables, i.e. a table that contains only values that are present in both tables?
For example, I have
a={1,2,3,4,5}
b={3,4,5,6,7}
Since 3, 4 and 5 are found in both a and b, I want {3,4,5}.
I suggest a slightly different approach, based on sets. The performance will not necessarily improve, bit the code will be clean, understandable and expandable.
In Lua a set can be easily implemented as a table, where the keys are set items and the values are true, therefore, to check whether an item is present in the set, you just need if set[item] then.
I will give three variants of the code. The first cleanly implements an intersect operator but then has to transform the resulting set back to an indexed Lua table. The second immediately returns the intersections of two sets as an array. The third is a compromise between the first two.
Note that neither contains nested loops over a and b: it's O(a + b) rather than O(ab).
First:
local insert, sort = table.insert, table.sort -- localise for performance.
local a = {1,2,3,4,5}
local b = {3,4,5,6,7}
local function toset (tbl)
local set = {}
for _, value in ipairs (tbl) do
set [value] = true
end
return set
end
local function intersect (set1, set2)
local intersection = {}
for item, _ in pairs (set1) do
if set2 [item] then
intersection [item] = true
end
end
return intersection
end
local function toarray (set)
local array = {}
for item, _ in pairs (set) do
insert (array, item)
end
sort (array) -- since you want {3,4,5} not {4,5,3}
return array
end
-- Fortunately, table.concat does not require previous tostring:
print ('{' .. table.concat (toarray (intersect (toset (a), toset (b))), ', ') .. '}')
Second:
local insert, sort = table.insert, table.sort -- localise for performance.
local a = {1,2,3,4,5}
local b = {3,4,5,6,7}
local function toset (tbl)
local set = {}
for _, value in ipairs (tbl) do
set [value] = true
end
return set
end
local function intersect_and_to_array (set1, set2)
local array = {}
for item, _ in pairs (set1) do
if set2 [item] then
insert (array, item)
end
end
sort (array) -- since you want {3,4,5} not {4,5,3}
return array
end
-- Fortunately, table.concat does not require previous tostring:
print ('{' .. table.concat (intersect_and_to_array (toset (a), toset (b)), ', ') .. '}')
Third:
local insert = table.insert -- localise for performance.
local a = {1,2,3,4,5}
local b = {3,4,5,6,7}
local function toset (tbl)
local set = {}
for _, value in ipairs (tbl) do
set [value] = true
end
return set
end
local function filter_array_through_set (array, set)
local filtered = {}
for _, value in ipairs (array) do
if set [value] then
insert (filtered, value)
end
end
return filtered -- the order of array is preserved.
end
-- Fortunately, table.concat does not require previous tostring:
print ('{' .. table.concat (filter_array_through_set (a, toset (b)), ', ') .. '}')
You need to iterate on one of the table and output the value if you find it in the other table. On, my computer the following code returns {3, 4, 5}, it basically implement the intersection of 2 sets.
a={1,2,3,4,5}
b={3,4,5,6,7}
function Contains (Array, Item)
local Found = false
local Index = 1
while ((not Found) and (Index <= #Array)) do
if Array[Index] == Item then
Found = true
else
Index = Index + 1
end
end
return Found
end
function Intersects (Ta, Tb)
local Result = { }
local Index
for Index = 1, #Tb do
if Contains(Ta, Tb[Index]) then
Result[#Result + 1] = Tb[Index]
end
end
return Result
end
Result = Intersects(a, b)
-- Print the results
local Index, Value
for Index, Value in ipairs(Result) do
print(Value)
end
I'm very stuck at this exercise, so I would be grateful for a very detailed answer.
Question:
At what point is a key added to the aDict array? I can only see the created index of the key being added to the array.
(return aDict [bucket-id]?)
I'm looking at this code:
module Dict
def Dict.new(num_buckets = 256)
#Initializes Dict with the given number of buckets.
aDict = []
(0...num_buckets).each do |i|
aDict.push([])
end
return aDict
end
def Dict.hash_key(aDict,key)
#given a key this will create a number
#turning it into an index for one of aDicts buckets
return key.hash % aDict.length
end
def Dict.get_bucket(aDict, key)
#Given a key, find the bucket where it would go.
bucket_id = Dict.hash_key(aDict,key)
return aDict[bucket_id]
end
def Dict.get_slot(aDict, key, default=nil)
#Returns the index, key and
#value of a slot found in a bucket.
bucket = Dict.get_bucket(aDict,key)
bucket.each_with_index do |kv, i|
k, v = kv
if key == k
return i, k, v
end
end
return -1, key, default
end
def Dict.get(aDict, key, value)
#Gets the value in a bucket for the given key or the default
i, k, v = Dict.get_slot (aDict,key, Value, default = default)
return v
end
def Dict.set(aDict,key,value)
#Sets the key to the value,
#replacing any existing value.
bucket = Dict.get_bucket(aDict, key)
i,k,v = Dict.get_slot(aDict, key)
if [i] >= 0
bucket[i] = [key,value]
else
bucket.push([key,value])
end
end
Let's say I import Dict.rb to another file and I want it to run:
require .\dict.rb
#create mapping of state to abbreviation
states Dict.new()
Dict.set( states, Oregon, OR)
When is the key (Oregon) in the bucket so that it can be returned by aDict[bucket_id]?
Ok, so first the structure of the hash table aDict will look like this with some keys in it:
[0] => [[k1, v1], [k2, v2]]
[1] => [[k3, v3]]
[2] => []
0,1,2 are the indices. At each index position, you have another array and each element of this array is a two element array containing a key and value. For example, this means that k3 is at aDict[1][0][0]
So, when you want to insert a key and value in the hash, the Dict.set method gets called
def Dict.set(aDict,key,value)
#Sets the key to the value,
#replacing any existing value.
bucket = Dict.get_bucket(aDict, key)
get_bucket calculates the first index by taking the mod of the key hash with the size of the array. It then returns the array stored at that index. (For example: bucket = aDict[1])
i,k,v = Dict.get_slot(aDict, key)
get_slot finds out which index in bucket array has your key and returns the index number along with the key and value. If it does not exist, it returns -1 for no index, the key and the default value (nil)
(For example: i, k, v will be 0, k3, v3 because [k3, v3] is at aDict[1][0]. If you were looking for k4, i, k, v would have been -1, k4, nil)
if i >= 0
bucket[i] = [key,value]
else
bucket.push([key,value])
end
end
This bit is easy. If i is not -1, then you update the location with your key and value otherwise you push new two element array at the end of the array.
Sorry if this is a dumb question, I'm a fairly inexperienced programmer.
I'm trying to return all values within an array using Lua. I can return individual elements by calling their index (ex. read_data[2]) but since the number of elements in the array is variable, I cannot simply type this out. My code:
function readformatEvent()
local read_data = {}
local duplicate
local unique_data = {}
for i=1,16 do
read_data[i] = readResult(i):readData()
end
for i=1,16 do
duplicate = 0
for j=(i+1),15 do
if read_data[i] == read_data[j] then
duplicate = 1
end
end
if duplicate == 0 then
unique_data[i] = read_data[i]
end
end
return unique_data
end
unique_data is an array consisting of unique values from the array read_data. read_data can consist of 1 to 16 elements. Being able to see the full array would help me continue to craft the code as a troubleshooting technique.
Thank you,
A short example of what you could do:
-- some example table
t = {1,2,4,5,1,2,7,8,5,4,9,3}
-- for every value v in t
for k,v in pairs(t) do
-- check v vs all remaining values in t
for m,w in pairs(t) do
-- but not vs v itself
if k ~= m then
-- remove any non unique values from t
if w == v then
t[m] = nil
t[k] = nil
end
end
end
end
-- print the result
for k,v in pairs(t) do
print(v)
end
Thanks for all the help. Here is the code that ended up working. I'm sure it's not efficient, but I'm getting the correct output now.
function readformatEvent()
function readformatEvent()
local read_data = {}
local unique_data = {}
local count = 1
local output = ""
local codes = readResult():readCount()
--create array from captured read data
for i=1,16 do
read_data[i] = readResult(i):readData()
end
--turn 2nd duplicate in the array into a nil value
for k,v in pairs(read_data) do
for m,w in pairs(read_data) do
if k ~= m then
if w == v then
read_data[m] = nil
end
end
end
end
--remove the nils from the array
for i=1,16 do
if read_data[i] ~= nil then
unique_data[count] = read_data[i]
count = count+1
end
end
--output unique values in correct format
if count == 12 and codes == 16 then
count = count - 1
else
count = count - 2
end
for i=1,count-1 do
output = output..unique_data[i]..", "
end
return output..unique_data[count]
end
I have an array of objects (or just numbers), and I have another array which contains all the objects that should not be removed from the first array in any circumstances. It looks something like this:
-- Array of objects (just numbers for now)
Objects = {}
-- Array of objects that should always stay in the 'Objects' array
DontDestroyThese = {}
-- Populate the arrays
Objects[#Objects+1] = 1
Objects[#Objects+1] = 2
Objects[#Objects+1] = 3
Objects[#Objects+1] = 4
Objects[#Objects+1] = 5
DontDestroyThese[#DontDestroyThese+1] = 2
DontDestroyThese[#DontDestroyThese+1] = 5
Now, I have a method called destroy() that should remove all objects from the Objects array except those included in the DontDestroyThese array. The method looks something like this:
function destroy()
for I = 1, #Objects do
if(DontDestroyThese[Objects[I]] ~= nil) then
print("Skipping " .. Objects[I])
else
Objects[I] = nil
end
end
end
However, as the result, the Objects array now contains nil values here and there. I'd like to remove these nils so that the Objects array would consist only of the numbers that were left there after calling destroy(). How do I do that?
The most efficient way is probably to create a new table to hold the result. Trying to move values around in the array is likely to have a higher overhead than simply appending to a new table:
function destroy()
local tbl = {}
for I = 1, #Objects do
if(DontDestroyThese[Objects[I]] ~= nil) then
table.insert(tbl, Objects[I])
end
end
Objects = tbl
end
This method also means you don't have to deal with altering the contents of the table/array you're iterating over.
I think the solution is much simpler. To remove any nils ('holes' in your array), all you need to do is iterate your table using pairs(). This will skip over any nils returning only the non-nil values that you add to a new local table that is returned in the end of the 'cleanup' function. Arrays (tables with indices from 1..n) will remain with the same order. For example:
function CleanNils(t)
local ans = {}
for _,v in pairs(t) do
ans[ #ans+1 ] = v
end
return ans
end
Then you simply need to do this:
Objects = CleanNils(Objects)
To test it:
function show(t)
for _,v in ipairs(t) do
print(v)
end
print(('='):rep(20))
end
t = {'a','b','c','d','e','f'}
t[4] = nil --create a 'hole' at 'd'
show(t) --> a b c
t = CleanNils(t) --remove the 'hole'
show(t) --> a b c e f
local function remove(t, pred)
for i = #t, 1, -1 do
if pred(t[i], i) then
table.remove(t, i)
end
end
return t
end
local function even(v)
return math.mod(v, 2) == 0
end
-- remove only even numbers
local t = remove({1, 2, 3, 4}, even)
-- remove values you want
local function keep(t)
return function(v)
return not t[v]
end
end
remove(Objects, keep(DontDestroyThese))
How can I write a function that determines whether it's table argument is a true array?
isArray({1, 2, 4, 8, 16}) -> true
isArray({1, "two", 3, 4, 5}) -> true
isArray({1, [3]="two", [2]=3, 4, 5}) -> true
isArray({1, dictionaryKey = "not an array", 3, 4, 5}) -> false
I can't see any way of finding out if the numeric keys are the only keys.
EDIT: Here's a new way to test for arrays that I discovered just recently. For each element returned by pairs, it simply checks that the nth item on it is not nil. As far as I know, this is the fastest and most elegant way to test for array-ness.
local function isArray(t)
local i = 0
for _ in pairs(t) do
i = i + 1
if t[i] == nil then return false end
end
return true
end
ipairs iterates over indices 1..n, where n+1 is the first integer index with a nil value
pairs iterates over all keys.
if there are more keys than there are sequential indices, then it cannot be an array.
So all you have to do is see if the number of elements in pairs(table) is equal to the number of elements in ipairs(table)
the code can be written as follows:
function isArray(tbl)
local numKeys = 0
for _, _ in pairs(tbl) do
numKeys = numKeys+1
end
local numIndices = 0
for _, _ in ipairs(tbl) do
numIndices = numIndices+1
end
return numKeys == numIndices
end
I'm pretty new to Lua, so there might be some builtin function to reduce the numKeys and numIndices calculations to simple function calls.
By "true array", I suppose you mean a table whose keys are only numbers. To do this, check the type of every key of your table. Try this :
function isArray(array)
for k, _ in pairs(array) do
if type(k) ~= "number" then
return false
end
end
return true --Found nothing but numbers !
end
Note: as #eric points out, pairs is not defined to iterate in a specific order. Hence this is no valid answer.
The following should be sufficient; it checks that the keys are sequential from 1 until the end:
local function isArray(array)
local n = 1
for k, _ in pairs(array) do
if k ~= n then return false end
n = n + 1
end
return true
end
I wrote this code for another similar question lately:
---Checks if a table is used as an array. That is: the keys start with one and are sequential numbers
-- #param t table
-- #return nil,error string if t is not a table
-- #return true/false if t is an array/isn't an array
-- NOTE: it returns true for an empty table
function isArray(t)
if type(t)~="table" then return nil,"Argument is not a table! It is: "..type(t) end
--check if all the table keys are numerical and count their number
local count=0
for k,v in pairs(t) do
if type(k)~="number" then return false else count=count+1 end
end
--all keys are numerical. now let's see if they are sequential and start with 1
for i=1,count do
--Hint: the VALUE might be "nil", in that case "not t[i]" isn't enough, that's why we check the type
if not t[i] and type(t[i])~="nil" then return false end
end
return true
end
Here's my take on this, using #array to detect a gap or stop when too many keys have been read:
function isArray(array)
local count=0
for k,_ in pairs(array) do
count=count+1
if (type(k) ~= "number" or k < 1 or k > #array or count > #array or math.floor(k) ~= k) then
return false
end
end
if count ~= #array then
return false
end
return true
end
Iterate from 0 to the number of elements, and check if all elements with the counter's index exist. If it's not an array, some indexes will miss in the sequence.