bliako has asked for the wisdom of the Perl Monks concerning the following question:
I would like to print a hash but exclude some of its keys which I know they clog the logfile with useless info (e.g. schema and log objects). I don't want to delete the keys before printing it (it is passed on as parameter to other subs). Is there an exclude-slice selection? e.g. %hash{-'schema', -'log'}, meaning include everything except those. Obviously the minus sign can be part of the keyname and has a meaning in array indexing. So that's not going to fly. Anything else?
I have googled this earlier but did not find anything. If there isn't, is it because of the lack of operators? Can this be a feature for the future? If it exists, then how (without grep and friends)?
bw, bliako
Re: Hash/Array slice : how to exclude items?
by hippo (Bishop) on Jan 24, 2023 at 13:46 UTC
|
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dump 'dumpf';
my $hr = {
schema => 'foo',
whatever => 'you want',
log => 'bar',
quux => {
log => 'baz',
still => 'want this'
}
};
print dumpf (
$hr,
sub {
return {hide_keys => [qw/log schema/]};
}
) . "\n";
| [reply] [d/l] |
Re: Hash/Array slice : how to exclude items?
by Corion (Patriarch) on Jan 24, 2023 at 13:03 UTC
|
If this is only about the top-level items, I would create a shallow copy and delete the keys there (or only keep the "good" keys):
sub debug_hash( $hashref, $bad_keys = [qw[ schema logger ]] ) {
my %output = %$hashref;
delete @output{ @$bad_keys };
print Dumper \%output;
}
debug_hash( { logger => 'foo', payload => 'bar' } );
debug_hash( { schema => 'users', password => 'hunter2' }, ['schema', '
+password'] );
| [reply] [d/l] |
|
I would create a shallow copy and delete the keys there
An alternative:
sub debug_hash ( $hashref, $bad_keys = [qw/ schema logger /] ) {
delete local @$hashref{ @$bad_keys };
print Dumper $hashref;
}
| [reply] [d/l] |
|
delete local requires 5.12 (as mentioned in the link you provided, too, I just think it's worth mentioning here).
map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
| [reply] [d/l] |
|
delete local looks promising, thanks haukex and ikegami. If only delete returned the remaining hash. Shouldn't this work?: print Dumper(do { delete local @$hashref{ @$bad_keys }, $hashref} ); or this print Dumper(do { delete local @$hashref{ @$bad_keys }; $hashref} ); My intention is to delete the elements of the local hash, pass that to Dumper and hopefully leave it untouched when Dumper returns.
my @unwanted = qw( schema log );
my %hash = (schema=>1, log=>2, aa=>3);
print Dumper(delete local @hash{ @unwanted }, \%hash);
print Dumper(\%hash);
$VAR1 = 1;
$VAR2 = 2;
$VAR3 = {
'aa' => 3
};
$VAR1 = {
'aa' => 3
};
my @unwanted = qw( schema log );
my %hash = (schema=>1, log=>2, aa=>3);
print Dumper(do { delete local @hash{ @unwanted }; \%hash });
print Dumper(\%hash);
$VAR1 = {
'aa' => 3,
'log' => 2,
'schema' => 1
};
$VAR1 = {
'aa' => 3,
'log' => 2,
'schema' => 1
};
| [reply] [d/l] [select] |
|
|
|
|
|
Re: Hash/Array slice : how to exclude items?
by davido (Cardinal) on Jan 24, 2023 at 16:19 UTC
|
use Data::Dumper;
# .....
print Dumper [@hash{grep {$_ ne 'schema' && $_ ne 'log'} keys %hash}];
| [reply] [d/l] |
Re: Hash/Array slice : how to exclude items?
by ikegami (Patriarch) on Jan 24, 2023 at 21:05 UTC
|
my @unwanted = qw( schema log );
You can use:
my %unwanted = map { $_ => 1 } @unwanted;
my @wanted = grep !$unwanted{ $_ }, keys %hash;
my %subhash = %hash{ @wanted };
do_something( \%sub_hash );
Or:
my %subhash = %hash;
delete @subhash{ @unwanted };
do_something( \%sub_hash );
Or:
delete local @hash{ @unwanted };
do_something( \%hash );
| [reply] [d/l] [select] |
Re: Hash/Array slice : how to exclude items? -- tie hash to mask values
by Discipulus (Abbot) on Jan 26, 2023 at 09:17 UTC
|
Hello bliako,
very interesting question and discussion. I am not able to offer a solution but this lead to me to something different: temporary hide or mask the value of some key of a tied hash.
Mostly copying ikegami's SO answer I got something working.
I'm sure some monk can play also with FIRTSKEY and NEXTKEY vodoo to make some key temporary invisible (see the comment line in fetch..), not me :)
package MaskKeys;
use strict;
use warnings;
use Tie::Hash ();
our @ISA = 'Tie::ExtraHash';
sub TIEHASH {
my $class = shift;
my $content = shift;
my $tomask = shift;
my $self = bless([{@$content}, {}]);
$self->[1]{$_} = 1 for @$tomask;
return $self;
}
sub FETCH {
my $self = shift;
my $key = shift;
if ( $self->[1]{$key} ){
return 'NA';
# {return $self->{ $self->FETCH($self->SUPER::NEXTKEY ($ke
+y))} || undef ;
}
else { return $self->[0]{$key} }
}
sub mask_key{
my $self = shift;
my $key = shift;
$self->[1]{$key} = 1;
}
sub unmask_key{
my $self = shift;
my $key = shift;
# delete @{ $self->[1] }{ $key }; unecessary from cut and paste co
+de that accepted a list
delete $self->[1]{$key};
}
package main;
# content # to mask
tie my %hash, 'MaskKeys',[schema=>11111, aaaaaa=>33333], ['schema'];
print "\noriginally masked: \n";
print "$_ -> $hash{$_}\n" for keys %hash;
print "\nunmasked: \n";
tied(%hash)->unmask_key('schema');
print "$_ -> $hash{$_}\n" for keys %hash;
print "\nmasked again: \n";
tied(%hash)->mask_key('schema');
print "$_ -> $hash{$_}\n" for keys %hash;
__END__
originally masked:
aaaaaa -> 33333
schema -> NA
unmasked:
aaaaaa -> 33333
schema -> 11111
masked again:
aaaaaa -> 33333
schema -> NA
L*
There are no rules, there are no thumbs..
Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
| [reply] [d/l] [select] |
|
That's a good solution Discipulus.
The mask/unmask (re: see also rsFalse's Re: Hash/Array slice : how to exclude items?) can be achieved via magic/dualavar. I cite here all contributions to Schizophrenic var. Question is where should the hidden attribute go: key, values and/or hash.
Another thing that comes to mind is that may lead to something quite bigger borrowing from the framework of Model-View-Controller: you have the data in the hash and you get different views from it: hidden/colors (rsFalse)/etc.
bw, bliako
| [reply] |
Re: Hash/Array slice : how to exclude items?
by johngg (Canon) on Jan 24, 2023 at 13:58 UTC
|
johngg@aleatico:~$ perl -Mstrict -Mwarnings -MData::Dumper -E 'say q{}
+;
my %hash = ( qw{ a 1 b 2 c 3 d 4 e 5 f 6 } );
print Data::Dumper->Dumpxs( [ \ %hash ], [ qw{ *hash } ] );
my @excl = qw{ b f };
my $rxExcl = do {
local $" = q{|};
qr{^@excl$};
};
print Data::Dumper->Dumpxs(
[ [ @hash{ @{ [ grep { ! m{$rxExcl} } keys %hash ] } } ] ],
[ qw{ *slice } ]
);'
%hash = (
'd' => '4',
'b' => '2',
'a' => '1',
'c' => '3',
'e' => '5',
'f' => '6'
);
@slice = (
'4',
'1',
'3',
'5'
);
I hope this is of interest.
Update: I renamed the second Data::Dumper->Dumpxs() output from @hash to @slice to better reflect what was going on.
| [reply] [d/l] [select] |
Re: Hash/Array slice : how to exclude items?
by LanX (Sage) on Jan 24, 2023 at 14:15 UTC
|
I suppose that you know that %hash{...} is a new slice syntax that returns the keys too, and that's your intention.
Regarding your syntax
> %hash{-'schema', -'log'}
Well "-" has a meaning in arrays, so I'd rather prefer something else like "!"
But this would get messy if the list also included non-negated elements,
%hash{'schema', -'log'} # WHAT???
so for the sake of a clean design one would rather do only one negation
%hash{! 'schema', 'log'}
I'm not aware of a built-in syntax to do this, but it's basically syntactic sugar for a grep, see demo in debugger
DB<1> @hash{'schema','log',a..c}=('s','l',1..3)
DB<2> x \%hash
0 HASH(0x3168da0)
'a' => 1
'b' => 2
'c' => 3
'log' => 'l'
'schema' => 's'
DB<3> x +{ %hash{grep !/^schema|log$/, keys %hash } } # Note °
0 HASH(0x32e09d8)
'a' => 1
'b' => 2
'c' => 3
DB<4>
I know you didn't want grep, but this offers many flexibilities, like dynamically deciding what to exclude.
°) the +{...} is just an anonymous hash for pairwise dump | [reply] [d/l] [select] |
|
you can also design your own pseudo-operator ->$exclude
DB<7> $exclude = sub { my $href = shift; my $excl= join '|', @_; r
+eturn +{ %$href{ grep !/^$excl$/, keys %$href } } }
DB<8> x $h_ref
0 HASH(0x3168da0)
'a' => 1
'b' => 2
'c' => 3
'log' => 'l'
'schema' => 's'
DB<9> x $h_ref->$exclude(qw/log schema/)
0 HASH(0x32e1158)
'a' => 1
'b' => 2
'c' => 3
DB<10>
| [reply] [d/l] [select] |
|
x +{ %hash{grep !/^schema|log$/, keys %hash } }
$exclude = sub { my $href = shift; my $excl= join '|', @_; return +{ %$href{ grep !/^$excl$/, keys %$href } } }
Noting, of course, that because there are no parentheses in either of these regex, the ^ anchor only applies to the first term in the alternation list, the $ anchor only applies to the final term in the alternation list, and any intermediate terms will be completely unanchored, which means that it's likely to exclude more keys than you might think: therefore, running ->exclude(qw/log schema/) will not necessarily match running ->exclude(qw/schema log/) , depending on the keys of the hash, which may or may not have been the intention.
DB<1> @hash{'schema','log',a..c, 'schematic', 'blog'}=('s','l',1..3,
+'circuit','rant')
DB<2> x \%hash
0 HASH(0x3da37a8)
'a' => 1
'b' => 2
'blog' => 'rant'
'c' => 3
'log' => 'l'
'schema' => 's'
'schematic' => 'circuit'
DB<3> x +{ %hash{grep !/^schema|log$/, keys %hash } } # Note that t
+he ^ only applies to schema, and the $ only applies to log, so it wil
+l remove more than you might have meant
0 HASH(0x3f6e6f8)
'a' => 1
'b' => 2
'c' => 3
DB<4> x +{ %hash{grep !/^(schema|log)$/, keys %hash } } # This vers
+ion has all exclusions fully anchored
0 HASH(0x3f6ed10)
'a' => 1
'b' => 2
'blog' => 'rant'
'c' => 3
'schematic' => 'circuit'
DB<5> $exclude1 = sub { my $href = shift; my $excl= join '|', @_;
+return +{ %$href{ grep !/^$excl$/, keys %$href } } }
DB<6> $h_ref = \%hash
DB<7> x $h_ref->$exclude1(qw/schema log/)
0 HASH(0x3f73640)
'a' => 1
'b' => 2
'c' => 3
DB<8> x $h_ref->$exclude1(qw/log schema/)
0 HASH(0x3f73988)
'a' => 1
'b' => 2
'blog' => 'rant'
'c' => 3
'schematic' => 'circuit'
DB<9> $exclude2 = sub { my $href = shift; my $excl = join '|', @_; r
+eturn +{ %$href{ grep !/^($excl)$/, keys %$href } } }
DB<10> x $h_ref->$exclude2(qw/schema log/)
0 HASH(0x3f73808)
'a' => 1
'b' => 2
'blog' => 'rant'
'c' => 3
'schematic' => 'circuit'
| [reply] [d/l] [select] |
|
Re: Hash/Array slice : how to exclude items?
by haukex (Archbishop) on Jan 28, 2023 at 14:23 UTC
|
use Data::Dumper;
use Tie::Subset::Hash::Masked;
my @unwanted = qw/ schema log /;
my %hash = ( schema=>1, log=>2, aa=>3 );
tie my %masked, 'Tie::Subset::Hash::Masked', \%hash, \@unwanted;
print Dumper(\%masked);
__END__
$VAR1 = {
'aa' => 3
};
| [reply] [d/l] |
Re: Hash/Array slice : how to exclude items?
by rsFalse (Chaplain) on Jan 25, 2023 at 09:17 UTC
|
Hi.
It is a very good question and a good wish!
As I understand from above replies, there are no such feature in core Perl.
You need either to use another hash to store specific keys to filter, either you need to copy the whole hash and delete items from it.
In my opinion, it is a good feature request. It could be named 'hide' function. You hide some keys, and later unhide them. And also an additional function 'visible' could be a sister to 'exists'.
Another idea is that hash with hidden keys is a subhash. Possibly there could be several kinds of 'hide' on the same hash, then rather it should be a color of particular subhash. And the simple way to color a hash is to add another dimension of hash: from $hash{ $key } = $value make $hash{ $key }{ 'hidden' } = 1; $hash{ $key }{ 'value' } = $value; (instead of 'hidden', you can use any e.g. '<color>', if you want to make several subhashes). | [reply] [d/l] [select] |
Re: Hash/Array slice : how to exclude items?
by Anonymous Monk on Jan 24, 2023 at 21:56 UTC
|
Is there an exclude-slice selection? e.g. %hash{-'schema', -'log'}, meaning include everything except those.
delete @hash{'schema', 'log'}
| [reply] |
|
|