Community discussions

MikroTik App
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 1:29 pm

I need to iterate over all elements of an array of unknown dimension (like a Cartesian identity)
The problem must be solved by recursively calling a function to traverse the entire tree with an unknown number and size of "branches" in advance
At the same time, there is no need to reinvent the wheel, there is practically a similar function for Mikrotik - here it is (the author of the great Chupaka,
https://github.com/Winand/mikrotik-json-parser)
# ------------------------------- fJParsePrint ----------------------------------------------------------------
:global fJParsePrint
:if (!any $fJParsePrint) do={ :global fJParsePrint do={
  :global JParseOut
  :local TempPath
  :global fJParsePrint

  :if ([:len $1] = 0) do={
    :set $1 "\$JParseOut"
    :set $2 $JParseOut
   }
   
  :foreach k,v in=$2 do={
    :if ([:typeof $k] = "str") do={
      :set k "\"$k\""
    }
    :set TempPath ($1. "->" . $k)
    :if ([:typeof $v] = "array") do={
      :if ([:len $v] > 0) do={
        $fJParsePrint $TempPath $v
      } else={
        :put "$TempPath = [] ($[:typeof $v])"
      }
    } else={
        :put "$TempPath = $v ($[:typeof $v])"
    }
  }
}}
But I don't need to print the paths to subarrays and element values, but iterate over their values ​​and replace them with others.

How to do it ? Who can help ? Maybe our Dear Guru Rextended?
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 2:49 pm

Hi,

I can help you, but I must first have to understand correctly what is need.

Provide one array for example and what do you want to do with that array.
No matter if is correct syntax, you can use pseudoinstructions.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 3:12 pm

Example bi-dimensional x array of y array:
:global test {{"A";"D";"I";"P";"Y"};{"B";"C";"H";"O";"X"};{"E";"F";"G";"N";"W"};{"J";"K";"L";"M";"V"};{"Q";"R";"S";"T";"U"}}
:put $x
:put ($test->0->0)
:put ($test->0->1)
:put ($test->0->2)
:put ($test->0->3)
:put ($test->0->4)
:put ($test->1->0)
:put ($test->1->1)
:put ($test->1->2)
:put ($test->1->3)
:put ($test->1->4)
:put ($test->2->0)
:put ($test->2->1)
:put ($test->2->2)
:put ($test->2->3)
:put ($test->2->4)
:put ($test->3->0)
:put ($test->3->1)
:put ($test->3->2)
:put ($test->3->3)
:put ($test->3->4)
:put ($test->4->0)
:put ($test->4->1)
:put ($test->4->2)
:put ($test->4->3)
:put ($test->4->4)

visual code

y
4 Y X W V U
3 P O N M T
2 I H G L S
1 D C F K R
0 A B E J Q
  0 1 2 3 4 x
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 3:19 pm

It's hard for me to give an example, but I'll try.
Imagine that there is an array containing as elements not only values, but also arrays. You need to go through all the values ​​of the elements of this tree and assign them other values ​​(for example, process them with your $myFunc function).

Let the input tree array be in $ArrayIN
I'm assuming it should look something like this, but there are probably bugs in my code:
:global fJParseArray do={
  :local TempPath
  :global fJParseArray
   
  :if ([:len $1] = 0) do={
    :set $1 "\$ArrayIn"
    :set $2 $ArrayIn
   }

  :foreach k,v in=$2 do={
    :if ([:typeof $k] = "str") do={
      :set k "\"$k\""
    }
    :set TempPath ($1. "->" . $k)
    :if ([:typeof $v] = "array") do={
      :if ([:len $v] > 0) do={
        $fJParseArray $TempPath $v
      } else={
        :set ($TempPath) []
      }
    } else={
        :set ($TempPath) [$MyFunc $v]
    }
  }
}
For an example of what ArrayIn contains (paths to values):

$ArrayIn->"ok" = true (bool)
$ArrayIn->"result"->"has_custom_certificate" = false (bool)
$ArrayIn->"result"->"last_error_date" = 1524483204 (num)
$ArrayIn->"result"->"last_error_message" = Connection timed out (str)
$ArrayIn->"result"->"max_connections" = 40 (num)
$ArrayIn->"result"->"pending_update_count" = 0 (num)
$ArrayIn->"result"->"url" = https://*****.ru:8443 (str)
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 3:21 pm

Example bi-dimensional x array of y array, It's too easy. We initially have an array with an unknown data nesting level
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 3:24 pm

I must go away to the office I came later and I reply to you, on meantime:
:global test {{"A";"D";"I";"P";"Y"};{"B";"C";"H";"O";"X"};{"E";"F";"G";"N";"W"};{"J";"K";"L";"M";"V"};{"Q";"R";"S";"T";"U"}}

:foreach x in=$test do={
    :foreach y in=$x do={
        :put "$[:find $test $x],$[:find $x $y] = $y"
    }
}
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 3:54 pm

Do you think that in this way we can process all elements of arrays, including key associative arrays?
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 4:23 pm

Here is an example of an array to be traversed. Note that it contains other arrays as elements.

alertList=;audioSettings=DoP=title=DoP playback;type=boolean;value=false;autoPlay=title=AutoPlay after boot;type=boolean;value=false;soundCard=data=id=0;name=Default;title=Sound Card;type=spinner;value=0;soundType=data=id=0;name=Mono Differential;id=1;name=Stereo;title=Sound Type;type=spinner;value=1;title=Audio settings;broadcastModeSettings=broadcastModeOptions=downloadURL=;streamURL=;broadcastType=data=id=0;name=Default;id=1;name=Stream URL;id=2;name=Download URL;title=Broadcast Type;type=spinner;value=0;enabled=false;title=Broadcast mode settings;deviceInfo=autoUpgrade=true;autoUpgradeInstall=true;cpuTemp=48°C;version=2.16.37;multiroomSettings=masterMode=false;slaveList=;networkSettings=connections=LAN=addresses=192.168.0.101/24;dns=192.168.0.1;gateway=192.168.0.1;method=auto;state=connected;WLAN=state=disconnected;interfaces=LAN=title=Ethernet;type=boolean;value=true;WLAN=title=Wi-Fi;type=boolean;value=false;title=Network settings;wifiList=
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 4:32 pm

And here are the paths to the elements of each array from my example above, indicating the value of the element and its type:

ArrayIN->"alertList" = [] (array)
ArrayIN->"audioSettings"->"DoP"->"title" = DoP playback (str)
ArrayIN->"audioSettings"->"DoP"->"type" = boolean (str)
ArrayIN->"audioSettings"->"DoP"->"value" = false (bool)
ArrayIN->"audioSettings"->"autoPlay"->"title" = AutoPlay after boot (str)
ArrayIN->"audioSettings"->"autoPlay"->"type" = boolean (str)
ArrayIN->"audioSettings"->"autoPlay"->"value" = false (bool)
ArrayIN->"audioSettings"->"soundCard"->"data"->0->"id" = 0 (num)
ArrayIN->"audioSettings"->"soundCard"->"data"->0->"name" = Default (str)
ArrayIN->"audioSettings"->"soundCard"->"title" = Sound Card (str)
ArrayIN->"audioSettings"->"soundCard"->"type" = spinner (str)
ArrayIN->"audioSettings"->"soundCard"->"value" = 0 (num)
ArrayIN->"audioSettings"->"soundType"->"data"->0->"id" = 0 (num)
ArrayIN->"audioSettings"->"soundType"->"data"->0->"name" = Mono Differential (str)
ArrayIN->"audioSettings"->"soundType"->"data"->1->"id" = 1 (num)
ArrayIN->"audioSettings"->"soundType"->"data"->1->"name" = Stereo (str)
ArrayIN->"audioSettings"->"soundType"->"title" = Sound Type (str)
ArrayIN->"audioSettings"->"soundType"->"type" = spinner (str)
ArrayIN->"audioSettings"->"soundType"->"value" = 1 (num)
ArrayIN->"audioSettings"->"title" = Audio settings (str)
ArrayIN->"broadcastModeSettings"->"broadcastModeOptions"->"downloadURL" = (str)
ArrayIN->"broadcastModeSettings"->"broadcastModeOptions"->"streamURL" = (str)
ArrayIN->"broadcastModeSettings"->"broadcastType"->"data"->0->"id" = 0 (num)
ArrayIN->"broadcastModeSettings"->"broadcastType"->"data"->0->"name" = Default (str)
ArrayIN->"broadcastModeSettings"->"broadcastType"->"data"->1->"id" = 1 (num)
ArrayIN->"broadcastModeSettings"->"broadcastType"->"data"->1->"name" = Stream URL (str)
ArrayIN->"broadcastModeSettings"->"broadcastType"->"data"->2->"id" = 2 (num)
ArrayIN->"broadcastModeSettings"->"broadcastType"->"data"->2->"name" = Download URL (str)
ArrayIN->"broadcastModeSettings"->"broadcastType"->"title" = Broadcast Type (str)
ArrayIN->"broadcastModeSettings"->"broadcastType"->"type" = spinner (str)
ArrayIN->"broadcastModeSettings"->"broadcastType"->"value" = 0 (num)
ArrayIN->"broadcastModeSettings"->"enabled" = false (bool)
ArrayIN->"broadcastModeSettings"->"title" = Broadcast mode settings (str)
ArrayIN->"deviceInfo"->"autoUpgrade" = true (bool)
ArrayIN->"deviceInfo"->"autoUpgradeInstall" = true (bool)
ArrayIN->"deviceInfo"->"cpuTemp" = 48 C (str)
ArrayIN->"deviceInfo"->"version" = 2.16.37 (str)
ArrayIN->"multiroomSettings"->"masterMode" = false (bool)
ArrayIN->"multiroomSettings"->"slaveList" = [] (array)
ArrayIN->"networkSettings"->"connections"->"LAN"->"addresses" = 192.168.0.101/24 (str)
ArrayIN->"networkSettings"->"connections"->"LAN"->"dns" = 192.168.0.1 (str)
ArrayIN->"networkSettings"->"connections"->"LAN"->"gateway" = 192.168.0.1 (str)
ArrayIN->"networkSettings"->"connections"->"LAN"->"method" = auto (str)
ArrayIN->"networkSettings"->"connections"->"LAN"->"state" = connected (str)
ArrayIN->"networkSettings"->"connections"->"WLAN"->"state" = disconnected (str)
ArrayIN->"networkSettings"->"interfaces"->"LAN"->"title" = Ethernet (str)
ArrayIN->"networkSettings"->"interfaces"->"LAN"->"type" = boolean (str)
ArrayIN->"networkSettings"->"interfaces"->"LAN"->"value" = true (bool)
ArrayIN->"networkSettings"->"interfaces"->"WLAN"->"title" = Wi-Fi (str)
ArrayIN->"networkSettings"->"interfaces"->"WLAN"->"type" = boolean (str)
ArrayIN->"networkSettings"->"interfaces"->"WLAN"->"value" = false (bool)
ArrayIN->"networkSettings"->"title" = Network settings (str)
ArrayIN->"networkSettings"->"wifiList" = [] (array)
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3250
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 5:06 pm

I'd listen to @rextended here.

But this may give you another example of using recursive functions to iterate over an array. The trick is you have check if it's "array" type to know to call it recursively. Basically instead of the :put's in the code, you could use a :set for your use case. See:
viewtopic.php?t=191480#p970794
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 5:29 pm

Thank you very match, Amm !

But :set no working ...

I tri :
:global ParseArray do={
  :global ArrayIn
  :local TempPath
  :global ParseArray
   
  :if ([:len $1] = 0) do={
    :set $1 "\$ArrayIn"
    :set $2 $ArrayIn
   }

  :foreach k,v in=$2 do={
    :if ([:typeof $k] = "str") do={
        :set k "\"$k\""
    }
    :set TempPath ($1. "->" . $k)
    :parse [[":set (\$$TempPath) hello"]]
    :if ([:typeof $v] = "array") do={
      :if ([:len $v] > 0) do={
        $ParseArray $TempPath $v
      } else={
        :set ($TempPath) []
      }
    } else={

#       [[:parse ":set ($TempPath) hello"]]
    }
  }
}
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3250
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: iterate over all elements of an array of unknown dimension

Tue Dec 20, 2022 7:24 pm

But :set no working ...
It's hard to answer. It's less code to change values in the same "associative" array (e.g. like map() in other programming languages). While you can creating some other structure than the original (in a reduce() in CS terms), this is more code and becomes dependent on the specifics of what you'd want to do.
:global FLATTEN do={
  :global FLATTEN
  :local memo [:toarray $2]
  :local FNPUT true
  :foreach i,k in=$1 do={
    :if ([:typeof $k]="array") do={      
        :if ($FNPUT) do={:put "$[:tostr $i]=(array)"}
        :set memo [$FLATTEN $k $memo]
    } else={
      :if ($FNPUT) do={:put "$[:tostr $i]=$[:tostr $k] ;$[:typeof $k]"}
      :set memo {$memo; [:toarray "$i , $k"]}
    }
  }
  :return $memo
 }


In this example, you call the function with your original array, and it will return a new array with all the values as string. e.g.
:global ParseArray [$FLATTEN $ArrayIn ""]

What you're code doing is assuming using the original array inside the function – this can work – but does make the code more complex than keeping the map/reduce() function "generic" by accepting the original array as the 1st param, and the output array is the 2nd (and this 2nd parameter is what's returned when all recursion is done, containing an "indexed" array with strings of all values from $ArrayIn).

edit: forgot }
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 2:39 am

This is your array example created with RouterOS syntax, for work over that array.

The only difference with what you wrote: I replace empty array alertList, slaveList and wifiList with {"arrayvalue1","arrayvalue2"} for testing purpose.

Array code

:global ArrayIN { "alertList"="arrayvalue1","arrayvalue2";
                  "audioSettings"={ "DoP"={ "title"="DoP playback";
                                            "type"="boolean";
                                            "value"=false
                                          };
                                    "autoPlay"={ "title"="AutoPlay after boot";
                                                 "type"="boolean";
                                                 "value"=false
                                               };
                                    "soundCard"={ "data"={ {"id"=0;"name"="Default"}
                                                         };
                                                  "title"="Sound Card";
                                                  "type"="spinner";
                                                  "value"=0
                                                };
                                    "soundType"={ "data"={ {"id"=0;"name"="Mono Differential"};
                                                           {"id"=1;"name"="Stereo"}
                                                         };
                                                  "title"="Sound Type";
                                                  "type"="spinner";
                                                  "value"=1
                                                };
                                    "title"="Audio settings"
                                  };
                  "broadcastModeSettings"={ "broadcastModeOptions"={ "downloadURL"="no URL provided";
                                                                     "streamURL"="no URL provided"
                                                                   };
                                            "broadcastType"={ "data"={ {"id"=0;"name"="Default"};
                                                                       {"id"=1;"name"="Stream URL"};
                                                                       {"id"=2;"name"="Download URL"}
                                                                     };
                                                              "title"="Broadcast Type";
                                                              "type"="spinner";
                                                              "value"=0
                                                            };
                                            "enabled"=false;
                                            "title"="Broadcast mode settings"
                                          };
                  "deviceInfo"={ "autoUpgrade"=true;
                                 "autoUpgradeInstall"=true;
                                 "cpuTemp"="48 C";
                                 "version"="2.16.37"
                               };
                  "multiroomSettings"={ "masterMode"=false;
                                        "slaveList"="arrayvalue1","arrayvalue2"
                                      };
                  "networkSettings"={ "connections"={ "LAN"={ "addresses"="192.168.0.101/24";
                                                              "dns"="192.168.0.1";
                                                              "gateway"="192.168.0.1";
                                                              "method"="auto";
                                                              "state"="connected"
                                                            };
                                                      "WLAN"={ "state"="disconnected" }
                                                    };
                                      "interfaces"={ "LAN"={ "title"="Ethernet";
                                                             "type"="boolean";
                                                             "value"=true
                                                           };
                                                     "WLAN"={ "title"="Wi-Fi";
                                                              "type"="boolean";
                                                              "value"=false
                                                            }
                                                   };
                                      "title"="Network settings";
                                      "wifiList"="arrayvalue1","arrayvalue2"
                                    };
}
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 3:17 am

Function updated, see:
viewtopic.php?t=191832#p973367
Last edited by rextended on Wed Aug 30, 2023 10:54 am, edited 2 times in total.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 4:35 am

Example:
Replace inside ArrayIN on "networkSettings" / "connections" / "LAN" / "addresses" from "192.168.0.101/24" to "192.168.0.102/24"

Obviously if already we know all path, is useless, because just set
:set ($ArrayIN->"networkSettings"->"connections"->"LAN"->"addresses") "192.168.0.102/24"

But if the path is unknow and containing only one "addresses" (if not, is replaced only the first, for check for more, the script must be modified)
:global searchpath do={ :global searchpath
                            :local path "$4"
                            /system script environment
                            :foreach j in=[find] do={
                                :if ([get $j value] = $1) do={:set path "\$$[get $j name]"}
                            }
                            :foreach x,y in=$1 do={
                                :local lpath $path
                                :if ([:typeof $x] = "str") do={:set lpath "$path->\"$x\""} else={:set lpath "$path->$x"}
                                :if (($x = $2) and ($y = $3)) do={
                                    :return "$lpath"
                                } else={
                                    :if ([:typeof $y] = "array") do={
                                        :local ret [$searchpath $y $2 $3 $lpath]
                                        :if ($ret != "KO") do={:return $ret}
                                    }
                                }
                            }
                            :return "KO"
                      }

{
# show previous value
:put ($ArrayIN->"networkSettings"->"connections"->"LAN"->"addresses")
# show current path
:put [$searchpath $ArrayIN "addresses" "192.168.0.101/24"]
# execute the substitution of value
[[:parse (":global ArrayIN; :set ($[$searchpath $ArrayIN "addresses" "192.168.0.101/24"]) \"192.168.0.102/24\"")]]
# show new value
:put ($ArrayIN->"networkSettings"->"connections"->"LAN"->"addresses")
}

This is what you need?
Last edited by rextended on Wed Dec 21, 2022 3:18 pm, edited 1 time in total.
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 7:26 am

Thank you, very match, Rextended and Amm0 !
:global fJParsePrint
:if (!any $fJParsePrint) do={ :global fJParsePrint do={
  :global JParseOut
  :local TempPath
  :global fJParsePrint

  :if ([:len $1] = 0) do={
    :set $1 "\$JParseOut"
    :set $2 $JParseOut
   }
   
  :foreach k,v in=$2 do={
    :if ([:typeof $k] = "str") do={
      :set k "\"$k\""
    }
    :set TempPath ($1. "->" . $k)
    :if ([:typeof $v] = "array") do={
      :if ([:len $v] > 0) do={
        $fJParsePrint $TempPath $v
      } else={
        :put "$TempPath = [] ($[:typeof $v])"
      }
    } else={
        :put "$TempPath = $v ($[:typeof $v])"
    }
  }
}}
I will definitely think about and test your solutions. But I would like to ask you to make edits to the code that Chupaka proposed, since it seems to me shorter, clearer and more perfect.
In theory, the Chupaka code is absolutely universal, since it provides a search of all elements of the array, regardless of the composition of its values and depth, by forming a path to the value through recursion. I would like to use it and it would be useful to everyone.
Note that it generates the full path to the value in the string
:set TempPath ($1. "->" . $k).
You just need to add :set to it in order to replace the values of the array when iterating through it where necessary.
I think I need to form :set by
 [[:parse ":set (\$$TempPath) $newvalue"]]
, but somewhere I have an error in it, because set does not work like that. Please look at this code with your "experienced eye".
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 9:26 am

I think that to iterate through the entire tree in order to replace the final values, you should end up with something like this code:
:global ParseArray do={
  :global ArrayIn
  :local TempPath
  :global ParseArray
   
  :if ([:len $1] = 0) do={
    :set $1 "\$ArrayIn"
    :set $2 $ArrayIn
   }

  :foreach k,v in=$2 do={
    :if ([:typeof $k] = "str") do={
        :set k "\"$k\""
    }
    :set TempPath ($1. "->" . $k)
    :if ([:typeof $v] = "array") do={
      :if ([:len $v] > 0) do={
        $ParseArray $TempPath $v
      } else={
         [[:parse ":set (\$$TempPath) []"]]
      }
    } else={
       [[:parse ":set (\$$TempPath) $newvalue"]]
    }
  }
}
Here $newvalue is the new value. I think that I have a mistake in the formation of the construction :parse - [[:parse ":set (\$$TempPath) $newvalue"], therefore :set does not work.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 2:17 pm

[...] I would like to ask you to make edits to the code that Chupaka proposed, since it seems to me shorter, clearer and more perfect.
It seems to me the opposite, not that Chupaka hasn't done a good job, but I find it hard to follow it, as it is written, since I didn't write it.

In theory, the Chupaka code is absolutely universal, since it provides a search of all elements of the array, regardless of the composition of its values and depth, by forming a path to the value through recursion.
But didn't you realize that the same description can be applied to my code on previous post?
viewtopic.php?t=191832#p973299

Note that it generates the full path to the value in the string
:set TempPath ($1. "->" . $k).
You just need to add :set to it in order to replace the values of the array when iterating through it where necessary.
I think I need [...], but somewhere I have an error in it, because set does not work like that.
My code already work, see the example included (and read the note).....

Please look at this code with your "experienced eye".
Read first reply o this topic...


Differencies on the two functions, based on your description:

Your modified Chupaka function, not writed directly from Chupaka, iterate on all array,
and during that iteraction replace values....
But you replace values on TEMPORARY array used to construct the path, not directly to the source array...

My function simply return KO if no occurrency found, or the full path to the value on the array.
On 2nd time, with that info, you can update the true array, not one temporary copy.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 2:51 pm

...
Last edited by rextended on Wed Dec 21, 2022 2:59 pm, edited 4 times in total.
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 2:53 pm

But you replace values on TEMPORARY array used to construct the path, not directly to the source array...

[[:parse ":set (\$$TempPath) $newvalue"]]

I can agree with everything, but with this... I don 't understand why ?

For example:

:set ($ArrayIN->"NetworkSettings"->"address") newaddress

After all, a specific path to the value is formed in this line and set to this value is executed. Does :parse in this case not work with a specific value of a specific array ? Why temporary ?

By the way, looking at these lines now, I think I understood the reason for my failures - $ArrayIN was formed in quotation marks, but it should be without them!
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 3:00 pm

The problem is simple, all is not clear,
what you try to obtain?


This function is similar of your output
viewtopic.php?t=191832#p973231

but my version provide more details:
:global revealfields do={ :global revealfields
                          :local path "$2"
                          /system script environment
                          :foreach j in=[find] do={
                              :if ([get $j value] = $1) do={:set path "\$$[get $j name]"}
                          }
                          :put "$path BEGIN ARRAY of $[:len $1] elements"
                          :foreach x,y in=$1 do={
                              :local typex [:typeof $x]
                              :local typey [:typeof $y]
                              :local lpath $path
                              :if ($typex = "str") do={:set lpath "$path->\"$x\""} else={:set lpath "$path->$x"}
                              :if ([:typeof $y] = "array") do={
                                  :if ([:len $y] > 0) do={
                                      :put "$lpath ($typex) BEGIN ARRAY of $[:len $y] elements"
                                      [$revealfields $y $lpath]
                                      :put "$lpath ($typex) END ARRAY"
                                   } else={
                                      :put "$lpath ($typex) = [] EMPTY ARRAY"
                                   }
                              } else={
                                  :put "$lpath ($typex) = $y ($typey)"
                              }
                          }
                          :put "$path END ARRAY"
                          :return "OK"
                        }


:put [$revealfields $ArrayIN]
Last edited by rextended on Wed Aug 30, 2023 10:56 am, edited 1 time in total.
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 3:28 pm

/system script environment
:foreach j in=[find] do={
:if ([get $j value] = $1) do={:set path "\$$[get $j name]"}
}

Very intresting code !

I am beginning to understand that the problem is probably related to the peculiarity of the router when copying arrays - not to do this, but to pass the array by pointer. I was warned about this by more experienced comrades. The output of your function showed this. Thank you very much. I'll figure it out a little more and post the conclusion.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 3:31 pm

This script generate exactly the same output as copied Chupaka, but for me is more readable:

equivalent code

:global shortrfields do={ :global shortrfields
                          :local path "$2"
                          /system script environment
                          :foreach j in=[find] do={
                              :if ([get $j value] = $1) do={:set path "\$$[get $j name]"}
                          }
                          :foreach x,y in=$1 do={
                              :local lpath $path
                              :if ([:typeof $x] = "str") do={:set lpath "$path->\"$x\""} else={:set lpath "$path->$x"}
                              :if ([:typeof $y] = "array") do={
                                  :if ([:len $y] > 0) do={
                                      [$shortrfields $y $lpath]
                                   } else={
                                      :put "$lpath = [] (array)"
                                   }
                              } else={
                                  :put "$lpath = $y ($[:typeof $y])"
                              }
                          }
                          :return "OK"
                        }


:put [$shortrfields $ArrayIN]
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 3:33 pm

Lesson 1) MIKROTIK DO NOT HAVE POINTER.
If on the past pointer work, is only one error and is "fixed".
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 3:43 pm

By
:global Array1 $Array2

Microtik does not copy Array2 data to Array1, but passes the array by reference. Has this bug been fixed ? Or its no bug ?
Last edited by Sertik on Wed Dec 21, 2022 3:56 pm, edited 1 time in total.
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 3:51 pm

Let 's digress a little from the topic ... I previously reported that when trying to call a function twice in a ROW, Microtik executes it three times. This is easy to verify:

:global myFunc do={:log info ok}
[$myFunc]
[$myFunc]

We called the function twice ...
But "ok" will be printed 3 times !

ok
ok
ok

Is this bug fixed too ? :)

An absolutely obvious and shameful bug, but Microtik stubbornly does not want to fix it, although I wrote to them about it more than two years ago ...

Interestingly, in this case, maybe it's not a bug, but some kind of trick of the company?
Last edited by Sertik on Wed Dec 21, 2022 4:13 pm, edited 4 times in total.
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 3:55 pm

In my functions, I have to defend myself from this developer error in this way:
:global my Func do={
:if [:typeof $0]="lookup" do={ .... my function code
 }
}
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 4:06 pm

But, back to our topic... Here is the output of your function:

$answer->"alertList" = [] (array)
$answer->"audioSettings"->"DoP"->"title" = DoP playback (str)
$answer->"audioSettings"->"DoP"->"type" = boolean (str)
$answer->"audioSettings"->"DoP"->"value" = false (bool)
$answer->"audioSettings"->"autoPlay"->"title" = AutoPlay after boot (str)
$answer->"audioSettings"->"autoPlay"->"type" = boolean (str)
$answer->"audioSettings"->"autoPlay"->"value" = false (bool)
$answer->"audioSettings"->"soundCard"->"data"->0->"id" = 0 (num)
$answer->"audioSettings"->"soundCard"->"data"->0->"name" = Default (str)
$answer->"audioSettings"->"soundCard"->"title" = Sound Card (str)
$answer->"audioSettings"->"soundCard"->"type" = spinner (str)
$answer->"audioSettings"->"soundCard"->"value" = 0 (num)
$answer->"audioSettings"->"soundType"->"data"->0->"id" = 0 (num)
$answer->"audioSettings"->"soundType"->"data"->0->"name" = Mono Differential (str)
$answer->"audioSettings"->"soundType"->"data"->1->"id" = 1 (num)
$answer->"audioSettings"->"soundType"->"data"->1->"name" = Stereo (str)
$answer->"audioSettings"->"soundType"->"title" = Sound Type (str)
$answer->"audioSettings"->"soundType"->"type" = spinner (str)
$answer->"audioSettings"->"soundType"->"value" = 1 (num)
$answer->"audioSettings"->"title" = Audio settings (str)
$answer->"broadcastModeSettings"->"broadcastModeOptions"->"downloadURL" = (str)
$answer->"broadcastModeSettings"->"broadcastModeOptions"->"streamURL" = (str)
$answer->"broadcastModeSettings"->"broadcastType"->"data"->0->"id" = 0 (num)
$answer->"broadcastModeSettings"->"broadcastType"->"data"->0->"name" = Default (str)
$answer->"broadcastModeSettings"->"broadcastType"->"data"->1->"id" = 1 (num)
$answer->"broadcastModeSettings"->"broadcastType"->"data"->1->"name" = Stream URL (str)
$answer->"broadcastModeSettings"->"broadcastType"->"data"->2->"id" = 2 (num)
$answer->"broadcastModeSettings"->"broadcastType"->"data"->2->"name" = Download URL (str)
$answer->"broadcastModeSettings"->"broadcastType"->"title" = Broadcast Type (str)
$answer->"broadcastModeSettings"->"broadcastType"->"type" = spinner (str)
$answer->"broadcastModeSettings"->"broadcastType"->"value" = 0 (num)
$answer->"broadcastModeSettings"->"enabled" = false (bool)
$answer->"broadcastModeSettings"->"title" = Broadcast mode settings (str)
$answer->"deviceInfo"->"autoUpgrade" = true (bool)
$answer->"deviceInfo"->"autoUpgradeInstall" = true (bool)
$answer->"deviceInfo"->"cpuTemp" = 55 C (str)
$answer->"deviceInfo"->"version" = 2.16.37 (str)
$answer->"multiroomSettings"->"masterMode" = false (bool)
$answer->"multiroomSettings"->"slaveList" = [] (array)
$answer->"networkSettings"->"connections"->"LAN"->"addresses" = 192.168.0.101/24 (str)
$answer->"networkSettings"->"connections"->"LAN"->"dns" = 192.168.0.1 (str)
$answer->"networkSettings"->"connections"->"LAN"->"gateway" = 192.168.0.1 (str)
$answer->"networkSettings"->"connections"->"LAN"->"method" = auto (str)
$answer->"networkSettings"->"connections"->"LAN"->"state" = connected (str)
$answer->"networkSettings"->"connections"->"WLAN"->"state" = disconnected (str)
$answer->"networkSettings"->"interfaces"->"LAN"->"title" = Ethernet (str)
$answer->"networkSettings"->"interfaces"->"LAN"->"type" = boolean (str)
$answer->"networkSettings"->"interfaces"->"LAN"->"value" = true (bool)
$answer->"networkSettings"->"interfaces"->"WLAN"->"title" = Wi-Fi (str)
$answer->"networkSettings"->"interfaces"->"WLAN"->"type" = boolean (str)
$answer->"networkSettings"->"interfaces"->"WLAN"->"value" = false (bool)
$answer->"networkSettings"->"title" = Network settings (str)
$answer->"networkSettings"->"wifiList" = [] (array)

It just shows that I gave the function an array ArrayIN, and the data is actually stored in the array $answer, which initially receives output from me! That is, it confirms that Lua RouterOS does not copy data (although I did :global ArrayIN $answer, and passes the array by reference. But of course you know all this much better than I do. I'm asking you to help me form :set to change the data in the primary array in the Chupaka code. Still, I really like him.
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3250
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 5:36 pm

I think @rextended "example" here is top notch for what's possible. So if the goal is replacement, I think the cat covers it. I'll just add a couple notes here to maybe answer some questions:

1. Arrays are "deep copy on assignment", NOT "references" (or pointers). So if you want a new array to edit/modify, just assign it, the original remains unchanged:
:global a {"a"="top";"child"={"a"="inside"}}
:global b $a
:put [:typeof $b]
# array
:put ($b->"child")
# a=inside
:set ($b->"child") "changed"
:put ($b->"child")
# changed
:put ($a->"child")
#  a=inside

2. On your "duplicate result problem"... Using the CLI to call a function using [] at the top-level prompt, the function's returned value is then automatically run by the shell AFTER the command operator finishes. That's why you may be seeing duplicate results. If a function doesn't have a "return" then the [] square bracket doesn't matter. You can just use the variable name with $ in front to call it (or uses () parentheses if name or args is complex).

3. You're assuming $0 is the always the function name. Since functions also can be members of an nested associative array, but $0 is actually the first argument since the stack apparently shifts. See viewtopic.php?t=181142
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 9:27 pm

$answer->"alertList" = [] (array)
$answer->"audioSettings"->"DoP"->"title" = DoP playback (str)
$answer->"audioSettings"->"DoP"->"type" = boolean (str)
[...]
$answer->"networkSettings"->"title" = Network settings (str)
$answer->"networkSettings"->"wifiList" = [] (array)
You creathe the array from JSON?
Can I have, please, exact copy of the original file you use?
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3250
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: iterate over all elements of an array of unknown dimension

Wed Dec 21, 2022 10:38 pm

I'll just say impressive script @rextended. But I think he's trying to argue against your "Lesson 1".
Lesson 1) MIKROTIK DO NOT HAVE POINTER.
which, as you point out, is clearly verified by looking in the /system/script/environment.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 12:45 am

I think it's just a translation problem from my Italian:
The defined global variables are listed in the "system script environment".
They are not a "pointer" but just a list of what exists, as if I hit $ in the terminal and then <TAB>.
Since I don't know a way to know the NAME of the variable passed to the function, I'm looking for an array with content identical to the one passed.
But actually it's not accurate, because if for example the array is :local I can't find it,
and if I have two identical arrays (which aren't the same, do not share same memory area, but which have the same content) that method might give the wrong name.

What I mean by pointer, is an unwanted bug in RouterOS, then subsequently corrected (therefore not to be used as it no longer works in the future),
which allowed to "point" to full or only subsection of an array,
not having two variables, but working in the same coincident memory area. So if you worked on the array "B" you also changed the "pointed" array "A".

I hope I have explained that, I'm not english and translator do not work well with complex arguments...
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3250
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 3:53 am

What I mean by pointer, is an unwanted bug in RouterOS, then subsequently corrected (therefore not to be used as it no longer works in the future),
which allowed to "point" to full or only subsection of an array,
not having two variables, but working in the same coincident memory area. So if you worked on the array "B" you also changed the "pointed" array "A".
Ah, I just never seen this one-time bug before, in V6 or V7 at least. Thanks for the clarification! The memory for two arrays has always been separate (e.g. changing array "B" does NOT change array "A"), which is how it should work IMO (given the rest of RouterOS logic).

Maybe OP should upgrade to long-term or stable on either V6 or V7, the reports don't always line up with how RouterOS script should work.

In bocca al lupo qui...
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 8:39 am

You're assuming $0 is the always the function name. Since functions also can be members of an nested associative array, but $0 is actually the first argument since the stack apparently shifts.
This I know. I meant my solution to this problem when creating ordinary functions (not arrays), when $0 contains the name of the function itself, which has the type "lookup"
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 8:41 am

You creathe the array from JSON?
Can I have, please, exact copy of the original file you use?
Ok. I'll try to send the json myself.
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 8:50 am

here it is:
I give it to JSON Chupaka's parser https://github.com/Winand/mikrotik-json ... ep88763970 and it pushes the data into a multidimensional associative array

{
"audioSettings": {
"title": "Audio settings",
"soundCard": {
"type": "spinner",
"title": "Sound Card",
"value": 0,
"data": [
{
"id": 0,
"name": "Default"
}
]
},
"soundType": {
"type": "spinner",
"title": "Sound Type",
"value": 1,
"data": [
{
"id": 0,
"name": "Mono Differential"
},
{
"id": 1,
"name": "Stereo"
}
]
},
"DoP": {
"type": "boolean",
"title": "DoP playback",
"value": false
},
"autoPlay": {
"type": "boolean",
"title": "AutoPlay after boot",
"value": true
}
},
"broadcastModeSettings": {
"title": "Broadcast mode settings" ,
"enabled": false,
"broadcastType": {
"type": "spinner",
"title": "Broadcast Type",
"value": 0,
"data": [
{
"id": 0,
"name": "Default"
},
{
"id": 1,
"name": "Stream URL"
},
{
"id": 2,
"name": "Download URL"
}
]
},
"broadcastModeOptions": {
"streamURL": "",
"downloadURL": ""
}
},
"networkSettings": {
"title": "Network settings",
"interfaces": {
"LAN": {
"type": "boolean",
"title": "Ethernet",
"value": false
},
"WLAN": {
"type": "boolean",
"title": "Wi-Fi",
"value": true
}
},
"connections": {
"LAN": {
"state": "disconnected"
},
"WLAN": {
"state": "connected",
"conName": "avsalon",
"method": "auto",
"addresses": "192.168.1.202/24" ,
"gateway": "192.168.1.1",
"dns": "8.8.8.8,8.8.4.4,192.168.1.1,194.158.206.206"
}
},
"wifiList": [
{
"ssid": "avsalon",
"quality": "74%",
"bars": ": ▂▄▆_"
}
]
},
"multiroomSettings": {
"masterMode": false,
"slaveList": []
},
"deviceInfo": {
"version": "2.7.5",
"cpuTemp": "49°C",
"autoUpgrade": true,
"autoUpgradeInstall": false
}
}
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 10:58 am

about arrays: try running this script several times and see what happens:
:log info "run";
:local ar1 ({});
:local ar2 {"ar2-st0";"ar2-st1";"ar2-st2"};
:log info $ar1;
:for i from=0 to=2 do={
:if ($i = 2) do={:set ($ar1->i) ($ar2->i);}}
:log info $ar1;
:set ($ar2->2) "test";
:log info $ar1;
:log info $ar2;
:log info "end\r\n";
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 1:09 pm

how many times?
It do everytime the same results...


what's its purpose?
:for i from=0 to=2 do={
    :if ($i = 2) do={
        :set ($ar1->i) ($ar2->i)
    }
}
is useless, and on both miss $ in front of i
at least simply write
:set ($ar1->2) ($ar2->2)

You still obtain unexpected results if you do not follow RouterOS syntax.


This is better version for debug, and do not do any problem, still using your code (except the fix from i, to $i)

debug code

{
:put "run"

	:local ar1 ({})
	:local ar2 {"ar2-st0";"ar2-st1";"ar2-st2"}
:put "ar1 is empty: ar1 = $[:tostr $ar1]"
:put "ar2 is $[:tostr $ar2]"

:put "now I execute useless cycle to set the #3 element on ar1 with value of element #3 on ar2"
:put "the EXPECTED RESULTS: after that ar1 have 3 element, the #3=ar2-st2 and two empty element #1 and #2"
	:for i from=0 to=2 do={
	    :if ($i = 2) do={
	        :set ($ar1->$i) ($ar2->$i)
	    }
	}
:put "ar1 now is = $[:tostr $ar1]"
:put "ar2 is still = $[:tostr $ar2]"


:put "now the #3 element of ar2 is changed from ar2-st2 to test"
	:set ($ar2->2) "test"
:put "ar1 is still = $[:tostr $ar1]"
:put "ar2 is now = $[:tostr $ar2]"

:put "end"
}

You can not set the #3 element of array (index 2, because index start from 0) without have the other two.
Mikrotik add ampty required elements #1 and #2.
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 1:43 pm

:local ar do={
:local x [:toarray ""];
  :set ($x->$arg) $arg;
  :return $x;
}
:log info [$ar arg=1];
:log info [$ar arg=2];
:log info [$ar arg=3];
1=1
2=2
3=3

ok.
:local ar do={
:local x ({});
  :set ($x->$arg) $arg;
  :return $x;
}
:log info [$ar arg=1];
:log info [$ar arg=2];
:log info [$ar arg=3];
1=1
1=1;2=2
1=1;2=2;3=3

BUG !
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 1:47 pm

Mikrotik add ampty required elements #1 and #2.
That 's great ! I didn't know that ...

When you write an array in a script using {}, you declare it and initialize it ONLY the first time. When you run the script a second time, ROS finds this released array in its memory and simply declares it without initialization. This is done to speed up the work of languages

You still obtain unexpected results if you do not follow RouterOS syntax.
I did not violate the syntax of the router os, because the scripts run and work

By the way , I sent the JSON ...
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 1:59 pm

Let 's digress a little from the topic ... I previously reported that when trying to call a function twice in a ROW, Microtik executes it three times. This is easy to verify:

:global myFunc do={:log info ok}
[$myFunc]
[$myFunc]

We called the function twice ...
But "ok" will be printed 3 times !

ok
ok
ok

Is this bug fixed too ? :)
Yes, and you left without comment my indication of this bug Router OS... Did I break the syntax here ? Who forbade calling a function in square brackets ?
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 2:41 pm

Yes, and you left without comment my indication of this bug Router OS... Did I break the syntax here ? Who forbade calling a function in square brackets ?
Is the same question as "who forbade initializing empty array using array joining function? ( {a;b;c}, $arrayX, ...)

({}) it's a known bug since 2016
viewtopic.php?t=83575
The compiler doesn't warn you that you forgot to specify at least another array on the join function.

If the bug arises using a wrong syntax, or forgetting something, it's not so much a bug...

This:
({})
mean:
Join (empty) array {} with undefined

To properly use ({a;b;c},$arrayS, ... ) to initialize an empty array, you need to write ({},{}) to join two empty arrays.

debug code

{
    :local ar do={
        # array initialized as new empty array
        :local x [:toarray ""]
        :global arg
        :set ($x->$arg) $arg
        :return $x
    }
    :put ("result of [\$ar arg=1] = $[:tostr [$ar arg=1]]")
    :put ("result of [\$ar arg=2] = $[:tostr [$ar arg=2]]")
    :put ("result of [\$ar arg=3] = $[:tostr [$ar arg=3]]")

:put "\r\n"

    :local as do={
        # array initialized as joining empty array {} with "undefined"
        :local x ({})
        :global arg
        :set ($x->$arg) $arg
        :return $x
    }
    :put ("result of [\$as arg=1] = $[:tostr [$as arg=1]]")
    :put ("result of [\$as arg=2] = $[:tostr [$as arg=2]]")
    :put ("result of [\$as arg=3] = $[:tostr [$as arg=3]]")

:put "\r\n"

    :local at do={
        # array initialized as joining two empty array {}
        :local x ({},{})
        :global arg
        :set ($x->$arg) $arg
        :return $x
    }
    :put ("result of [\$at arg=1] = $[:tostr [$at arg=1]]")
    :put ("result of [\$at arg=2] = $[:tostr [$at arg=2]]")
    :put ("result of [\$at arg=3] = $[:tostr [$at arg=3]]")
}
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 3:15 pm

  :local x ({},{})
Yes... this is already from the field of flight to Mars ...

Thank you very much for your explanations and time spent.
However, you haven't convinced me of everything.
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 4:00 pm

:global shortrfields do={ :global shortrfields
                          :global ArrayIN
                          :local path "$2"
                          /system script environment
                          :foreach j in=[find] do={
                              :if ([get $j value] = $1) do={:set path "\$$[get $j name]"}
                          }
                          :foreach x,y in=$1 do={
                              :local lpath $path
                              :if ([:typeof $x] = "str") do={:set lpath "$path->\"$x\""} else={:set lpath "$path->$x"}
                              :if ([:typeof $y] = "array") do={
                                  :if ([:len $y] > 0) do={
                                      [$shortrfields $y $lpath]
                                   } else={
                                      :put "$lpath = [] (array)"
                                   }
                              } else={
                                  :put "$lpath = $y ($[:typeof $y])"
                                  :put (":set ($lpath) new")
                                  [[:parse ":set ($lpath) new"]]
                                  :put "$lpath = $y ($[:typeof $y])"
                              }
                          }
                          :return "OK"
                        }
I checked my data with your function. All paths are correct. The $lpatch variable forms the path to the final value of each branch of the tree that I need to change. This is exactly what the :put (":set ($lpath) new") command shows. But [[:parse ":set ($lpath) new"]] doesn't work - it doesn't change the value. I don't understand why.

Here is a part of the output for an example from which it is clear that everything seems to be correct, but the values ​​​​do not change:

$ArrayIN->"alertList" = [] (array)
$ArrayIN->"audioSettings"->"DoP"->"title" = DoP playback (str)
:set ($ArrayIN->"audioSettings"->"DoP"->"title") new
$ArrayIN->"audioSettings"->"DoP"->"title" = DoP playback (str)
$ArrayIN->"audioSettings"->"DoP"->"type" = boolean (str)
:set ($ArrayIN->"audioSettings"->"DoP"->"type") new
$ArrayIN->"audioSettings"->"DoP"->"type" = boolean (str)
$ArrayIN->"audioSettings"->"DoP"->"value" = false (bool)
:set ($ArrayIN->"audioSettings"->"DoP"->"value") new
$ArrayIN->"audioSettings"->"DoP"->"value" = false (bool)
$ArrayIN->"audioSettings"->"autoPlay"->"title" = AutoPlay after boot (str)
:set ($ArrayIN->"audioSettings"->"autoPlay"->"title") new
$ArrayIN->"audioSettings"->"autoPlay"->"title" = AutoPlay after boot (str)
$ArrayIN->"audioSettings"->"autoPlay"->"type" = boolean (str)
:set ($ArrayIN->"audioSettings"->"autoPlay"->"type") new
$ArrayIN->"audioSettings"->"autoPlay"->"type" = boolean (str)

Moreover, if I give a command from the Terminal, the value will be replaced by "new":
:put ($ArrayIN->"audioSettings"->"DoP"->"title")
DoP playback

:set ($ArrayIN->"audioSettings"->"DoP"->"title") new
:put ($ArrayIN->"audioSettings"->"DoP"->"title")
new
Where is my mistake ?
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 4:48 pm

[[:parse ":set ($lpath) \"new\""]] (missing escaped quotes between 'new' on your script!!!)
is like
execute [ the code [ obtained from :parse the text inside " "]]
and what is mean the text inside?
:set the array ( with name and path inside $lpath ) with value \"new\"
you try to set one value inside array but the array is unknow to :parse...

lpath is a string, not a pointer to one $ArrayIN field, already writed: RouterOS do not support pointers.
You can not take a function that only show path and try to use it to alter one value.

I have already provided one example on how change the value:

previous example code

:global searchpath do={ :global searchpath
[...]
}

{
# show previous value
:put ($ArrayIN->"networkSettings"->"connections"->"LAN"->"addresses")

# show current path
:put [$searchpath $ArrayIN "addresses" "192.168.0.101/24"]

# execute the substitution of value
[[:parse (":global ArrayIN; :set ($[$searchpath $ArrayIN "addresses" "192.168.0.101/24"]) \"192.168.0.102/24\"")]]

# show new value
:put ($ArrayIN->"networkSettings"->"connections"->"LAN"->"addresses")
}

You seem more focused on refuting than understanding.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 5:38 pm

Test with
[[:parse ":global $[:pick $lpath ([:find $lpath "\$" -1] + 1) [:find $lpath "-" -1]]; :set ($lpath) \"new\""]]

or for your array directly
[[:parse ":global ArrayIN; :set ($lpath) \"new\""]]

Is useless put :global ArrayIN on top of the function if the array is passed as argument, you can (must) remove it.

Is also useless print again "$lpath = $y ($[:typeof $y])" because you are visualizing the result of temporary array used to browser subsections of the array...
You can see changes calling again the clean shortrfields version.

example code

:global shortrfields do={ :global shortrfields
                          :local path "$2"
                          /system script environment
                          :foreach j in=[find] do={
                              :if ([get $j value] = $1) do={:set path "\$$[get $j name]"}
                          }
                          :foreach x,y in=$1 do={
                              :local lpath $path
                              :if ([:typeof $x] = "str") do={:set lpath "$path->\"$x\""} else={:set lpath "$path->$x"}
                              :if ([:typeof $y] = "array") do={
                                  :if ([:len $y] > 0) do={
                                      [$shortrfields $y $lpath]
                                   } else={
                                      :put "$lpath = [] (array)"
                                   }
                              } else={
                                  :put "$lpath = $y ($[:typeof $y])"
##################################
                                  [[:parse ":global $[:pick $lpath ([:find $lpath "\$" -1] + 1) [:find $lpath "-" -1]]; :set ($lpath) \"new\""]]
##################################
                              }
                          }
                          :return "OK"
                        }


:put [$shortrfields $ArrayIN]
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 6:16 pm

Resorted the JSON and on bars are present some non CP1252 characters: ▂, ▄ and ▆
(and probably another character █ that represent the ~100% when the signal value is higner)

JSON code

{
	"audioSettings": {
		"title": "Audio settings",
		"soundCard": {
			"type": "spinner",
			"title": "Sound Card",
			"value": 0,
			"data": [{
				"id": 0,
				"name": "Default"
			}]
		},
		"soundType": {
			"type": "spinner",
			"title": "Sound Type",
			"value": 1,
			"data": [{
					"id": 0,
					"name": "Mono Differential"
				},
				{
					"id": 1,
					"name": "Stereo"
				}
			]
		},
		"DoP": {
			"type": "boolean",
			"title": "DoP playback",
			"value": false
		},
		"autoPlay": {
			"type": "boolean",
			"title": "AutoPlay after boot",
			"value": true
		}
	},
	"broadcastModeSettings": {
		"title": "Broadcast mode settings",
		"enabled": false,
		"broadcastType": {
			"type": "spinner",
			"title": "Broadcast Type",
			"value": 0,
			"data": [{
					"id": 0,
					"name": "Default"
				},
				{
					"id": 1,
					"name": "Stream URL"
				},
				{
					"id": 2,
					"name": "Download URL"
				}
			]
		},
		"broadcastModeOptions": {
			"streamURL": "",
			"downloadURL": ""
		}
	},
	"networkSettings": {
		"title": "Network settings",
		"interfaces": {
			"LAN": {
				"type": "boolean",
				"title": "Ethernet",
				"value": false
			},
			"WLAN": {
				"type": "boolean",
				"title": "Wi-Fi",
				"value": true
			}
		},
		"connections": {
			"LAN": {
				"state": "disconnected"
			},
			"WLAN": {
				"state": "connected",
				"conName": "avsalon",
				"method": "auto",
				"addresses": "192.168.1.202/24",
				"gateway": "192.168.1.1",
				"dns": "8.8.8.8,8.8.4.4,192.168.1.1,194.158.206.206"
			}
		},
		"wifiList": [{
			"ssid": "avsalon",
			"quality": "74%",
			"bars": ": ▂▄▆_"
		}]
	},
	"multiroomSettings": {
		"masterMode": false,
		"slaveList": []
	},
	"deviceInfo": {
		"version": "2.7.5",
		"cpuTemp": "49°C",
		"autoUpgrade": true,
		"autoUpgradeInstall": false
	}
}
If you notice, RouterOS sort all array and sub array everytime, is why json and the array are different on item order, but identical on contents.
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 6:29 pm

Thank you very match, Rex !


While your answer came, I already found a solution for my case. It's just that I forgot to do :global inside :parse with the name of the input array:
[[:parse ":global ArrayIN; :set ($lpath) new"]]
But your solution is even better - it's generic, since :global makes an unknown individual array name !
[[:parse ":global $[:pick $lpath ([:find $lpath "\$" -1] + 1) [:find $lpath "-" -1]]; :set ($lpath) \"new\""]]
This line was what I needed, I got it from you!

Now everything works and I can finally change my complex array value used from JSON.
Thanks again. Agree, you were also interested. I think that the Chupacabra function will come in handy for you too.
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 6:54 pm

And here is the Chupacabra function that I have now rewritten, which finds and replaces values ​​in a multidimensional associative array. By the way, it works at least four times faster. You can test this.
It's because she's not looking:
/system script environment
                          :foreach j in=[find] do={

Here is an option "a la Chupaca":

:global ParseArray do={
  :local TempPath
  :global ParseArray

  :if ([:len $1] = 0) do={
    :set $1 "\$ArrayIN"
    :set $2 $ArrayIN
   }
   
  :foreach k,v in=$2 do={
    :if ([:typeof $k] = "str") do={
      :set k "\"$k\""
    }
    :set TempPath ($1. "->" . $k)
    :if ([:typeof $v] = "array") do={
      :if ([:len $v] > 0) do={
        $ParseArray $TempPath $v
      } else={
        :put "$TempPath = [] ($[:typeof $v])"
      }
    } else={
        :put "$TempPath = $v ($[:typeof $v])"
        :set $TempPath ("\$".$TempPath)
        [[:parse ":global $[:pick $1 1 [:len $1]]; :set ($TempPath) new"]]
    }
  }
}
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3250
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 8:23 pm

Glad it works for ya. But unsure how this is useful to anyone.

Once JSON parse gets you an array (e.g. via JSONLoad or JSONLoads), if the plan is to edit it, you don't need to traverse the array to find elements to edit: the normal syntax work. Here, you have some specific JSON, so normal syntax just works to modify it. But even if you have arbitrary/unknown JSON, and trying to deduce its schema/structure, to then be able edit/modify, regex would be faster (if that's the goal) to identify specific fragments from the array-as-string, than recursion+parse.
Last edited by Amm0 on Thu Dec 22, 2022 8:43 pm, edited 1 time in total.
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 8:43 pm

Thank you very much too, Amm0.

Rex,
You seem more focused on refuting than understanding
Please don't think like that. It's just that it's not easy for me to follow your thought, I'm not a professional programmer, I'm a doctor :)

I have another important question for me, which I myself can not solve. If you allow, I will create a separate topic for him tomorrow.
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3250
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: iterate over all elements of an array of unknown dimension

Thu Dec 22, 2022 8:50 pm

Out of curiosity. What version of RouterOS are you using? Some of your examples of bugs here just don't match what happens. Specifically your [] at CLI one (e.g. :log output's nothing, and only "ok" is logged once per call in both recent V6 and V7)
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Fri Dec 23, 2022 2:24 am

[...] it works at least four times faster [...]
I don't write scripts for speed, but for people to understand what's written.
Ask other people what script is more readable and understandable...
You are very courteous and polite in thanking those who have followed you up to now, it is certain that even if it were true, it is not that nice to write it...

And honestly, for me,
modify a function for a (at that point) useless view of the array just to make it change the contents,
instead of using a function that does this on purpose,
seems like bullshit to me.


It's because she's not looking:
Exactly, do not look and the name ArrayIN is "hardcoded"...
Don't you think it's silly to compare which vehicle is faster in doing the Paris-Dakar, when one of the two vehicles already starts in Morocco?
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: iterate over all elements of an array of unknown dimension

Fri Dec 23, 2022 2:48 am

Remember post #2?
viewtopic.php?p=973744#p973209
I can help you, but I must first have to understand correctly what is need.

Provide one array for example and what do you want to do with that array.
No matter if is correct syntax, you can use pseudoinstructions.

Finally the ultimate goal is revealed and is that to replace values of all non-array elements with the string "new" on the global array ArrayIN?...

yourfunction code

:global yourfunction do={
    :global yourfunction
    :local  path
    :foreach x,y in=$1 do={
        :if ([:typeof $x] = "str") do={:set path "$4->\"$x\""} else={:set path "$4->$x"}
        :if ([:typeof $y] = "array") do={
            [$yourfunction $y $2 $3 $path]
        } else={
            [[:parse ":global $2; :set (\$$2$path) \"$3\""]]
        }
    }
    :return "Done"
}

:put [$yourfunction $ArrayIN "ArrayIN" "new"]
 
User avatar
Sertik
Member
Member
Topic Author
Posts: 435
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: iterate over all elements of an array of unknown dimension

Fri Dec 23, 2022 8:34 am

Finally the ultimate goal is revealed and is that to replace values of all non-array elements with the string "new" on the global array ArrayIN?...
Yes. Thank you again very much. The goal has been achieved. The topic can be closed.

Who is online

Users browsing this forum: No registered users and 26 guests