Powershell create array of arrays - arrays

I'm trying to push data to a REST api using powershell.
http://influxdb.com/docs/v0.8/api/reading_and_writing_data.html
The server expects data like so:
[
{
"name" : "hd_used",
"columns" : ["value", "host", "mount"],
"points" : [
[23.2, "serverA", "mnt"]
]
}
]
However, I"m only able to make a json object that looks like this (notice the extra quotes):
[
{
"name" : "hd_used",
"columns" : ["value", "host", "mount"],
"points" : [
"[23.2, "serverA", "mnt"]"
]
}
]
How can I construct the data into an array of arrays without wrapping the nested array in quotes?
This works, but it isn't a nested array
$influxdata = [ordered]#{}
$influxdata.name = $hd_used
$influxdata.columns = #("value", "host", "mount")
$influxdata.points = #()
$influxdata.points += #("23.2", "serverA", "mnt")
$influxdatajson = $influxdata | ConvertTo-Json -Depth 2
This works but the inner array is actually a string.
$influxdata = [ordered]#{}
$influxdata.name = $hd_used
$influxdata.columns = #("value", "host", "mount")
$influxdata.points = #()
$influxdata.points += #('["23.2", "serverA", "mnt"]')
$influxdatajson = $influxdata | ConvertTo-Json -Depth 2

With $PSVersion.$PSVersion equal to 3.0 and your exact input I get the following when I print the $influxdatajson variable:
{
"name": "hd_used",
"columns": [
"value",
"host",
"mount"
],
"points": [
"23.2",
"serverA",
"mnt"
]
}
Which clearly isn't what you want but isn't what you said you got either.
The reason that that is the output we get is because your attempt to add the array to the existing array didn't work the way you expect because of powershell's annoying tendency to unroll arrays (I think).
If you work around that oddity by using this syntax instead:
$influxdata.points += ,#("23.2", "serverA", "mnt")
(the leading , forces an array context so that outer array gets unrolled instead of the array you are trying to add)
then I get the following output from $influxdatajson:
{
"name": "hd_used",
"columns": [
"value",
"host",
"mount"
],
"points": [
[
"23.2",
"serverA",
"mnt"
]
]
}

To complement Etan Reisner's helpful answer (whose of use unary , to create a nested array solves the problem):
PowerShell's hashtable literals are quite flexible with respect to incorporating variable references, expression, and commands, which makes for a more readable solution:
,, [ordered] #{
name = 'hd_used'
columns = 'value', 'host', 'mount'
points = , (23.2, 'serverA', 'mnt')
} | ConvertTo-Json -Depth 3
This yields:
[
{
"name": "hd_used",
"columns": [
"value",
"host",
"mount"
],
"points": [
[
23.2,
"serverA",
"mnt"
]
]
}
]
Note how the array-construction operator (,) is applied twice at the beginning of the pipeline (before [ordered]):
first to turn the ordered hashtable into a (single-item) array,
and the 2nd time to wrap that array in an outer array.
Sending the result through the pipeline makes PowerShell unwrap any collection, i.e., enumerate the collection items and send them one by one, which in this case strips away the outer array, leaving ConvertTo-Json to process the inner array, as desired.
Note that passing an array adds a level to the hierarchy, which is why the -Depth value was increased to 3 above.
Caveat: Any property whose hierarchy level is deeper than -Depth is stringified (evaluated as if placed inside "$(...)"), which in the case of an array would simply join the array elements with a space; e.g., array 23.2, 'serverA', 'mnt' would turn to a single string with literal contents 23.2 serverA mnt.
Note how the arrays above do not use syntax #(...), because it is generally not necessary to construct arrays and is actually less efficient: simply ,-enumerate the elements, and, if necessary, enclose in (...) for precedence (although #() is syntactically convenient for creating an empty array).
+ with an array as the LHS doesn't so much unwrap (unroll) its RHS, but concatenates arrays, or, to put it differently, allows you to append multiple individual items; e.g.:
$a = 1, 2
$a += 3, 4 # add elements 3 and 4 to array 1, 2 to form array 1, 2, 3, 4
Note that the use of += actually creates a new array behind the scenes, given that arrays aren't resizable.

Just to add my two pence worth. Its is worth noting that everytime you touch an array and you know is a single item array, you must use the appropriate comma syntax. This is not well highlighted in articles I found on the subject.
Take this example case I wrote for a Pester test case:
A lesson about single item arrays, every time you touch a single item array, you must use the comma op. It is not a case of set it and forget it:
Mock get-AnswerAdvancedFn -ModuleName Elizium.Loopz {
$pairs = #(, #('Author', 'Douglas Madcap Adams'));
$first = $pairs[0];
Write-Host "=== SINGLE-ITEM: pairs.count: $($pairs.Count), first.count: $($first.Count)"
([PSCustomObject]#{ Pairs = $pairs })
}
The above won't work, because of the fault assigning $pairs to Pairs even though we've used the correct syntax setting $pairs to #(, #('Author', 'Douglas Madcap Adams'))
The following fixes this issue; everytime you touch the single item array, you must use the comma syntax, otherwise you give PowerShell another chance to flatten your array:
Mock get-AnswerAdvancedFn -ModuleName Elizium.Loopz {
$pairs = #(, #('Author', 'Douglas Madcap Adams'));
$first = $pairs[0];
Write-Host "=== SINGLE-ITEM: pairs.count: $($pairs.Count), first.count: $($first.Count)"
([PSCustomObject]#{ Pairs = , $pairs })
}
My test code ended up being this:
Mock get-AnswerAdvancedFn -ModuleName Elizium.Loopz {
([PSCustomObject]#{ Pairs = , #(, #('Author', 'Douglas Madcap Adams')) })
}
Note, we had to use the comma op twice and both of those are necessary

Related

Sorting an array together with other array in jq

I have two arrays in a structure but between elements of arrays has logical connections. (Eg. comments[1] belongs to records[1], comments[2] belongs to records[2])
These arrays are sorted by alpabetical order of records array.
When I want to add a new element for both array then I have sort both arrays. For records array it is easy, but the elements of comments array have to move same way as each element of record.
Input:
{
"records":
[
{"content":"a"},
{"content":"z"}
],
"comments":[
{"content":"something"},
{"content":"anything"}
]
}
New elements are added to the end of arrays:
{
"records":[
{"content":"a"},
{"content":"z"},
{"content":"b"}
],
"comments":[
{"content":"something"},
{"content":"anything"},
{"content":"new element"}
]
}
Expected sorted output:
```json
{
"records":[
{"content":"a"},
{"content":"b"},
{"content":"z"}
],
"comments":[
{"content":"something"},
{"content":"new element"},
{"content":"anything"}
]
}
I tried "to_elements, map, transpose and addfunctions without any (partial) result.
Rather than appending the new elements to the arrays, you could use bsearch to find the insertion point, and then use that to perform the two insertions. Specifically, if $x is not in a sorted array, then the insertion point of $x in the array is -1 - bsearch($x).
You might find this helper function useful:
def insert($x;$i): .[:$i]+[$x]+.[$i:];
Solution
Here then is a solution for the problem at hand:
# It is assumed that (.|f) is a sorted array
def insert_into_sorted($x; f; $y; g):
def insert($x;$i): .[:$i]+[$x]+.[$i:];
(f|bsearch($x)) as $ix
| (if $ix > -1 then $ix else -1 - $ix end) as $i
| f|=insert($x; $i)
| g|=insert($y; $i) ;
insert_into_sorted( {"content": "b"}; .records;
{"content": "new element"}; .comments)

Perl - How do I update (and access) an array stored in array stored in a hash?

Perhaps I have made this more complicated than I need it to be but I am currently trying to store an array that contains, among other things, an array inside a hash in Perl.
i.e. hash -> array -> array
use strict;
my %DEVICE_INFORMATION = {}; #global hash
sub someFunction() {
my $key = 'name';
my #storage = ();
#assume file was properly opened here for the foreach-loop
foreach my $line (<DATA>) {
if(conditional) {
my #ports = ();
$storage[0] = 'banana';
$storage[1] = \#ports;
$storage[2] = '0';
$DEVICE_INFORMATION{$key} = \#storage;
}
elsif(conditional) {
push #{$DEVICE_INFORMATION{$key}[1]}, 5;
}
}#end foreach
} #end someFunction
This is a simplified version of the code I am writing. I have a subroutine that I call in the main. It parses a very specifically designed file. That file guarantees that the if statement fires before subsequent elsif statement.
I think the push call in the elsif statement is not working properly - i.e. 5 is not being stored in the #ports array that should exist in the #storage array that should be returned when I hash the key into DEVICE_INFORMATION.
In the main I try and print out each element of the #storage array to check that things are running smoothly.
#main execution
&someFunction();
print $DEVICE_INFORMATION{'name'}[0];
print $DEVICE_INFORMATION{'name'}[1];
print $DEVICE_INFORMATION{'name'}[2];
The output for this ends up being... banana ARRAY(blahblah) 0
If I change the print statement for the middle call to:
print #{$DEVICE_INFORMATION{'name'}[1]};
Or to:
print #{$DEVICE_INFORMATION{'name'}[1]}[0];
The output changes to banana [blankspace] 0
Please advise on how I can properly update the #ports array while it is stored inside the #storage array that has been hash'd into DEVICE_INFORMATION and then how I can access the elements of #ports. Many thanks!
P.S. I apologize for the length of this post. It is my first question on stackoverflow.
I was going to tell you that Data::Dumper can help you sort out Perl data structures, but Data::Dumper can also tell you about your first problem:
Here's what happens when you sign open-curly + close-curly ( '{}' ) to a hash:
use Data::Dumper ();
my %DEVICE_INFORMATION = {}; #global hash
print Dumper->Dump( [ \%DEVICE_INFORMATION ], [ '*DEVICE_INFORMATION ' ] );
Here's the output:
%DEVICE_INFORMATION = (
'HASH(0x3edd2c)' => undef
);
What you did is you assigned the stringified hash reference as a key to the list element that comes after it. implied
my %DEVICE_INFORMATION = {} => ();
So Perl assigned it a value of undef.
When you assign to a hash, you assign a list. A literal empty hash is not a list, it's a hash reference. What you wanted to do for an empty hash--and what is totally unnecessary--is this:
my %DEVICE_INFORMATION = ();
And that's unnecessary because it is exactly the same thing as:
my %DEVICE_INFORMATION;
You're declaring a hash, and that statement fully identifies it as a hash. And Perl is not going to guess what you want in it, so it's an empty hash from the get-go.
Finally, my advice on using Data::Dumper. If you started your hash off right, and did the following:
my %DEVICE_INFORMATION; # = {}; #global hash
my #ports = ( 1, 2, 3 );
# notice that I just skipped the interim structure of #storage
# and assigned it as a literal
# * Perl has one of the best literal data structure languages out there.
$DEVICE_INFORMATION{name} = [ 'banana', \#ports, '0' ];
print Data::Dumper->Dump(
[ \%DEVICE_INFORMATION ]
, [ '*DEVICE_INFORMATION' ]
);
What you see is:
%DEVICE_INFORMATION = (
'name' => [
'banana',
[
1,
2,
3
],
'0'
]
);
So, you can better see how it's all getting stored, and what levels you have to deference and how to get the information you want out of it.
By the way, Data::Dumper delivers 100% runnable Perl code, and shows you how you can specify the same structure as a literal. One caveat, you would have to declare the variable first, using strict (which you should always use anyway).
You update #ports properly.
Your print statement accesses $storage[1] (reference to #ports) in wrong way.
You may use syntax you have used in push.
print $DEVICE_INFORMATION{'name'}[0], ";",
join( ':', #{$DEVICE_INFORMATION{'name'}[1]}), ";",
$DEVICE_INFORMATION{'name'}[2], "\n";
print "Number of ports: ", scalar(#{$DEVICE_INFORMATION{'name'}[1]})),"\n";
print "First port: ", $DEVICE_INFORMATION{'name'}[1][0]//'', "\n";
# X//'' -> X or '' if X is undef

Array assignment in Perl

What is the difference between
myArr1 => \#existingarray
and
myArr2 => [
#existingarray
]
I am assigning the #existingarray to a element in a hash map.
I mean what exactly internally happens. Is it that for the first one, it points to the same array and for the second array it creates a new array with the elements in the #existingarray
Thanks in advance
Yes, the first one takes a reference, the second one does a copy and then takes a reference.
[ ... ] is the anonymous array constructor, and turns the list inside into an arrayref.
So with #a = 1, 2, 3,
[ #a ]
is the same as
[ 1, 2, 3 ]
(the array is flattened to a list) or
do {
my #b = #a;
\#b;
}
In effect, the elements get copied.
Also,
my ($ref1, $ref2) = (\#a, [#a]);
print "$ref1 and $ref2 are " . ($ref1 eq $ref2 ? "equal" : "not equal") . "\n";
would confirm that they are not the same. And if we do
$ref1->[0] = 'a';
$ref2->[0] = 'b';
then $a[0] would equal a and not b.
You can use
perl -e 'my #a=(1); my $ra=\#a; my $rca=[#a]; $ra->[0]=2; print #a, #{$ra}, #{$rca};'
221
to see that your assumption that [#existingarray] creates a reference to a copy of #existingarray is correct (and that myArray* isn't Perl).
WRT amon's revising my perl -e "..." (fails under bash) to perl -e '...' (fails under cmd.exe): Use the quotes that work for your shell.
The square brackets make a reference to a new array with a copy of what's in #existingarray at the time of the assignment.

Perl: How do I declare empty array refs in a new hash?

I've got strict and warnings on, but it keeps complaining about the initialization of the following line:
$hash{$key} = ($row, [], [], [], '');
It warns for that single line:
"Useless use of private variable in void context"
"Useless use of anonymous list ([]) in void context" (3 times)
I am filling the data in later, but I want indexes 1, 2, 3 to be array references, and index 4 to be a string. I am accessing and filling the data like so:
$hash{$key}->[1]->[3] = 'Data';
$hash{$key}->[4] = $hash{$key}->[4] . 'More Data';
Obviously, I'm doing something wrong, but I'm not exactly sure how to make it right. (Also, I'm aware that that last line is redundant, could that also be summed up in a nicer way?)
Elements of a hash can only be scalars, so you have to change your assignment to use the anonymous array constructor instead of parens:
$hash{$key} = [$row, [], [], [], ''];
See perldsc for more information.
The line:
$hash{$key}->[4] = $hash{$key}->[4] . 'More Data';
could be written:
$hash{$key}->[4] .= 'More Data';
And finally, unless you like them, the -> characters are implicit between subscript delimiters, so $hash{$key}->[1]->[3] means the same thing as $hash{$key}[1][3]
I'm not quite sure what you are trying to do, but if you want to assign an array to a scalar value, you need to use brackets to create an anonymous array:
$hash{$key} = [$row, [], [], [], ''];
In your case, what you are attempting to do is interpreted as follows:
$row, [], [], [];
$hash{$key} = '';
Because you cannot assign a list of values to a scalar (single value variable). You can, like we did above, however, assign a reference to an anonymous array containing a list of values to a scalar.
You almost got it.
Remember that every hash and array value must be a scalar, so if you want a hash of arrays, you have to assign an array reference to your hash key. So:
$hash{$key} = [ $row, [], [], [], '' ];
is what you want.

Filling hash of multi-dimensional arrays in perl

Given three scalars, what is the perl syntax to fill a hash in which one of the scalars is the key, another determines which of two arrays is filled, and the third is appended to one of the arrays? For example:
my $weekday = "Monday";
my $kind = "Good";
my $event = "Birthday";
and given only the scalars and not their particular values, obtained inside a loop, I want a hash like:
my %Weekdays = {
'Monday' => [
["Birthday", "Holiday"], # The Good array
["Exam", "Workday"] # The Bad array
]
'Saturday' => [
["RoadTrip", "Concert", "Movie"],
["Yardwork", "VisitMIL"]
]
}
I know how to append a value to an array in a hash, such as if the key is a single array:
push( #{ $Weekdays{$weekday} }, $event);
Used in a loop, that could give me:
%Weekdays = {
'Monday' => [
'Birthday',
'Holiday',
'Exam',
'Workday'
]
}
I suppose the hash key is the particular weekday, and the value should be a two dimensional array. I don't know the perl syntax to, say, push Birthday into the hash as element [0][0] of the weekday array, and the next time through the loop, push another event in as [0][1] or [1][0]. Similarly, I don't know the syntax to access same.
Using your variables, I'd write it like this:
push #{ $Weekdays{ $weekday }[ $kind eq 'Good' ? 0 : 1 ] }, $event;
However, I'd probably just make the Good/Bad specifiers keys as well. And given my druthers:
use autobox::Core;
( $Weekdays{ $weekday }{ $kind } ||= [] )->push( $event );
Note that the way I've written it here, neither expression cares whether or not an array exists before we start.
Is there some reason that
push #{ $Weekdays{Monday}[0] }, "whatever";
isn’t working for you?

Resources