Inserting Array References into Perl Heap - perl

I am inserting 2-dimensional array references into my heap in Perl.
How should I define the 'elements' attribute when constructing my heap so that I can properly use my comparator function?
my $heap = Heap::Simple->new( order => \&byNumOrStr,
elements => [Array => 0]
);
sub byNumOrStr
{
my ( $a, $b ) = #_;
$b->[0] <=> $a->[0] #0-th element is a number.
||
$a->[1] cmp $b->[1]; #1-st element is a number
}
I keep getting back this error:
Can't use string ("2.55") as an ARRAY ref while "strict refs" in use ... (This means I might actually have to compare my "number string" numerically)

Well, it's likely that either $a or $b is being passed in as a string. Try printing out this variables after the assignment.
From what I can see from the documentation, when you pass elements => [ Array => 0 ], unless the 0th item in the array is an array then you'll only be comparing the values in the first slot of the array.
[Array => $index]
Indicates that the elements are array references, with the key at index $index. So now the element can be not just the key, but also associated data.
This means that if 2.55 is in the array like [ 2.55, ... ] then that's what's being passed in as $a or $b.
The elements entry tells H::S how you want to derive the key. For a completely generic way, it says that you can pass [Function => $code_ref_for_key]. You could make it like this:
sub first_two_slots {
my $array_ref = shift;
return [ #$array_ref[0,1] ];
}
And then with the order as specified, it would pass that array into your order and specify
my $heap = Heap::Simple->new( order => \&byNumOrStr,
elements => [Function => \&first_two_slots]
);
Original comment left in place: (It's not relevant to how Heap::Simple calls order).
if byNumOrStr is called from sort DON'T assign $a and $b in it. Those values are set by sort. If there is something coming in #_ it's probably not what you want.

Sorting a two-dimensional array doesn't really make sense -- when you sort something, there is a defined order. Having two sort criteria doesn't make it a two-dimensional list... do you mean that you have data that are a list of two elements? e.g.:
my $element = [ '0', 'string' ];
I think Example 1 in the documentation ("where key and value are kept separate") applies here -- you want to sort the references, not the values themselves. So try declaring with elements => "Any", and then adjust your sort method to match:
(I was wrong.. it looks like elements => [Array => 0] is correct, since these are just plain old arrayrefs being sorted.
my $heap = Heap::Simple->new( order => \&byNumOrStr,
elements => [Array => 0],
);
sub byNumOrStr
{
my ( $val1, $val2 ) = #_;
my $result =
$val1->[0] <=> $val2->[0] # the 0th element is a number
||
$val1->[1] cmp $val2->[1]; # the 1st element is a string
# The docs say "this should return a true value if $key1 is smaller than $key2 and a false value otherwise."
return $result == -1;
}
PS. As discussed in Secondary Order in Heap::Simple, the comparison function in Heap::Simple does not want a return value of -1, 0, or 1, but rather true or false. You need to convert the comparison result before returning from the function.

Related

How to splice an array that is in a hash of arrays?

I am populating a data structure like so:-
push #{$AvailTrackLocsTop{$VLayerName}}, $CurrentTrackLoc;
Where $VLayerName is a string like m1, m2, m3, etc., and $CurrentTrackLoc is simply a decimal number. If I use Data::Dumper to print the contents of the hash after it is fully populated, it reveals what I expect, e.g.:-
$VAR1 = {
'm11' => [
'0.228',
'0.316',
'0.402',
'0.576',
'0.750',
'569.458',
'569.544',
'569.718',
'569.892'
]
};
Now I need to effectively splice the stored list of decimal numbers. I can delete entries like so:-
for (my $i = $c; $i <= $endc; $i++) {
delete $AvailTrackLocsTop{$VLayerName}->[$i];
}
The result is, as expected, a bunch of "undef" entries where numbers used to exist, e.g.:-
$VAR1 = {
'm11' => [
undef,
undef,
undef,
undef,
'0.750',
'569.458',
'569.544',
'569.718',
'569.892'
]
};
But how can I purge the undef entries so that I see something like this instead?
$VAR1 = {
'm11' => [
'0.750',
'569.458',
'569.544',
'569.718',
'569.892'
]
};
It is important to note that the deletions can be anywhere in the array, e.g. like index 33 and 99 of 100. It is easy to splice arrays outside the context of a hash structure, but I am struggling to manipulate the array when it is embedded inside a large hash.
First, I want to note from the delete documentation:
WARNING: Calling delete on array values is strongly discouraged. The notion of deleting or checking the existence of Perl array elements is not conceptually coherent, and can lead to surprising behavior.
The correct way to set an array element to undef is with the undef function (or just assigning undef to it).
To instead remove the elements, you can use the splice function, it works the same way on nested arrayrefs as on a normal array, you just need to dereference it like you did for push.
splice #{$AvailTrackLocsTop{$VLayerName}}, $c, $endc - $c + 1;
Probably the easiest given where you're at is to rebuild the arrays without the undefs:
$_ = [ grep defined, #$_ ] for values %AvailTrackLocsTop;
Alternatively, instead of a hash of arrays, you could have a hash of hashes, and then deleting will cause them to disappear without simply turning to undef. You'll just lose the order, if that matters.

How to store values of hash in array in perl?

I have a hash having duplicate values and unique keys.I have to store keys in array of size 5, if more keys are there new array should be created and stored in it.
The keys stored in 1 array should have same value.
Note: I have to read those values from excel sheet and generate c source file.
Ex:
%hash = (a=>1,b=>2,c=>1,d=>1,e=>3,f=>4,g=>4,h=>1,i=>1,j=>1);
output in c file:
datatype arr1[]={a,c,d,h,i};
datatype arr2[]={j};
datatype arr3[]={b};
datatype arr4[]={e};
datatype arr5[]={f,g};
So you need to find keys that have the same values?
So we need to kind of revert the array, but being a bit smart to handle that the original values are not unique. Som instead of just transforming 'key' => 'value' pairs to 'value' => 'key', we need to store the keys in arrays.
my %hash = ...;
my %transposed;
for my $key (keys %hash) {
my $value = $hash{$key};
$transposed{$value} = [] unless defined $transposed{$value};
push #{ $transposed{$value} }, $key;
}
Then you have a hash of arrays, where each key is a value in the original hash and the elements of the arrays are the keys. The next step is to iterate over the keys and spilt each list into lines of 5 elements:
for my $key (sort keys %transposed) {
while (#{ $transposed{$key} }) {
my #list = splice #{ $transposed{$key} }, 0, 5;
say join ", ", #list;
}
}
The main parts is the while loop iterating as long as there are elements in the current list and the splice removes and returns up to 5 element from the list each iteration. Adding the exact C code is left as an exercise for the interested reader... :-)
You might need to read up on references: http://perldoc.perl.org/perlreftut.html
The line setting a hash value to a reference to an empty array is not necessary as perl will automatically create a arrayref when you tries to push a value to it. I have included it to make it clearer what is going on.

How to alter the hash values with new values in perl?

How can i reset the hash values in perl
use warnings;
use strict;
my %hash = qw(one 1 two 2 three 3 four 4);
my #key = keys(%hash);
my #avz = (9..12);
my %vzm;
print "Original hash and keys : ",%hash,"\n";
for(my $i = 0; $i<=scalar #avz; $i++){
my #new = "$key[$i] $avz[$i] ";
push(%vzm , #new);
}
print "modified hash and keys",%vzm,"\n";
I tried to alter the keys of original hash with another keys. How can i do it
This program give the error is:
Original hash and keys : three3one1two2four4
Not an ARRAY reference at key.pl line 10.
I expect the output is
Original hash and keys : three3one1two2four4
modified hash and keys : three11one9two10four12
How can i do it
Ok, first off - you're doing something nasty in your code:
You're trying to take an ordered data structure - an array - and push it into a keyed data structure, which has no particular ordering defined.
This isn't going to work very well - it technically works, because internally perl treats arrays and hashes similarly.
But for example your first assignment - what you're actually getting is:
my %hash = (
one => 1,
two => 2,
three => 3,
four => 4
);
You can access the keys (in no particular order) via keys(). And the values via values(). But to try and treat it like an array is undefined behaviour.
To add elements to your array:
$hash{'nine'} = 9;
To delete elements from your array:
delete ( $hash{'one'} );
You can iterate on keys or values - and combined with sort even do them in some sort of order. (Just bear in mind for sorting alphanumeric numbers you'll have a custom sort job).
foreach my $key ( sort keys %hash ) {
print "$key => $hash{$key}\n";
}
(Note - this is sorting by alphanumeric string, so gives:
four => 4
one => 1
three => 3
two => 2
If you want to sort by value:
foreach my $key ( sort { $hash{$a} <=> $hash{$b} } keys %hash ) {
print "$key => $hash{$key}\n";
}
And so you'll get:
one => 1
two => 2
three => 3
four => 4
So the real question remains - what are you actually trying to accomplish? The point of a hash is to give you an unordered mini-database of key-value pairs. Treating one like an array doesn't make an awful lot of sense. Either you're iterating hash elements in arbitrary order, or you're applying a specific sort to it - but one where you're relying on getting elements in a particular order is a bad plan - it may work, but it's not guaranteed to work, and that makes for bad code.
You have to keep the order of the keys in some array, or take it from original list
my #tmp = qw(one 1 two 2 three 3 four 4);
my %hash = #tmp;
# 'one', 'two', ..
my #key = #tmp[ grep !($_%2), 0 .. $#tmp ];
# ..
for my $i (0 .. $#avz) {
$vzm{ $key[$i] } = $avz[$i];
}
or using hash slice as more perlish approach,
#vzm{ #key } = #avz;
You can't do what you want (replace the values for keys in the hash in the order they originally were added) without keeping track of that order separately, since the hash doesn't have any particular order. In other words, this:
my #key = keys(%hash);
needs to be this:
my #key = ( 'one', 'two', 'three', 'four' );
Once you have that, you can just assign the values all at once with a hash slice:
my %vzm;
#vzm{#key} = #avz;
To create a hash element, you use assignment to $var{$key}.
for (my $i = 0; $i < scalar #avz; $i++) {
$vzm{$key[$i]} = $avz[$i];
}
Note also that the loop condition should be <, not <=. List/array indexes end at scalar #avz - 1.

How can I find out if a hash has an odd number of elements in assignment?

How could I find out if this hash has an odd number of elements?
my %hash = ( 1, 2, 3, 4, 5 );
Ok, I should have written more information.
sub routine {
my ( $first, $hash_ref ) = #_;
if ( $hash_ref refers to a hash with odd numbers of elements ) {
"Second argument refers to a hash with odd numbers of elements.\nFalling back to default values";
$hash_ref = { option1 => 'office', option2 => 34, option3 => 'fast' };
}
...
...
}
routine( [ 'one', 'two', 'three' ], { option1 =>, option2 => undef, option3 => 'fast' );
Well, I suppose there is some terminological confusion in the question that should be clarified.
A hash in Perl always has the same number of keys and values - because it's fundamentally an engine to store some values by their keys. I mean, key-value pair should be considered as a single element here. )
But I guess that's not what was asked really. ) I suppose the OP tried to build a hash from a list (not an array - the difference is subtle, but it's still there), and got the warning.
So the point is to check the number of elements in the list which will be assigned to a hash. It can be done as simple as ...
my #list = ( ... there goes a list ... );
print #list % 2; # 1 if the list had an odd number of elements, 0 otherwise
Notice that % operator imposes the scalar context on the list variable: it's simple and elegant. )
UPDATE as I see, the problem is slightly different. Ok, let's talk about the example given, simplifying it a bit.
my $anhash = {
option1 =>,
option2 => undef,
option3 => 'fast'
};
See, => is just a syntax sugar; this assignment could be easily rewritten as...
my $anhash = {
'option1', , 'option2', undef, 'option3', 'fast'
};
The point is that missing value after the first comma and undef are not the same, as lists (any lists) are flattened automatically in Perl. undef can be a normal element of any list, but empty space will be just ignored.
Take note the warning you care about (if use warnings is set) will be raised before your procedure is called, if it's called with an invalid hash wrapped in reference. So whoever caused this should deal with it by himself, looking at his own code: fail early, they say. )
You want to use named arguments, but set some default values for missing ones? Use this technique:
sub test_sub {
my ($args_ref) = #_;
my $default_args_ref = {
option1 => 'xxx',
option2 => 'yyy',
};
$args_ref = { %$default_args_ref, %$args_ref, };
}
Then your test_sub might be called like this...
test_sub { option1 => 'zzz' };
... or even ...
test_sub {};
The simple answer is: You get a warning about it:
Odd number of elements in hash assignment at...
Assuming you have not been foolish and turned warnings off.
The hard answer is, once assignment to the hash has been done (and warning issued), it is not odd anymore. So you can't.
my %hash = (1,2,3,4,5);
use Data::Dumper;
print Dumper \%hash;
$VAR1 = {
'1' => 2,
'3' => 4,
'5' => undef
};
As you can see, undef has been inserted in the empty spot. Now, you can check for undefined values and pretend that any existing undefined values constitutes an odd number of elements in the hash. However, should an undefined value be a valid value in your hash, you're in trouble.
perl -lwe '
sub isodd { my $count = #_ = grep defined, #_; return ($count % 2) };
%a=(a=>1,2);
print isodd(%a);'
Odd number of elements in hash assignment at -e line 1.
1
In this one-liner, the function isodd counts the defined arguments and returns whether the amount of arguments is odd or not. But as you can see, it still gives the warning.
You can use the __WARN__ signal to "trap" for when a hash assignment is incorrect.
use strict ;
use warnings ;
my $odd_hash_length = 0 ;
{
local $SIG{__WARN__} = sub {
my $msg = shift ;
if ($msg =~ m{Odd number of elements in hash assignment at}) {
$odd_hash_length = 1 ;
}
} ;
my %hash = (1, 2, 3, 4, 5) ;
}
# Now do what you want based on $odd_hash_length
if ($odd_hash_length) {
die "the hash had an odd hash length assignment...aborting\n" ;
} else {
print "the hash was initialized correctly\n";
}
See also How to capture and save warnings in Perl.

Perl hash value without key name

Is it possible in Perl to access a value of a hash, if it has just one key, without using key value?
Let's say, %h has just 'key_name' => 'value'.
Can I access the 'value' only via $h->{key_name}?
Or, is possible to access this 'value' without key name?
The values builtin function for hashes will return a list of all the hash values. You can use this to get or set any values with aliasing list constructs such as foreach, map, and grep:
for my $value (values %hash) {
say $value; # prints the value
$value++; # adds one to the value
}
Or you can store the values in an array:
my #vals = values %hash;
The order of the returned values is effectively random, but it will be the same order as the corresponding keys function.
Hashes themselves are lists, so you can access any odd element of the hash in list context to get at the value, but this method is less efficient since the whole hash needs to be taken apart to form the list, not just the values.
The techniques above work with hashes of any size. If you only have one key / value pair:
my %hash = qw(foo bar);
Then they reduce to:
{my ($x) = values %hash; say $x} # bar
{my (undef, $x) = %hash; say $x} # bar
{my $x = (values %hash)[0]; say $x} # bar
{my $x = (%hash)[1]; say $x} # bar
There are many ways to do this. For example:
my %h=("key_name"=>"value"); print values(%h)
or
my %h=("key_name"=>"value"); print( (%h)[1])
But in my opinion that doesn't look pretty...
You've got two options here - you can either optimize for space, or optimize for time. If you need to get the key from that value and you don't care about how long it takes, you can iterate over each entry in the associative array:
while(($key, $value) = each(%h))
{
if($value eq 'value')
{
return $key;
}
}
But if you don't mind having two copies, the most time-efficient solution is to hold a backwards and forwards associative array -- that is: %h_by_name and %h_by_value.
In this case, if you have multiple keys with the same value, your %h_by_value should contain an array. That is if:
%h_by_name = (
"a" => "1",
"b" => "1",
"c" => "1",
"d" => 2"
);
Then you would want to construct your %h_by_value such that it was:
%h_by_value = (
"1" => [ "a", "b", "c" ],
"2" => [ "d" ]
);