Can't access value at array's zero index in a for loop using the default $index variable

arrays
fixed
loops

#1

The following example piston shows that there is some unexpected behavior with the special $index variable used by default in for loops. The output shows that for the zero index, the $index variable does not access the element in the array correctly:

+244ms	║letters[0]: A letters[index]: A
+319ms	║$index: 0 index: 0 letters[$index]: letters[index]: A
+394ms	║$index: 1 index: 1 letters[$index]: B letters[index]: B
+466ms	║$index: 2 index: 2 letters[$index]: C letters[index]: C

Instead, letters[$index] is null. Since both assigning $index to index and constructing the loop to use index as the counter variable work fine to access the zero index this seems like it might be a parsing or coercion issue. The easy workaround is to use a custom integer counter variable.


Code review wanted: notification suite with @recipients, urgency levels, repeating, and presence-based message queuing
#2

$index is a decimal while index is an integer. since list[] are named arrays, i think using a decimal as index throws it off.


#3

Ah, you’re right that is the important difference. Changing index to a decimal in the example above has the same effect. Writing to that index rather than reading produces a value at '0' just like an integer so it seems to just be weird for accessing the value.

Admittedly using integers as keys in a named array and tracking a length property manually is a hack to begin with but at least now I can use zero-indexed arrays confidently. Support for arrays in for each would be an excellent future improvement.


#4

i am waiting for:

define const variable list[] names = {'0:Name 0', '1:Name 1', '2:Name 2'}

so we dont have to sutff a list every execution of the piston. :slight_smile:


#5

@ipaterson what are you using this for?


#6

Primarily for the notification suite, there are a few queues in there where messages can wait until the recipient is present or be re-sent on an interval. Also, since there doesn’t seem to be a way to delete values once they are set in an array there are a few places where I need to use length to keep track of the total number of items in the current execution and to allow iterating other arrays.

So for a more concrete example, I have string[] recipientsName and boolean[] recipientsPresence. The piston is configured with any number of recipients, where recipientsName will end up looking like { 0: 'Jane', 1: 'John', length: '2' }. recipientsPresence is keyed off of the names and whether the associated devices are currently present and will end up looking like { 'Jane': true, 'John': false }.

I can’t iterate recipientsPresence directly because for each only works with devices and there is no keys() function or equivalent to extract the keys from recipientsPresence. So, I iterate recipientsName with a for loop from 0 to int(recipientsName[length]) - 1 and then use the value of recipientsName[index] to access recipientsPresence. Or something like that, I’m in the middle of reworking it :slight_smile:


#7

have you considered setting up a device variable with all the presence sensors and iterating in a for each loop over that variable? device variables essentially act like a list[] in that you can do for each on.


#8

Yep, but I use multiple devices for each person and also need the recipient names for parsing messages. My example was not very good, I’m actually checking presence for a known recipient (just recipientsPresence[name]) rather than iterating the presence statuses. Should have looked at the code rather than working from memory…

A more appropriate example is string[] repeatingKeys and string[] repeatingMessages. You can have only one active message for each arbitrary key but I need to keep track of those keys in order to iterate and fire off the repeating messages. For example, execute the piston with “@repeat:washer Washing machine is finished” and you’ll get repeatingKeys[0] = 'washer', repeatingKeys[length] = 1 and repeatingMessages[washer] = 'Washing machine is finished'. Then I can iterate repeatingKeys to fire off the corresponding messages.

Edit: I’m looking into arrayItem(), it seems to let me access named arrays by integer indexes which could cut down on some of the foolishness.


#9

i do something similar and use a define section for lists at the bottom of the execute section.

define
   variable device presenceSensors = presence sensor 11, presence sensor 12, 
                                     presence sensor 2, presence sensor 31, 
                                     presence sensor 32
   variable string[] names
end define

if piston tile 1 executes 3 times within 10 seocnds
   set names[presence sensor 11] = 'Name 1'
   set names[presence sensor 12] = 'Name 1'
   set names[presence sensor 2] = 'Name 2'
   set names[presence sensor 31] = 'Name 3'
   set names[presence sensor 32] = 'Name 3'
end if

then direct access names[device] in rest of the piston to get the name.

i was writing the arraryitem part up when i saw your edit, so leaving that out. also, list[], arrayitem, indexof and device variable all usually go together in a piston of this nature. :slight_smile:


#10

Thanks for your help so far! So I can use arrayItem to get what looks like a key/value pair but I haven’t figured out what the return type is or how best to use it.

arrayitem

This logs the following:

+297ms	║A:a
+307ms	║ B:b
+318ms	║  C:c
+334ms	║  C:c

The dynamic thing value is displayed as:

thing

I’m not quite sure what to make of this. It does something that is very useful to me since I can eliminate at least the repeatingKeys array but I’m not sure if there is an easier way to use it than key = replace(arrayItem(0, repeatingMessages), '/^\s*(.*?):.*/', '$1') to in this example extract C as the key which I can use to look up the value. This certainly works for me, just curious what else I may be missing!


Correct Way to Do Array?
"Device List" Variable Type
#11

you are welcome.

i would give it a try if i had that example so not sure if dynamic thing is accessible as a list?

whcih piston are you referring to for the repeatingKeys array?


#12

The return value doesn’t seem to be a list since arrayItem(0, arrayItem(2, stuff)) is null.

@ady624 is this use of arrayItem() with non-device lists leveraging some undefined behavior? I don’t want to use it if there’s a high likelihood that it will change later or if there is something better, but for this case it will avoid having a separate iterable array that tracks the keys. It’s just a bit ugly to get the key out of the string with substring(arrayItem(index, stuff), 1, indexOf(arrayItem(index, stuff), ':') - 1).

Possibly more useful for iterating named arrays would be an arrayKey(1, stuff) and arrayValue(1, stuff) that return the key and value at that index (it seems the index in arrayItem is already based on alpha-sorted keys). Fortunately count(stuff) can already be used to get the total number of elements.


#13

I noticed that the return value is a bit different with numeric-keyed arrays. Rather than the key:value format, it’s just value so I just used trim(arrayItem(index, $json)) to retrieve each value as seen here:

Above is an example of using a JSON formatted array rather than a silly number of Set Variable statements for a situation where I needed a constant array. The messages value is an expression to leverage the multi-line textarea input – very convenient that multi-line strings are supported! (note the single quotes at the start and end of this expression)


#14

yeah, if you are setting a handful of them i find set variables are easier. but for what you are doing, something like this is simpler:

messages value is an expression that leverages the multi-line textarea input:

i just did a set variable here for an example of doing this with one set variable instead of silly number of set variables. in your example you would set message with the expression on the right side before calling notify. :slight_smile:

EDIT: o yeah, forgot this picture:


#15

Whoa whoa whoa now… I can just use a comma-separated string as an array? That is life-changing… well, at least within the scope of this weekend. Again, thank you!


#16

Ouch, so this explains everything… The named arrays appear as key:value because these functions (count, arrayItem) are operating on the stringified value rather than an actual array. If a key or value happens to contain a comma it’s going to throw everything off.

count

This array with only one item { 'foo': 'bar,baz' } shows a count of 2. Iterating this array with arrayItem gives elements foo:bar and baz. I’m going to have to substitute commas in any messages that I store to an array with a certain character that can be switched back later…


#17

you are welcome.


#18

unless you can convince @ady624 to also expose the delimiter :wink:


Code review wanted: notification suite with @recipients, urgency levels, repeating, and presence-based message queuing
#19

Ok, this is the info I’ve been seeking. Can’t wait to begin playing with it.

My objective: have an incoming variable from Alexa trigger changes on a Bravia TV. The DTH already makes input, channel#, volume, channel up/down, and other functions available in webcore.

So the first step will be to see if I can get an array configuration that will permit a function to be output and executed from within an array. So that, for example, if I set the variable ‘ALEXA_CH’ to ‘ch2’, it correlates to the DTH function num2( ) and gets output to, and processed by, the device driver.

From there, it would be a question of writing an Alexa skill to output that variable to the endpoint of the piston.
But one step at a time.


#20

This zero $index bug is now fixed as of v0.2.101.20171227. It turned out to be impossible to access the zero index of any array using a decimal because a series of casting operations resulted in trying to access a null key.