anadem has asked for the wisdom of the Perl Monks concerning the following question:

If a hash has only a single value, is there a nice way to get the value without knowing the key and without a foreach loop? The following code illustrates what I've got (with some extra layers, in case any kind monk cares to critique it and help me improve). I need to get the sourcefile for the WinBinary component; it seems a bit weird to use foreach because the "target" key only ever has a single value but can vary between data files. I need to avoid storing the value of the "target" key.
#--------------------- component -- targetver -- Arr -- datahash # # $ComponentData = { # 'key_A' => { # '6.0' => [ # { # 'FILENAME' => 'fi +lename', # 'VERSION' => 've +rsion'; # ooo # }, # ooo # ], # ooo # }, # ooo # } # # where ooo represents potential repetition # my %ComponentData; sub store_data { my $parmref = shift; # input parameter is a hash ref my $component = $parmref->{COMPONENT}; my $tgtver = $parmref->{TGTVER}; my $version = $parmref->{VERSION}; my $file = $parmref->{FILENAME}; # store data $ComponentData{$component}->{$tgtver} = [] unless exists $Componen +tData{$component}->{$tgtver}; my $href = {}; $href->{VERSION} = $version; if( $file ){ $href->{FILENAME} = $file }; push @{$ComponentData{$component}->{$tgtver}}, $href; } sub oldstore_data { # store a data hash $ComponentData{$component}->{$targetver} = [] unless exists $Compo +nentData{$component}->{$targetver}; my $href = {}; if( $file ){ $href->{FILENAME} = $file }; if( $checksum ){ $href->{CHECKSUM} = $checksum }; # etc push @{$ComponentData{$component}->{$targetver}}, $href; return; } # show all the data sub show_all_data { foreach my $comp ( sort keys %ComponentData ) { print "- - - - -\n"; print "component:$comp\n"; foreach my $tgtver ( sort keys %{ $ComponentData{$comp}} ) { print " target:$tgtver\n"; for my $href (@{ $ComponentData{$comp}{$tgtver} }) { print " version:", $href->{VERSION},"\n"; if( $href->{FILENAME} ) { print " file: ", $href->{FILENAME},"\n"; }else{ print " file: undefined\n "; } } print "\n"; } } } while (<DATA>) { chomp; $_ =~ s/ +//g; my ($comp, $tgtver, $version, $file ) = split /\|/; #print "comp=$comp, tgtver=$tgtver, version=$version, file=$file;\ +n"; my $href = {}; $href->{COMPONENT} = $comp; $href->{TGTVER} = $tgtver; $href->{VERSION} = $version; if( $file ){ $href->{FILENAME} = $file; } store_data( $href ); } show_all_data ; # is there a nice way to get the value I want here, without foreach? foreach my $xstgt ( sort keys %{ $ComponentData{ "WinBinary" }} ) { my $specialsource = ${@{ $ComponentData{ "WinBinary" }{$xstgt}}[0] +}{FILENAME}; print "what I want is $specialsource\n" } print "done/n"; # component target version sourcefile __DATA__ WinBinary | 4.0 | 7.8.9.123 | winbinsrc NetBinary | 5.0 | 10.1.2.34 | netbinsrc SupplementalPack | 6.0 | 1.6.0 SupplementalPack | 6.1 | 1.6.0 Hotfix | 6.0 | NULL | hotsource01 Hotfix | 6.0 | NULL | hotsource02 Hotfix | 6.1 | NULL | hotsource03 Hotfix | 6.1 | NULL | hotsource04
  • Comment on Can I avoid loops getting the single value from a hash without knowing the key?
  • Download Code

Replies are listed 'Best First'.
Re: Can I avoid loops getting the single value from a hash without knowing the key?
by LanX (Saint) on Jun 09, 2014 at 23:46 UTC
    > If a hash has only a single value, is there a nice way to get the value without knowing the key and without a foreach loop?

    A hash in list context returns just all pairs, so

      (undef, $value) = %hash

    will do!

    The undef syntax just ignores the key if you really don't need it.

    Regarding your code .... Sorry tl;dr, please keep it short next time!

    HTH! :)

    Cheers Rolf

    (addicted to the Perl Programming Language)

      Ah just what I need, thanks! tl;dr is fine ;-)
Re: Can I avoid loops getting the single value from a hash without knowing the key?
by choroba (Cardinal) on Jun 09, 2014 at 23:53 UTC
    You can use values to get the one member list of values.
    my $special_source = (values %{ $ComponentData{WinBinary} })[0][0]{FIL +ENAME};

    or shorter, but more cryptic

    my $special_source = (%{ $ComponentData{WinBinary} })[1][0]{FILENAME};
    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
      I'll enjoy using that, thanks!
Re: Can I avoid loops getting the single value from a hash without knowing the key?
by Laurent_R (Canon) on Jun 10, 2014 at 06:16 UTC
    You have received good responses to your question, but I would like to expand a little the scope: if your hash is always going to have only one key-value pair, then may be you don't really need this hash and you could start your data structure one level down.
      Thanks, yes agreed. My code was simplified and there are multiple key/value pairs in the actual data.
        Hmm, maybe I misunderstood, but this is difficult to follow. On one hand you ask for a value of a hash with one entry, OTOH you state this situation is simplified, in your real code there are more entries.

        But if there are multiple entries in the hash, LanX's suggestion will put the value of a randomly choosen hash entry in $value. This might not be what you want.

        So I suggest defensive coding:
        first check if the hash only holds one entry if (1 == scalar keys %hash) before (blindly) retrieving a value.

Re: Can I avoid loops getting the single value from a hash without knowing the key?
by AnomalousMonk (Archbishop) on Jun 10, 2014 at 01:57 UTC

    Here's a not-thoroughly-tested | moderately-well-tested recursive approach for any reference to a hash/array-of-hash/array structure of any depth. Whether this is "nice" is perhaps debatable, but at least it avoids a for-loop.

    c:\@Work\Perl\monks>perl -wMstrict -MData::Dump -le "my $ComponentData = { 'key_A' => { '6.0' => [ { 'FILENAME' => 'filename', 'VERSION' => 'version', ooo => ' +xxx', }, ], }, }; dd $ComponentData; ;; print highest_multi_element($ComponentData)->{FILENAME}; print highest_multi_element($ComponentData)->{ooo}; my $coderef = sub { return { ooo => 'yyy' }; }; print highest_multi_element($coderef)->{ooo}; ;; sub highest_multi_element { my $dataref = shift; ;; if (ref $dataref eq 'HASH') { my ($single, $val, $multi) = %$dataref; die 'empty hash' unless defined $single; return defined $multi ? $dataref : highest_multi_element($val); } elsif (ref $dataref eq 'ARRAY') { my $items = @$dataref; die 'empty array' unless $items; return $items > 1 ? $dataref : highest_multi_element($dataref->[0 +]); } else { die qq{not hash/array ref: $dataref}; } } " { key_A => { "6.0" => [ { FILENAME => "filename", ooo => "xxx", VERSION => "version" }, ], }, } filename xxx not hash/array ref: CODE(0x66a6bc) at -e line 1.

    Update: Changed code example as follows:

    • Changed function name from  lowest_multi_element() to  highest_multi_element() because we're actually looking for the highest level multi-element hash/array ref.;
    • Placed the test for a hash reference first because these refs may be (?) more common;
    • Used LanX's hash-to-list-of-scalar-lexicals-flattening hack because it may be slightly faster than extracting all values to an array — and also because I think it's just cooler.
    I've also had a chance to do more testing and I'm more confident about the robustness of the function.

      Thank you. I'll play with my code along those lines