Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

Hash/Array slice : how to exclude items?

by bliako (Monsignor)
on Jan 24, 2023 at 12:56 UTC ( #11149816=perlquestion: print w/replies, xml ) Need Help??

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

Replies are listed 'Best First'.
Re: Hash/Array slice : how to exclude items?
by hippo (Bishop) on Jan 24, 2023 at 13:46 UTC

    Sounds like you want Data::Dump::Filtered:

    #!/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";

    🦛

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'] );
      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; }
        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]

        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 };
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}];

    Dave

Re: Hash/Array slice : how to exclude items?
by ikegami (Patriarch) on Jan 24, 2023 at 21:05 UTC

    Given:

    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 );
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.

      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

Re: Hash/Array slice : how to exclude items?
by johngg (Canon) on Jan 24, 2023 at 13:58 UTC

    Somewhat messy but you could grep a negated regex in a hash slice.

    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.

    Cheers,

    JohnGG

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.

    Cheers Rolf
    (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
    Wikisyntax for the Monastery

    ) the +{...} is just an anonymous hash for pairwise dump

      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>

      Cheers Rolf
      (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
      Wikisyntax for the Monastery

        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'
Re: Hash/Array slice : how to exclude items?
by haukex (Archbishop) on Jan 28, 2023 at 14:23 UTC

    A while ago I whipped up Tie::Subset::Hash, and I took this oppertunity (and the inspiration from Discipulus's node) to expand upon that with Tie::Subset::Hash::Masked:

    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 };
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).
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'}
    

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11149816]
Approved by marto
Front-paged by haukex
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (4)
As of 2023-02-01 03:11 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?