pritesh_ugrankar has asked for the wisdom of the Perl Monks concerning the following question:
Respected Monks,
The code given below is something I've written as a test.
use strict;
use warnings;
use diagnostics;
my @nums = (17, 10, 20, 33, 30, 40, 10, 33, 40, 15, 16, 17);
my %unordered = map { $_ => undef } @nums;
my @un_nums = join " ", keys %unordered;
print "@un_nums\n";
And it seems to work fine.
D:\perlscripts>perl test.pl
30 17 33 10 40 15 16 20
I'll be using this in a script to gather some information, therefore kindly let me know if
1) It's ok to use a join on a hash?
2) It's ok to collect the output of a join as an array context?
Or is this a bad practice? Any side effects?
Re: Is it safe to use join on a hash?
by choroba (Cardinal) on Sep 06, 2020 at 22:53 UTC
|
Using join on a hash would be kind of strange, but using it on the keys of a hash is quite common. Join has no side effects (it stringifies the elements, which might change the internal representation of the values if they are numbers, but from Perl perspective, they don't change; moreover, hash keys are always strings, anyway).
To give predictable output, you might want to sort the keys before joining them:
my $unique_nums = join ' ', sort keys %unordered;
Update: See davido's post.
map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
| [reply] [d/l] [select] |
Re: Is it safe to use join on a hash?
by davido (Cardinal) on Sep 07, 2020 at 01:08 UTC
|
Consider what you're doing. Perl is perfectly capable of using join on any list, including a list of hash keys. But that doesn't mean that your usage actually makes a whit of sense.
my @nums = (17, 10, 20, 33, 30, 40, 10, 33, 40, 15, 16, 17);
Here you have an array with the following elements: 17, 10, 20, 33, 30, 40, 10, 33, 40, 15, 16, 17.
The number of elements in the array is 12.
my %unordered = map { $_ => undef } @nums;
Now you have a hash of key/value pairs where the keys are comprised of the elements that
are in the @nums array. Except for one problem? 17 is repeated twice. 10 is repeated twice,
33 is repeated twice. 40 is repeated twice. Hash keys can only be unique. Duplicates replace previous instances. So now the keys are:
17, 10, 20, 33, 30, 40, 15, 16. So there are now 8 hash elements.
my @un_nums = join " ", keys %unordered;
What do you think @un_nums contains now? It contains a single element.
$un_nums[0] contains a string: "17 10 20 33 30 40 15 16"
(but with its numbers in unpredictable order).
There is no $un_nums[1], or beyond. Only a single element. Of what use is that?
print "@un_nums\n";
Now this is funny: if @un_nums contained eight elements, or only one element that consists of
a single string of eight numbers, the output here will be the same because by default the list
separator $" is a single space by default (see perlop). Of course you're not invoking the list separator because
your array contains only a single element. But what I'm saying is this:
my @array1 = (1, 2, 3, 4, 5);
my @array2 = ("1 2 3 4 5");
print "@array1\n";
print "@array2\n";
These two will produce identical output.
There's nothing wrong with using join on hash keys, nor is there anything wrong with using join
to construct an element of an array. But it looks a lot like you're using it mistakenly in this case. Otherwise, why
would you have assigned the string to @un_nums instead of $un_nums?
So the effect of your code is to take a list of 12 elements, boil it down to a list of 8 unique elements in no particular order, and then convert those elements to a string, and store that string in the first (and only) element of an array. Then print all the elements of that array (of which there is only one; a string of 8 unique numbers in whatever order they ended up being placed in the string).
| [reply] [d/l] [select] |
|
Hi David, Thanks. And I forgot to mention but I wanted to extract unique elements only. Still, your answer clears a lot of doubts.
Thanks once again.
| [reply] |
|
| [reply] [d/l] |
|
Re: Is it safe to use join on a hash?
by BillKSmith (Monsignor) on Sep 07, 2020 at 02:07 UTC
|
And it seems to work fine
This suggests that you want an array of unique numbers. It would be better to use the "uniq" function of List::Util. This function preserves the original order. Note that I print colons (:) to separate array elements.
use strict;
use warnings;
use List::Util qw(uniq);
my @nums = (17, 10, 20, 33, 30, 40, 10, 33, 40, 15, 16, 17);
my @un_nums = uniq(@nums);
local $" = ":";
print "@un_nums\n";
OUTPUT:
17:10:20:33:30:40:15:16
| [reply] [d/l] [select] |
|
It would be better to use the "uniq" function of List::Util
Beware List::Util::uniq() with numbers.
There's no problem with the integer values being thrown about in this thread, but uniq looks at the stringified values, and there are large numbers of floating point values (including many that represent large integer values) that are unequivalent, yet stringify to the same string:
use warnings;
use List::Util qw(uniq uniqnum);
$x = sqrt 2.0;
# Create $y with a less precise
# approximation of sqrt 2
$y = "$x" + 0;
#$y != $x
print "unequivalent\n" if $x != $y;
# But $x and $y both stringify
# to 1.4142135623731, hence:
$count = uniq($x, $y);
print $count;
__END__
# Outputs:
unequivalent
1
Using uniqnum instead of uniq yields a value of 2 for $count .
If you want to weed out duplicate strings, use uniq or uniqstr .
But if you want to safely weed out duplicate numeric values, use uniqnum ... and, even then, use only the uniqnum implementation that comes with List::Util-1.55 or later.
(Actually, there might be other modules that now implement a uniqnum function correctly .... I haven't checked.)
Cheers, Rob | [reply] [d/l] [select] |
|
pritesh_ugrankar: Note that List::Util::uniq() will
return unique elements in the exact order of the input list (less
duplicates, of course); this can be very useful in some cases. A duplicate
removal approach like that shown here will return unique
elements in random order.
Also note that in older (and also in current) versions of Perl, uniq()
can also be found in the List::MoreUtils module; it has not always
lived in List::Util.
Give a man a fish: <%-{-{-{-<
| [reply] [d/l] [select] |
Re: Is it safe to use join on a hash?
by GrandFather (Saint) on Sep 07, 2020 at 03:20 UTC
|
use strict;
use warnings;
my @nums = (17, 10, 20, 33, 30, 40, 10, 33, 40, 15, 16, 17);
my %values;
++$values{$_} for @nums;
print join "\n", map{"$_: $values{$_}"} sort {$a <=> $b} keys %values;
which prints an ordered list of the unique values and their counts:
10: 2
15: 1
16: 1
17: 2
20: 1
30: 1
33: 2
40: 2
Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
| [reply] [d/l] [select] |
Re: Is it safe to use join on a hash?
by clueless newbie (Curate) on Sep 06, 2020 at 22:55 UTC
|
| [reply] |
Re: Is it safe to use join on a hash?
by perlfan (Vicar) on Sep 07, 2020 at 04:09 UTC
|
In addition to relying on the return of keys or values, a hash is just a list. It can be seen an array where there exists an ordering between each tuple (key, value). BUT there is no ordering among tuples. The => is also known as a fat comma.
This is why you can assign a hash, equivalently like:
my %hash1 = ('keyA' => 'valA', 'keyB' => 'valB');
my %hash2 = ('keyA', 'valA', 'keyB', 'valB');
my %hash3 = ('keyA' => 'valA'=> 'keyB' => 'valB');
And use it to capture arguments in a subroutine into a hash, like:
sub mySub {
my %args = @_
#... do stuff
}
# and call it equivalently like
mySub ('keyA' => 'valA', 'keyB' => 'valB');
mySub ('keyA', 'valA', 'keyB', 'valB');
This is also why you may see the error, "Odd number of elements in anonymous hash". It's because list assignment to a hash must have an even number of elements (i.e., key/value pairs).
Finally, to get back to your question. It is not only safe to use join on the output of keys, but also on the hash itself:
my $stringA = join ',', %hashA;
#additionally, this is valid (to further demonstrate the point):
my @arrayA = %hashA;
It bears repeating that hash to array assignment, key/value ordering between pairs is necessarily retained, but ordering among tuples is not guaranteed.
update: fixed spelling of comma! | [reply] [d/l] [select] |
Re: Is it safe to use join on a hash?
by pritesh_ugrankar (Monk) on Sep 07, 2020 at 17:44 UTC
|
Respected Monks,
Thank you for your patience and readiness to help and guide. I would love to upvote the answers, but I do not see the upvote option anymore for any of the answers. Is it temporarily disabled?
| [reply] [d/l] [select] |
|
| [reply] |
|
|