Variabler som är listor och “loopar” eller hur man bearbetar listor…

Uppdaterad med lite bättre syntaxbeskrivning…

Jag hade ursprungligen tänkt att detta skulle ha varit i Del 2 – men tiden sprang iväg och jag bestämde mig för att dela upp variabler i två delar. Så nu kommer vi till variabler i form av listor 8-)

En lista med värden, ibland kallas det för vektor eller array i andra programmeringsspråk, kan ibland vara lämplig. Man kan t.ex. tänka sig att man definierar en lista med DeviceIDs som man vill kanske tända eller släcka, och för att slippa koda varje sådan rad så skapar man en lista och sedan “loopar” (bearbetar) man igenom denna lista. Koden blir lite mindre och smidigare – enklare att se vad man gör helt enkelt. Samma sak om man vill, som efterfrågades nyligen, slumpa fram ordningen. Och det var när jag skrev i den tråden som jag insåg att detta borde man nog ta med trots allt.

Listor kan mycket snabbt bli väldigt komplexa saker, med index och “linked lists” – men jag kommer inte gå igenom det – det är helt klart överkurs, och inte direkt något som man behöver sådär jätteofta i hemautomatiseringsbranschen ;-)

Låt oss börja med hur man definierar en lista (och i alla exempel nedan kommer jag använda samma grundtanke, en lista med DeviceIDs som man vill bearbeta):

local DeviceList = { 7, 9, 13, 17 }

Som ni ser är syntaxen lite annorlunda. Varje element (siffra i detta fall) står för sig, och start/stopp utgörs av { resp }. Alltså:

<variabelnamn> = { värde1, värdeN }

För att skriva ut ett av dessa element så skriver man t.ex.

local DeviceList = { 7, 9, 13, 17 }
fibaro:debug(Device nummer 3 har ID: "..DeviceList[4])

Observera alltså att när vi vill åt ett av värdena i listan så är syntaxen:

<variabelnamn>[<vilket element som motsvarar värdets plats i listan>]

En av poängerna med listor är dock inte att adressera en fast plats, utan att få flexibiliteten av en eller flera värden i listan. Att bara hantera ETT värde kan vi lika gärna använda en fast lokal variabel.

Således så har vi nu alltså skapat en LOKAL list variabel, för det går (såvitt jag vet) bara att skapa lokala listor som variabler.

Om man vill se vad som finns i listan så skriver man lämpligen:

for loop,Device in pairs(DeviceList) do
	fibaro:debug("Index: "..loop.." DeviceID: "..Device)
end

Och nu börjar det dyka upp lite nya ord här. Vi försöker ta det steg för steg.

Först och främst, och detta gäller inte bara vid listor utan så fort man vill göra något x antal gånger, så måste man på något sätt “loopa” igenom bearbetningsmängden. Det sker lämpligen med en “for” sats, och för att förenkla det hela så kan vi titta på denna kodsnutt:

for loop = 1, 10 do
	fibaro:debug("Loop: "..loop)
end

Kör vi denna snurra så får vi följande resultat:

[DEBUG] 19:03:41: Loop: 1
[DEBUG] 19:03:41: Loop: 2
[DEBUG] 19:03:41: Loop: 3
[DEBUG] 19:03:41: Loop: 4
[DEBUG] 19:03:41: Loop: 5
[DEBUG] 19:03:41: Loop: 6
[DEBUG] 19:03:41: Loop: 7
[DEBUG] 19:03:41: Loop: 8
[DEBUG] 19:03:41: Loop: 9
[DEBUG] 19:03:41: Loop: 10

Dvs vi köra samma kod 10 gånger – enkel loop. Observera syntaxen, alltså hur det är skrivet:

for loop = startvärde, slutvärde do
	<gör någonting>
end

Det finns en variant på denna:

for loop = startvärde, slutvärde, uppräkningsvärde do
	<gör någonting>
end

Dvs om man skriver:

for loop = 1, 10, 2 do
	fibaro:debug("Loop: "..loop)
end

Så får man inte samma resultat som ovan:

[DEBUG] 19:05:40: Loop: 1
[DEBUG] 19:05:40: Loop: 3
[DEBUG] 19:05:40: Loop: 5
[DEBUG] 19:05:40: Loop: 7
[DEBUG] 19:05:40: Loop: 9

Som ni ser så har vi nu enbart ojämna värden. Och det har att göra med att man startar på värdet 1 (som är det man angivet som startvärde, och till skillnad från t.ex. programspråket C som alltid startar på noll, så rekommenderar jag att ni startar på värdet 1 – det är mera “integrerat” i LUA om man senare får för sig att vara mera extrem i sin programmering ;-) ). Sedan för varje varv så räknas värdet upp med 2, eftersom det är det vi angivet. Och vi fortsätter alltså räknar upp så länge som värdet på loop är mindre än eller lika med slutvärdet. Alltså får vi nummerserien 1,3,5,7,9 – 11 som skulle bli nästa värde är mer än 10 och alltså avbryts beräkningen.

Men om ni efter “loopen” försöker använda värdet av variabeln som ni använt för att veta vilket “varv” ni är i loopen, så kommer den variabel inte finnas kvar – den finns BARA under själva “for” bearbetningen!!! således ger följande kodsnutt:

for loop = 1, 10, 2 do
	fibaro:debug("Loop: "..loop)
end

fibaro:debug("Loop: "..loop)

Följande irreterande feltext:

[ERROR] 19:10:57: line 39: attempt to concatenate global ‘loop’ (a nil value)

Variabel “loop” finns inte längre efter “end” kodordet. Bara så ni vet att ni inte rakt av kan utnyttja värdet ;-)

Det finns flera sätt att “loopa” runt, man kan t.ex. använda en s.k. “while”-loop. Den bygger på lite annan metodik. Den “loopar” tills dess att villkoret man specificerat uppfylles. Ett “klassiskt” villkor är att “loopa” för evigt, och det antagligen mest använda:

while true do
<gör någonting>
end

Alltså kör för evigt. Ja det normala är nog att man, iallafall i LUA sammanhang på HC2, kanske har denna “loop” varje minut för att t.ex. kolla status på något, och då blir det:

LExecuteSecond = 60 -- Execute Every N-th second
while true do
<gör någonting>
--Sleep LExecuteSecond seconds
fibaro:sleep(LExecuteSecond*1000)
end

Dvs med “fibaro:sleep(x)” så kommer vi att låta scenen/VDn sova och inte göra något i x antal millisekunder, för att få det till sekunder får vi multiplicera med 1000. Men det är enbart dina krav och påhittighet som sätter gränser, t.ex. skulle man kunna “loopa” igenom en lista:

local loop=1
while loop < 10 do
	fibaro:debug("Loop: "..loop)
	loop=loop+1
end

Med andra ord samma resultat som tidigare när vi använda “for” – men med en viktig skillnad, variablen “loop” har nu ett lokalt värde hela tiden, och således kan du komma åt värdet efter loopen är klar.

Oberoende av om ni använder “for” eller “while” så ser ni förhoppningsvis att det finns ett “do” på slutet av raden, och det ordet beskriver vad du avser göra (what will you DO). Och det man syftar på är att det som står mellan “do” och “end” är det som utföres:

do
<gör något då>
end

Viktigt alltså att komma ihåg att man inleder med ett “do” och avslutar med ett “end”.

Om vi nu återgår till där jag försökte börja:

local DeviceList = { 7, 9, 13, 17 }

for loop,Device in pairs(DeviceList) do
	fibaro:debug("Index: "..loop.." DeviceID: "..Device)
end

Så ser vi alltså att vi har 4 värden i den lista som vi skapar, och döper till “DeviceList”. För att bekräfta vilka värden som ligger lagrade så använder vi en lite speciell funktion i LUA som håller reda på hur listan “ser ut” – vi vet ju inte alltid hur stor (antal element) listan är. Just nu har jag definierat fyra värden, alltså 4 element. Jag skulle kunna skriva:

local loop=1
while loop < 5 do
	fibaro:debug("Index: "..loop.." DeviceID: "..DeviceList[loop])
	loop=loop+1
end

Eller:

for loop = 1, 4 do
	fibaro:debug("Index: "..loop.." DeviceID: "..DeviceList[loop])
end

Det ger samma slutresultat som ovan där ordet “pairs()” används. Men om vi har en lista med 5 element i så får vi alltså bara se de första 4 om vi inte använder “pairs()”. Det är inte helt enkelt att förklara hur det hela hänger ihop, så jag avstår faktiskt från det, mer än att med “pairs()” så kommer man enbart bearbeta det antalet element som finns i den ordning de ligger i listan, och så som syntaxen fungerar så är det element i listan man bearbetar den som ligger i variable Device. Det hela styrs alltså av:

for loop,Device in pairs(DeviceList) do

Variabel loop är den plats man befinner sig på, index i listan om man så vill. Variabel Device är i sin tur värdet i listan DeviceList. Dvs om loop är = 1 så är värdet i Device = 7 osv. Som ni ser finns flera varianter på samma sak, och det är i princip bara att välja den man själv känner sig mest bekväm med.

Det finns flera varianter och den funktion vi nyttjar med “pairs()” är delvis förknippad med index listor (och det tänker jag inte gå in på). Så vi nöjer oss med att ja detta fungerar för oss ;-)

Så vad kan man då göra med listan ovan. Ja om man tänker på frågan om man kan tända flera lampor men i slumpad ordning – för om man hårdkodar tändordningen så kan man inte direkt säga att det är slumpen som styr i vilken ordning som lamporna tänds eller hur? Dessvärre finns ingen superb metod att skapa en slumpad lista, så vi får ta vår lista och skapa en egen form av slumphantering:

local DeviceList = { 7, 9, 13, 17 }

function fn_TableLength(T)
	local count = 0
	for _ in pairs(T) do count = count + 1 end
	return count
end

local NumberOfDevices = fn_TableLength(DeviceList)

for loop,Device in pairs(DeviceList) do
	IndexOne = math.random(1,NumberOfDevices)
	IndexTwo = math.random(1,NumberOfDevices)
	Temp = DeviceList[IndexOne]
	DeviceList[IndexOne] = DeviceList[IndexTwo]
	DeviceList[IndexTwo] = Temp   
end

for loop,Device in pairs(DeviceList) do
	fibaro:debug("Index: "..loop.." DeviceID: "..Device)
end

Och som ni ser så kommer nu även en ytterligare detalj fram, som iofs har med listor att göra, men som gör att ni även får testa på “funktioner”. En funktion är ungefär vad det låter som, i detta fallet så räknar funktionen fram antalet element som finns i listan:

local NumberOfDevices = fn_TableLength(DeviceList)

Det kallas för funktion eftersom funktionen returnerar ett värde, i detta fallet värdet 4 eftersom det finns 4 element i den lista jag skapat som heter “DeviceList”.

Efter att jag tagit reda på antalet element, så försöker jag med slumptalens hjälp byta plats på två åt gången, i “antal element” varv. Dvs 4 gånger kommer “for”-loopen byta två värden i listan, och nu har vi fått en lista som inte längre är i samma ordning som när vi definierade den överst i koden. T.ex. så kan det se ut såhär:

[DEBUG] 19:10:57: Index: 1 DeviceID: 13
[DEBUG] 19:10:57: Index: 2 DeviceID: 9
[DEBUG] 19:10:57: Index: 3 DeviceID: 7
[DEBUG] 19:10:57: Index: 4 DeviceID: 17

Inte direkt samma fina ordning, men skall man tända eller släcka lampor, så är det kanske en bra variant att “förvirra” tjuven.