Sorting key value pairs results ?

Hi

Calling all the lua experts out there, :slight_smile:

When someone has time - please could they help update the following code so that the printed list is sorted in order of the oldest one first (longest period since Sensor was last updated)

[code]-- Basis taken from Door/Window sensor check and speech annotation by Pasqual Zottola

local devcnt = 0
local devno = 0
local numopen = 0
local isare = “is”
local opensensors = " "
local SSID = “urn:micasaverde-com:serviceId:SecuritySensor1”

for deviceNo,d in pairs(luup.devices) do
if d.category_num == 4 then

  local tripped = luup.variable_get(SSID, "Tripped", deviceNo) or "Nil"
  local lastTrip = luup.variable_get (SSID, "LastTrip", deviceNo) or os.time()

local timeString = os.date(" %Y/%m/%d - %X", lastTrip)

  lastTrip = tonumber (lastTrip)
  
  if (tripped == "1") then
     opensensors = opensensors .. ". " .. d.description
    
     numopen = numopen + 1
  end

print (
lastTrip …
’ a.k.a ’ … timeString …
’ : ’ … d.description … " Sensor was last tripped")

devcnt = devcnt + 1
end
end

if numopen == 0 then --If there are no sensors tripped, log and announce!
print ("- - - - ")
print(‘There were ’ … devcnt … ’ door and window sensors found and all are closed’)

else --If there are any sensors tripped, log and announce!

if numopen > 1 then
isare = “are”
end
print ("- - - - ")
print (‘There were ’ … devcnt … ’ door and window sensors found and ’ … numopen … " " … isare … ’ currently open’ … opensensors)
end[/code]

You’re going to have to do some of the work yourself, but the basic idea is to put the results into an array, and then sort the array. Alternatively, make a list of the array indices (ie. device numbers) and sort those by update time, then use the sorted list to index your main loop.

I had tried but the things I read made limited sense, and I really just wanted the answer - hence the post …

I did look at adding the creation of an array to the code,

listing {}

And I also read these about sorting/tables (below) , but due to my ew job I have such limited time, I hoped it was a quick one for someone.

https://www.lua.org/pil/19.3.html

If no one has the answer, then a Vera/luup example might help.

Either way - thanks .

This code will sift through luup.devices taking all devices that have LastTrip set, then sort them most to least recent, then print (log) the list. I hope you find this helpful.

[tt]local d, n
local tt = {}
for n,d in pairs(luup.devices) do
local lasttrip = tonumber(luup.variable_get(“urn:micasaverde-com:serviceId:SecuritySensor1”, “LastTrip”, n) or 0,10)
if lasttrip > 0 then
table.insert(tt, { devnum=n, last=lasttrip })
end
end
table.sort(tt, function(a,b) return a.last > b.last end) – sort highest to lowest
for n,d in ipairs(tt) do
luup.log(d.devnum … " name " … luup.devices[d.devnum].description … " last trip " … d.last)
end[/tt]

To do oldest first, just change the > in the sort function to a <.

Huge Thanks @rigpapa

Im embarrassed to say it but I really struggled trying to work out how to do this. It not helped I think by the fact that Im still getting to grips with arrays and tables. I had tried reworking this taken from a Lua Tutorial into my code above but it did not seem to do anything,

a = {} i n in pairs(listing) do table.insert(a, n) end table.sort(a) for i,n in ipairs(a) do print(n) end

I had also read this - lua-users wiki: For Tutorial (Numeric progression) but so much felt like gobbledygook :frowning:

Im beginning to come to resign myself to the fact that my programming skills are more akin to a musician that learns best by their ear, rather than by reading music. :).

Show me a working example and I can have a good go at reverse engineering it for how I might need it.

Many thanks again.

@akbooer thanks for responding too, I know you are only trying to help (… teach a man to fish etc… )

Keep at it. Working from examples and templates is a time-tested way to bootstrap yourself. I’ve been programming since punched cards and paper tape days, but there’s always something new and I still enjoy the “ah-hah” moments the most.

Thanks again @rigpapa

Having that example allowed me to see better how the pairs and ipairs work in practice with Vera. I had seen example on the LUA site but they just didn?t sink in. (One creates the table and the other updates it?)

For others reading this …

Here?s my updated code, which sorts in order (by human readable time) when all your security Sensors were last tripped.

[code]local d, n
local tt = {}
for n,d in pairs(luup.devices) do

if d.category_num == 4 then
local lasttrip = tonumber(luup.variable_get ("urn:micasaverde-com:serviceId:SecuritySensor1", "LastTrip", n) or os.time())
local timeString = os.date(" %Y/%m/%d - %X", lasttrip)

if lasttrip > 0 then
    table.insert(tt, { devnum=n, last=lasttrip, ltime=timeString })
end

end
end

table.sort(tt, function(a,b) return a.last > b.last end) – sort highest to lowest

for n,d in ipairs(tt) do
print (
d.ltime …
’ : ’ … luup.devices[d.devnum].description … " Sensor was last tripped")
end[/code]

Resulting print out, with most recent at the top…

2017/11/15 - 09:14:33 : 07. Bedroom Sensor was last tripped 2017/11/15 - 09:09:43 : 20. Living Room Sensor was last tripped 2017/11/15 - 09:09:34 : 18. Hallway Sensor was last tripped 2017/11/15 - 09:08:48 : 13. Conservatory Sensor was last tripped 2017/11/15 - 09:08:43 : 12. Dining Room Sensor was last tripped 2017/11/15 - 09:08:01 : 10. Kitchen Sensor was last tripped

Some questions.

  1. I wanted to ask you about a line you used below (I changed it) as I was not sure what it exactly did ? Can you help me understand.

Yours - local lasttrip = tonumber(luup.variable_get(“urn:micasaverde-com:serviceId:SecuritySensor1”, “LastTrip”, n) or 0,10)

I changed it to - local lasttrip = tonumber(luup.variable_get (“urn:micasaverde-com:serviceId:SecuritySensor1”, “LastTrip”, n) or os.time())

  1. Also why do we define local values d, n first ?
    Do we have to when they are going to be defined/used as the key and value for the pairs (array/table creation)?
    I can exclude them and it still works?

  2. Is a table and array the same thing ?

Yours - local lasttrip = tonumber(luup.variable_get("urn:micasaverde-com:serviceId:SecuritySensor1", "LastTrip", n) or 0,10)

I changed it to - local lasttrip = tonumber(luup.variable_get (“urn:micasaverde-com:serviceId:SecuritySensor1”, “LastTrip”, n) or os.time())

  1. That seems fine. Any default that works for your intended semantics is A-OK.
2) Also why do we define local values d, n first ?
  1. I declared d and n first so they are not created as globals. If you set d to a value before declaring it as a local variable, Lua will make it global. I’m not a fan of globals, although they do have their place, and Luup forces us to use them, but I try to avoid creating them myself unless there’s a clear benefit/need. Probably my #1 wish for Lua is that it had a “strict” mode that would just trap when a variable is set or used before being declared (fortunately there are ways to do this, just not built-in).
3) Is a table and array the same thing ?
  1. An array is a table, where the keys are unbroken/sequential numeric values starting at one. They are not different structures or types in Lua, though. This causes some interesting behaviors with pairs() and ipairs(). Consider the following initialization of a table:

[tt]local t = {}
t[1] = “A”
t[0] = “zero”
t[99] = “last”[/tt]

Note that we have non-sequential indexes (or indices if you prefer), and 0 (recall Lua arrays start at 1). If you iterate over t using pairs(), you will get this:

[tt]local k,v
for k,v in pairs(t) do
print(k … “=” … v)
end

1=A
0=zero
99=last[/tt]

I think that’s pretty much what you’d expect. However, if you use ipairs() instead, Lua produces the following output:

[tt]for k,v in ipairs(t) do print(k … “=” … v) end

1=A[/tt]

The table elements with keys 0 and 99 (numeric) aren’t returned in the ipairs() iteration. This is because ipairs() really assumes its table argument is being used strictly as an array, so it only iterates from 1 up and stops when an index is missing. It doesn’t print 0 because Lua arrays start at 1, and it doesn’t print 99 because 2, 3, and everything else in between are missing (non-sequential).

Generally, unless you are very sure the table you are dealing with is an array, it’s safer to use pairs() instead of ipairs(). And when dealing with a true, bona-fide array, if using the [tt]table[numericIndex]=value[/tt] form to add array elements you should take extra care to make sure your index values are sequential or (preferably, IMO) use [tt]table.insert()[/tt] for adding elements; and if removing a value from an array, do not use the [tt]table[index]=nil[/tt] form (which creates a gap), but rather [tt]table.remove()[/tt].

Massive thanks @rigpapa that helps a lot…

Ive updated our code with some HTML so that the table published via sb.html has borders.

[code]local d, n
local file = io.open(“/www/sb.html”, “w”)
file:write(“.custom { font-size: 1em; font-family: Gill Sans Extrabold, sans-serif; padding:5px; border-collapse: collapse; border: 1px solid black; }\n”)

local tt = {}
for n,d in pairs(luup.devices) do

if d.category_num == 4 then
local lasttrip = tonumber(luup.variable_get ("urn:micasaverde-com:serviceId:SecuritySensor1", "LastTrip", n) or os.time())
local timeString = os.date(" %Y/%m/%d - %X", lasttrip)

if lasttrip > 0 then
    table.insert(tt, { devnum=n, last=lasttrip, ltime=timeString })
end

end
end

table.sort(tt, function(a,b) return a.last > b.last end) – sort highest to lowest

for n,d in ipairs(tt) do

file:write(“

\n”)

print (
d.ltime ..

’ : ’ … luup.devices[d.devnum].description … " Sensor was last tripped")
end
file:write(“

Security Sensor Last Tripped
” … luup.devices[d.devnum].description … “ ”… d.ltime … “
\n”)
file:close()[/code]

Looking at our script - if I understand everything correctly luup.devices is already an indexed table - http://wiki.micasaverde.com/index.php/Luup_Lua_extensions#variable:_devices

How do I see (print out) that entire table to see all the contents ?

In your example we defined the table as t, but how can you indentify a predefined table or an array E.g luup.devices, luup.rooms, luup.scenes etc?

For the luup tables (devices, scenes, etc.) you should use [tt]pairs()[/tt], as these tables are not arrays. The key values are the IDs of their respective objects, and so the table may (will at some point) have gaps in sequencing that make [tt]ipairs()[/tt] an incorrect choice for iteration.

Here is code I often use to dump the contents of tables…

[tt]local function dump(t)
if t == nil then return “nil” end
if type(t) ~= “table” then return tostring(t) end
local k,v,str,val
local sep = “”
local str = “{ "
for k,v in pairs(t) do
if type(v) == “table” then
val = dump(v)
elseif type(v) == “function” then
val = “(function)”
elseif type(v) == “string” then
val = string.format(”%q", v)
else
val = tostring(v)
end
str = str … sep … k … “=” … val
sep = “, "
end
str = str … " }”
return str
end
local k,v
for k,v in pairs(luup.devices) do
luup.log(“device #” … k … “=” … dump(v))
end
[/tt]

You should also put a decent depth in your dump function.
There are many LUA objects that are recursive.
You can also use the depth to indent the content to better see the structure.