Re: Using 'keys' on a list
by choroba (Cardinal) on Jun 29, 2021 at 12:41 UTC
|
keys doesn't operate on a list, it operates on a hash (or an array since 5.12).
You can create a hash, populate it with the list, and get its keys:
my %h = f();
say for keys %h;
You can also just return every second element of the list:
my $i;
say for grep ++$i % 2, f();
map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
| [reply] [d/l] [select] |
|
for my ($key, $value) (f()) {
say $key;
}
I guess that this will come in 5.36 as experimental feature and hopefully will leave the experimental stage with 5.38. | [reply] [d/l] |
|
And this would have its own iterator?
| [reply] |
|
|
Thanks for the response =).
keys doesn't operate on a list
Do you know why this might be? Maybe because strictly speaking, only hashes have keys, and lists don't? But then perl isn't usually that strict e.g. as you mentioned from 5.12 keys can operate on an array.
I ask because I was hoping to be able to do this in one line. It's not a big deal but I'm kind of curious to see if it's possible.
| [reply] |
|
> Do you know why this might be?
Lists don't have keys. For arrays, keys returns the indices of the elements, not every second element. So, given a list, should keys use the hash semantics or the array one?
Also note that a "list" in fact doesn't exist in Perl. There's a list context, but there always has to be something in the context, and this something is never a list. It might be a hash, it might be an array, or a sequence of comma operators. But keys doesn't want to see the hash (or array) in a list context, it wants to see its underlying HV or AV (which also makes getting the keys much faster, Perl doesn't really iterate over alternating keys and values, skipping the latter).
map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
| [reply] [d/l] |
|
|
|
Re: Using 'keys' on a list
by tybalt89 (Monsignor) on Jun 29, 2021 at 18:40 UTC
|
my @keys = keys %{{f}};
is much more elegant than the equivalent
use List::Util qw( shuffle uniq pairkeys );
my @keys = shuffle uniq pairkeys f;
Remember that hashes do uniq'ing and shuffling for free :)
UPDATE: added a missed "keys" as LanX pointed out.
| [reply] [d/l] [select] |
|
>
my @keys = %{{f}};
Did you mean:
my @keys = keys %{{f}};
???
| [reply] [d/l] [select] |
|
| [reply] |
|
| [reply] |
Re: Using 'keys' on a list (updated)
by haukex (Archbishop) on Jun 29, 2021 at 17:13 UTC
|
You can use pairkeys from List::Util, i.e. say for pairkeys f; works fine. You'll need List::Util 1.29 for this function, which is part of the Perl core since 5.20, or you can get it from CPAN.
Update: tybalt89 makes a good point that pairkeys may return duplicate "keys", unlike keys.
| [reply] [d/l] [select] |
Re: Using 'keys' on a list
by LanX (Saint) on Jun 29, 2021 at 12:37 UTC
|
> but I find this kind of inelegant.
well, how would you do it in other "more elegant languages"?
> I'm far far from a monk so bear with me, but I can't see why keys doesn't provide list context.
keys HASH operates on the datatype hash not list, which means something starting with a % sigil here
> "Experimental keys on scalar is now forbidden"
that's another topic which wouldn't have helped you much, but you "probably" would have been able to write keys {f()}
Questions are:
HTH! :)
edit
my preferred way to gain elegance would be a private method, operating on a reference
my $keys = sub { my $self =shift; keys %$self };
sub f {
return { a=>1, b =>2 }
}
print for f->$keys;
edit
> Also, is there a way of creating a hash for consumption by keys in the above situation which doesn't involve creating a hash reference from a list and then immediately dereferencing?
could you please elaborate? I don't understand the question ...
update
see also Re^10: Using 'keys' on a list (prototype & backwards compatibility) for a full explanation why your feature request can't possibly be implemented.
| [reply] [d/l] [select] |
|
Firstly, thanks for your response, much appreciated =).
could you please elaborate? I don't understand the question ...
I'm referring to this %{{f}} code which creates a transient hash (which keys can then operate on) from a hash reference. Perhaps I'm not using the jargon correctly, my apologies.
I don't know of more elegant ways to do this in other languages to be honest. I was hoping that it were possible to create a transient hash more directly with something like
keys %(f)
for example.
Creating a helper routine which hides this away or adjusting the f() routine to return a hash reference would both work, but the former sacrifices the brevity I'm after and the latter may not always be feasible.
keys HASH operates on the datatype hash not list, which means something starting with a % sigil here
Do you know if there's a reason that it can't operate on a list?
| [reply] [d/l] [select] |
|
> Do you know if there's a reason that it can't operate on a list?
Lists are the most basic containers in perl syntax but not a variable's data type.
A list is not necessarily a hash, you can have odd numbers and dupplicate keys or irregular "keys"
Keys would also be more vulnerable to syntax errors.
Many other languages don't even know how to handle lists.
But you're free to implement the workarounds I've shown you.
| [reply] |
Re: Using 'keys' on a list
by ikegami (Patriarch) on Jun 29, 2021 at 20:03 UTC
|
The issue is that keys requires a hash (or an array), but you are (attempting to) provide it a bunch of scalars.
But while keys doesn't help, working with such data is exactly the purpose of the pair* functions in core module List::Util.
say for pairkeys f;
You could also use grep, but it's a bit inelegant.
sub pairkeys { my $i = 0; grep { $i ^= 1 } @_ }
Seeking work! You can reach me at ikegami@adaelis.com
| [reply] [d/l] [select] |
Re: Using 'keys' on a list
by Marshall (Canon) on Jun 30, 2021 at 00:46 UTC
|
Perhaps consider this:
Updated code to be perhaps more helpful...
I think an issue here that when you "say", you have to say what you want to "say"!
use strict;
use warnings;
use Data::Dumper;
use v5.10;
sub f {a=>1,b=>2}
my %result = f(); #converts a a hash passed as an arry
#back into a hash
print Dumper \%result;
print "to print just the keys of the hash:\n";
say "$_" for keys %result;
# this is much faster, execution wise...
# passes back a referecne to a hash that has
# been already been created.
sub x
{
my %result;
$result{a}=1; #more likely way to set the results
$result{b}=2;
return \%result;
}
my $hash_ref = x();
print "to print the keys of this hash\n";
print "$_\n" for keys (%$hash_ref);
print Dumper $hash_ref;
__END__
$VAR1 = {
'b' => 2,
'a' => 1
};
to print just the keys of the hash:
b
a
to print the keys of this hash
a
b
$VAR1 = {
'a' => 1,
'b' => 2
};
PS: I did find that say for keys %{{f}}; did indeed work.
I didn't expect that result, but it does "work".
I find say "$_" for keys %{{f}}; to be easier to understand.
However, Perl is gonna make a hash from the return value of f() whether or not you give it name. I prefer the above syntax.
| [reply] [d/l] [select] |
|
| [reply] [d/l] |
|
I don't think a benchmark makes much sense with this very small contrived example. Most of the hashes I work with have a lot more than 2 keys! In general passing back a single thing (ref to a hash) is gonna be much faster than passing back a list of key/value pairs so that the hash can be re-created by the caller. The bigger the hash is, the more apparent this speed difference is going to be. With 2 keys, there isn't a huge difference in a practical sense (impact on total application performance might not be anything of note). With say 80,000 keys, there is a lot of difference between the 2 methods of passing back an entire hash!
Now if all you need are the keys instead of the full hash, yes, there won't be much of a difference at all. The sub could traverse the hash and make a list. Or the sub gives the caller the ref and the caller traverses the hash to make a list. The effect and performance will be about the same. Sometimes these very short snippets of code don't reflect what is going on the in overall application. The code as formulated is a rather odd idea, pass a list of key/value pairs to the sub, only to ask what the keys are? Why have to make a hash in the first place? I dunno.
| [reply] |
|
|
|
Re: Using 'keys' on a list
by glycine (Sexton) on Jun 29, 2021 at 17:57 UTC
|
>Also, is there a way of creating a hash for consumption by 'keys' in the above situation which doesn't involve creating a hash reference from a list and then immediately dereferencing?
this is because hash hope first argument is a hash or array, so if offer a list the first argument is first element of list.
so keys just can't work on 'a' (in your example)...
if want keys work on return values of f(), the list still need stored into hash or array.
my %hash = f();
my @keys = keys %hash;
but you hope finish in one line but don't want %{{}}
I can't offer any to avoid hash reference, but if the pupose just look tidy
maybe let subroutine f() return reference?
so we can use:my @keys = keys %{f()};
#or in postfix dereferencing
my @keys = keys f()->%*;
hope this can help.
edit:I see you say you don't want :(
| [reply] [d/l] [select] |
|
| [reply] |
|
> no, list in scalar context returns the last value
That's wrong.
A comma separated list returns the last value in scalar context, otherwise it always depends on the operator.
update
surrounding a LIST with (...)[-1] is a reliable way to get the last element
DB<7> x (1..5)[-1]
0 5
DB<8>
update
see also
| [reply] [d/l] [select] |
|
|
|
|
A "list in scalar context" doesn't exist.
| [reply] |
|
|
Re: Using 'keys' on a list
by Marshall (Canon) on Jul 06, 2021 at 23:41 UTC
|
Further down in this thread, I got trolled by an Anon Monk.
Instead of complicated syntax, I would recommend something easier.
Performance-wise it is always better to return fewer things from the sub() that more things.
That should be obvious!
The fastest way (performance-wise) and often the easiest way for a sub() to return a hash to the caller is via a reference to the hash.
Don't worry at all about taking a couple of lines of code vs one line. Sometimes 3 lines are faster and easier to understand than a single line.
Here are some benchmarks for your perusal.
Number (3) below is a "go to" winner!
use strict;
use warnings;
use Benchmark;
use Data::Dumper;
# Note: You can increment a string in Perl! WOW!
# As long as you don't use the string in a numeric context!
# $string++ is quite different than $string +=1
#
# Below, this is used to make a bunch of unique hash keys
# I don't think that the fact that they are sequential in
# an alphabetic sense makes much difference in the generated
# hash table because of the way that the Perl hash algorithm
# works.
sub return_hash
{
# create a large hash and return that entire hash as list
my $string = "ABCDEFGHIJ";
my %hash = map{$string++ => 1}(1..100000);
return %hash;
}
sub return_hash_ref
{
# create a large hash and return a ref to that hash
my $string = "ABCDEFGHIJ";
my %hash = map{$string++ => 1}(1..100000);
return \%hash;
}
sub return_just_keys
{
# create a large hash and return just the keys of that hash
my $string = "ABCDEFGHIJ";
my %hash = map{$string++ => 1}(1..100000);
return keys %hash;
}
timethese(1000, {
'1)Keys of Hash via list' => 'my @keys = keys %{{return_hash()}}',
'2)Keys of Local Hash copy' => 'my %hash2 = return_hash(); my @key
+s = keys %hash2;',
'3)Keys of local Hash Ref' => 'my $href = return_hash_ref(); my @k
+eys = keys %$href;',
'4)Just the returned keys' => 'my @keys = return_just_keys()',
});
__END__
Benchmark: timing 1000 iterations of
1)Keys of Hash via list,
2)Keys of Local Hash copy,
3)Keys of local Hash Ref,
4)Just the returned keys...
1)Keys of Hash via list: 182 wallclock secs (179.20 usr + 1.97 sys =
+181.17 CPU) @ 5.52/s (n=1000)
2)Keys of Local Hash copy: 184 wallclock secs (182.69 usr + 0.42 sys
+= 183.11 CPU) @ 5.46/s (n=1000)
3)Keys of local Hash Ref: 130 wallclock secs (129.56 usr + 0.72 sys =
+ 130.28 CPU) @ 7.68/s (n=1000)
4)Just the returned keys: 125 wallclock secs (125.20 usr + 0.52 sys =
+ 125.72 CPU) @ 7.95/s (n=1000)
Added: When looking at benchmarks, don't worry so much about 182 vs 184. That could be an artifact of a
particular run. Most of the CPU MIP's are being consumed by creating and re-creating the hash. Look at
~180 vs ~130. The relative performance difference between the 2 methods of passing the result back is
actually much, more than that because generating the hash "takes a lot of effort".
| [reply] [d/l] |
|
I'm surprised that returning a list of keys is faster than returning a hashref and then getting its keys. A dereference requires more operations, but should it be that much?
A good chunk of the time is spent in building the hash, so I added a baseline sub to measure it. I also reduced the number of iterations so it would run in reasonable time on my machine. And some minor formatting of results.
This is perl 5, version 28, subversion 0 (v5.28.0) built for MSWin32-x
+64-multi-thread
# Adapted from https://www.perlmonks.org/?node_id=11134740
use strict;
use warnings;
use Benchmark;
use Data::Dumper;
# Note: You can increment a string in Perl! WOW!
# As long as you don't use the string in a numeric context!
# $string++ is quite different than $string +=1
#
# Below, this is used to make a bunch of unique hash keys
# I don't think that the fact that they are sequential in
# an alphabetic sense makes much difference in the generated
# hash table because of the way that the Perl hash algorithm
# works.
sub baseline {
# create a large hash but return nothing
my $string = "ABCDEFGHIJ";
my %hash = map{$string++ => 1}(1..100000);
return;
}
sub return_hash
{
# create a large hash and return that entire hash as list
my $string = "ABCDEFGHIJ";
my %hash = map{$string++ => 1}(1..100000);
return %hash;
}
sub return_hash_ref
{
# create a large hash and return a ref to that hash
my $string = "ABCDEFGHIJ";
my %hash = map{$string++ => 1}(1..100000);
return \%hash;
}
sub return_just_keys
{
# create a large hash and return just the keys of that hash
my $string = "ABCDEFGHIJ";
my %hash = map{$string++ => 1}(1..100000);
return keys %hash;
}
timethese(200, {
'1)Keys of Hash via list ' => 'my @keys = keys %{{return_hash()}}
+',
'2)Keys of Local Hash copy' => 'my %hash2 = return_hash(); my @key
+s = keys %hash2;',
'3)Keys of local Hash Ref ' => 'my $href = return_hash_ref(); my @
+keys = keys %$href;',
'4)Just the returned keys ' => 'my @keys = return_just_keys()',
'5)Baseline ' => 'my $res = baseline()',
});
__END__
Benchmark: timing 200 iterations of 1)Keys of Hash via list , 2)Keys
+of Local Hash copy, 3)Keys of local Hash Ref , 4)Just the returned ke
+ys , 5)Baseline ...
1)Keys of Hash via list : 50 wallclock secs (49.44 usr + 1.52 sys =
+50.95 CPU) @ 3.93/s (n=200)
2)Keys of Local Hash copy: 50 wallclock secs (49.98 usr + 0.38 sys =
+50.36 CPU) @ 3.97/s (n=200)
3)Keys of local Hash Ref : 35 wallclock secs (34.75 usr + 0.34 sys =
+35.09 CPU) @ 5.70/s (n=200)
4)Just the returned keys : 33 wallclock secs (32.51 usr + 0.16 sys =
+32.67 CPU) @ 6.12/s (n=200)
5)Baseline : 25 wallclock secs (24.86 usr + 0.20 sys =
+25.06 CPU) @ 7.98/s (n=200)
| [reply] [d/l] [select] |
|
I liked your idea of adding a "baseline"!
I also really don't know either why generating the list of keys in the sub appears to be faster then giving the caller the ref and having him do it? I would have expected that difference to be smaller. Hopefully some other Monk knows?
However, the main point remains: If the caller needs the whole hash, give him a ref to a hash. This is much faster than passing the entire hash back as a list. Of course there are memory allocation issues with that because Perl will keep the memory for that hash allocated as long as there is reference to it.
Added: Except as a part of an object method, I don't know why a sub() in general would create a hash, only to just pass back just the keys? Seems a bit weird, but I'd also like to know why this appears to be somewhat faster.
| [reply] |
|
|
|
|