this question succeeds the following question: Moose: Array of Objects->loop through Attribute
I'm struggling to implement the grep syntax into a List::Compare Object:
my #aIdentList=("z003","t302","p032");
my $lc=List::Compare->new('-u',\#aIdentList,\#{(grep {$_->Identifier}#aArray1)});
my #unique=$lc->get_unique;
\#{(grep {$_->Identifier}#aArray1)}
This part is weird. You are trying to dereference a list via #{}. That doesn't work.
The grep returns a list already. You do not need to use parenthesis () to construct a new list. Perl will ignore them. Then your #{} is simply wrong. You need a new array reference. That's []. It will construct a new array ref from the list that grep returns. That's already enough to pass to List::Compare->new. The backslash \ is only needed if you want to take a reference of something, but you now already have a reference.
But you actually do not want to grep here. You want to map!
This is what your code should look like.
List::Compare->new( '-u', \#aIdentList, [ map { $_->Identifier } #aArray1 ] )
grep will filter out every element of the list you pass in where the block does not return a true value. Since all objects in #aArray1 (typing this is horrible!) have an Identifier property, this will let all elements through. The second array ref arg to new will then be a list of objects. That's not what you want, because you'd be comparing apples to oranges.
Instead, you need to use map, which will pass the return value of the block for each element. It's like an inline foreach loop. Then your second list will be the actual identifiers of the objects, and not the objects themselves.
Related
I have a powershell 5.1 script that I've created that imports a csv file, manipulates some of the data, then converts the object of data to json format using the ConvertTo-Json cmdlet at the end of the script. The problem that I'm running into; in one of my fields I need to create a single array for the object property with brackets. So I need the object to be like:
"PersonGroups":[
{
"Name":"test Name",
"Id": 3433
}
]
Here is the call to the function:
$_.PersonGroups = Set-DataHash -InputObject $_
Below is my code that I have:
function Set-DataHash{
param(
[psobject] $InputObject
)
$customObject = [psobject]#{
Name = "Test"
Id = 78888
}
$customArray = #($customObject)
return $customArray
}
Of course if I have more than one object in the array it works fine; but since it's only one object ConvertTo-Json makes it a single object. Any suggestions on what to do?
How about:
$_.PersonGroups = #(Set-DataHash -InputObject $_)
You don't need the "return".
js2010's helpful answer shows how to solve the problem on the caller's side, using #(), the array-subexpression operator, around your Set-DataHash function call to ensure that the return value is an array.
If you want to solve the problem from within your function, you must ensure that the array being output (returned) is output as a whole, as a single object, which can be achieved with an auxiliary single-element wrapper array created with (the unary form of) ,, the array constructor operator:
function Set-DataHash {
param(
[psobject] $InputObject
)
$customObject = [psobject] #{
Name = "Test"
Id = 78888
}
# Wrap $customObject in an array with #(...),
# then *additionally* wrap this array in an *auxiliary* single-element array,
# via `,` (array-construction operator).
# Outputting this aux. array causes *it* to be enumerated, which therefore
# outputs its only element as-is: the *original* array - see explanation below.
# Note: I've omitted `return`, which is always optional.
, #($customObject)
}
Explanation:
By default - irrespective of whether you use return or implicit output in a function or script - outputting a collection (including arrays) causes its elements to be enumerated (aka streamed, unwrapped or unrolled); that is, the elements are output one by one to the (invisible, in this case) pipeline through which the caller receives the output.
In the case of a single-element collection, the nature of the pipeline is such that the caller receives just that single element itself - the collection wrapper is lost.
In the case of a multi-element collection, the specific original collection is lost too, and the enumerated elements are automatically collected in an [object[]] array).
Therefore, wrapping a collection that you want to output as a whole, as a single object requires the aux. single-element wrapper array technique shown above; a less efficient alternative is to use Write-Output with the -NoEnumerate switch, which too prevents enumeration of a collection passed to it as an argument (not via the pipeline).
Note:
In general, in functions/scripts intended to be called directly by others, it is better not to output collections as a whole, so as not to confound the general expectation of streaming (enumerating) behavior in the pipeline; in the streaming case, it is the caller that must ensure that the collected output is an array, via #(...), as shown in js2010's answer; see this answer for background information.
Conversely, however, outputting collections as a whole is faster and allows you to output a specific collection type.
I have a powershell 5.1 script that I've created that imports a csv file, manipulates some of the data, then converts the object of data to json format using the ConvertTo-Json cmdlet at the end of the script. The problem that I'm running into; in one of my fields I need to create a single array for the object property with brackets. So I need the object to be like:
"PersonGroups":[
{
"Name":"test Name",
"Id": 3433
}
]
Here is the call to the function:
$_.PersonGroups = Set-DataHash -InputObject $_
Below is my code that I have:
function Set-DataHash{
param(
[psobject] $InputObject
)
$customObject = [psobject]#{
Name = "Test"
Id = 78888
}
$customArray = #($customObject)
return $customArray
}
Of course if I have more than one object in the array it works fine; but since it's only one object ConvertTo-Json makes it a single object. Any suggestions on what to do?
How about:
$_.PersonGroups = #(Set-DataHash -InputObject $_)
You don't need the "return".
js2010's helpful answer shows how to solve the problem on the caller's side, using #(), the array-subexpression operator, around your Set-DataHash function call to ensure that the return value is an array.
If you want to solve the problem from within your function, you must ensure that the array being output (returned) is output as a whole, as a single object, which can be achieved with an auxiliary single-element wrapper array created with (the unary form of) ,, the array constructor operator:
function Set-DataHash {
param(
[psobject] $InputObject
)
$customObject = [psobject] #{
Name = "Test"
Id = 78888
}
# Wrap $customObject in an array with #(...),
# then *additionally* wrap this array in an *auxiliary* single-element array,
# via `,` (array-construction operator).
# Outputting this aux. array causes *it* to be enumerated, which therefore
# outputs its only element as-is: the *original* array - see explanation below.
# Note: I've omitted `return`, which is always optional.
, #($customObject)
}
Explanation:
By default - irrespective of whether you use return or implicit output in a function or script - outputting a collection (including arrays) causes its elements to be enumerated (aka streamed, unwrapped or unrolled); that is, the elements are output one by one to the (invisible, in this case) pipeline through which the caller receives the output.
In the case of a single-element collection, the nature of the pipeline is such that the caller receives just that single element itself - the collection wrapper is lost.
In the case of a multi-element collection, the specific original collection is lost too, and the enumerated elements are automatically collected in an [object[]] array).
Therefore, wrapping a collection that you want to output as a whole, as a single object requires the aux. single-element wrapper array technique shown above; a less efficient alternative is to use Write-Output with the -NoEnumerate switch, which too prevents enumeration of a collection passed to it as an argument (not via the pipeline).
Note:
In general, in functions/scripts intended to be called directly by others, it is better not to output collections as a whole, so as not to confound the general expectation of streaming (enumerating) behavior in the pipeline; in the streaming case, it is the caller that must ensure that the collected output is an array, via #(...), as shown in js2010's answer; see this answer for background information.
Conversely, however, outputting collections as a whole is faster and allows you to output a specific collection type.
hash_test.pl
#a=("f","a","b");
$K{"f"}{"aa"}=1;
$K{"a"}{"aa"}=1;
$k{"b"}{"bb"}=1;
foreach(#a){
#c= sort keys %{$k{$_}};
}
print "#c\n";
foreach(#c) {...}
perl hash_test.pl
bb
I want to keep the keys of the hash into an array, so that I can use the array as an input for the following statements.
But it seemed that the assay #c just only hold the last element.
Could anyone tell me why or help me to improve the script?
You assign the array every time in the foreach, thus overwriting it every time. So you end up with only having the last thing assigned to it. If you move the print inside the foreach you'll see that they are all there.
To store those keys you need to add them to the array, not assign the array. I've corrected the typo $k to $K, and changed aa that goes with f to ff (expecting it to be a typo as well).
my #c;
foreach my $el (#a) {
push #c, sort keys %{$K{$el}};
}
print "#c\n";
This prints the line: ff aa bb. Every time through the loop all keys found in the hash for a particular array element are added to #c, each as a separate element. So #c will contain all bottom-level keys across the whole data structure.
However, there is more that I would like to suggest.
Always use strict; and use warnings; This is not pedantry but it directly helps. I never write code without them. The typo would be caught here, for example.
Use descriptive variable names. Specifically, single-letter variable names are just too easy to confuse, unless in very short loops or where it is crystal clear what they are. (For example, a typo like this couldn't really happen.) Most importantly, the code is going to be far nicer to work with. That generally results in better code.
Please use good indentation and spacing. It helps a lot, in many ways.
A useful core package for nested data structures is Data::Dumper, which can print the whole thing nicely formatted so we can see it. Try to add to the end of your code
use Data::Dumper;
print Dumper(\%K);
There are yet others that do the same or similar.
Here is another way to do what you ask.
my #lowest_keys = map { sort keys %{$K{$_}} } #a;
I call them lowest_keys to emphasize that these are the ones from the last hash in your data structure, the bottom of it. The map applies processing in the block { ... } to each element of #a in turn, returning a list with all these results. (If any one result itself is a list, with more elements than one, it gets merged into the overall output list. So this may create the output list with many more elements than the input.) This list can then be assigned to an array, as above, or passed on to another function that expects a list as input, or interated over.
The map is generally used to transform an array into another, by doing to each element what is in { ... } block. Its close cousin grep is used to filter, so passing through only the elements of the input list for which the condition in { ... } evaluates to true, forming the output list. For example, filter out undefined array elements: my #good = grep { defined } #all
Variable names are case sensitive, so %K... and %k... are not the same.
Always use
use strict;
use warnings;
and declare your variables with my. That prevents you from making this kind of mistakes.
I have an array in a perl hash declared like this:
my %updatevars = (datapoints => []);
I'm later trying to add elements to it like this:
push($updatevars{'datapoints'}, [$updatestart+$i, $bandwidth]);
I get this error:
Type of arg 1 to push must be array (not hash element) at dirlist.pl line 61, near "])"
Hashes (and arrays) can only contain scalars. That's why we must put reference to arrays (and hashes) in them. $updatevars{datapoints} contains a reference to an array. As such, you need to use
push #{ $updatevars{datapoints} }, [ $updatestart+$i, $bandwidth ];
Note that your code would work on 5.14+ as push was changed to also accept a reference. (This change is "considered highly experimental" however, so you should use the above code in newer versions too.)
$updatevars{'datapoints'} is an array ref, as you assigned it: []. push takes an array as argument, not an array reference. So you need to dereference your reference:
push #{ $updatevars{'datapoints'} }, ...
In Perl v5.14, you may use a reference, as noted in the documentation. But it does not sound like it is a recommended practice just yet.
Starting with Perl 5.14, "push" can take a scalar EXPR, which must
hold a reference to an unblessed array. The argument will be
dereferenced automatically. This aspect of "push" is considered highly
experimental. The exact behaviour may change in a future version of
Perl.
Try this:
push #{$updatevars{'datapoints'}}, [$updatestart+$i, $bandwidth];
Since push takes an array and $updatevars{'datapoints'} is an array reference, you have to de-reference it by putting the #{} in front.
my #array = #{$array_ref};
I'm not entirely sure what this array is storing. Can anyone care to explain to me what is happening? Thanks.
$array_ref presumably holds a reference to an array, which is different from an array itself. An array (named #some_array) has elements (unless it's empty). An array ref (named $some_arrayref) is a scalar value that is used to reference an actual array. The actual array may be a named array (as in, it has a variable name), or may be an anonymous array (as in, no named variable, but referred to exclusively by reference).
To use the elements held in the array referred to by $array_ref, you have to dereference it. One syntax is #$array_ref (read that as "The array (#) referred to by the scalar ($) named array_ref). I've never been a fan of jamming a bunch of sigils together (the #, $ and % symbols) when dereferencing. I feel it hampers readability of the code, particularly as the references become more complex or nested. Perl allows for curly brackets, which help to disambiguate and visually clarify what's going on. #{$array_ref} can be read as dereference as an array ( #{....} ) the array referred to by $array_ref.
From Perl's POD, have a look at perlreftut. It's a great starting point.
The code makes a top-level copy of the array referenced by $array_ref and stores it in #array.
To make a deep-level copy, use dclone from Storable:
use Storable;
#array = #{ dclone( $array_ref ) };
See perldoc Storable for details.
Storable is a standard module that is install with Perl. To see a list of all standard pargmatics and modules, see perldoc perlmodlib.
This is simply dereferencing the array ref. $array_ref contains a reference to an array, #array is set to the contents of the referenced array.
$ perl -E 'my $a = [1, 2, 3]; my #b = #{$a}; say $a; say #b'
ARRAY(0x100803ea0) # a reference to an array
123 # actual array
You need to read perlref.
In short, #array is the same array that $array_ref was pointing to. That is the most precise answer that I can give without knowing what got put into $array_ref.
$array_ref is a reference to a list / array.
so in order to dereference it in a list content and get all the contents, you use
#{$array_ref}
and this can be assigned into a different array / list like:
my #array = #{$array_ref};