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

I have a need to get a count of identical items in a list. The first way I did this was to use something like:
  foreach (@a) { $hash{$a}++; } 
  @key = keys %hash;            
  foreach (@key) { print "$_ found $hash{$_} times\n";}
Question: How can I populate the hash without a loop?
  @hash{@a} = (1) x @a; 
doesnt really work to create a count of duplicate hash items.

Replies are listed 'Best First'.
Re: How can I populate a hash?
by trammell (Priest) on Jan 25, 2005 at 22:31 UTC
    I'm curious why it matters how you populate the hash. It's like saying "I want to print a string, but I don't want to use print()." Are you just a masochist or something?
Re: How can I populate a hash?
by jdporter (Paladin) on Jan 25, 2005 at 22:35 UTC
    Counting is probably the best way, even though it isn't exciting. Of course there are other ways. One that comes to mind is:
    my %h; @h{@a} = (); # make a set of the unique strings for my $k ( keys %h ) { $h{$k} = () = grep { $_ eq $k } @a; # get count of matching elements }
    But somehow I doubt that's more efficient. ;-)
Re: How can I populate a hash?
by Aristotle (Chancellor) on Jan 25, 2005 at 22:30 UTC

    You can't do that without an explicit loop. It's the nature of hash assignments. The right-hand list is only evaluated once, with competing keys successively overwriting each other on assignment, so it's not possible to do such a calculation.

    As an aside, Perl6 hyperops would allow this. The closest Perl5 gets is

    ++$_ for @count{ @a };

    Makeshifts last the longest.

      You can do it without an explicit loop if you're sick enough to try:

      #!/usr/bin/perl use strict; use warnings; use Test::More tests => 3; package CountHash; sub TIEHASH { my $class = shift; bless {}, $class; } sub FETCH { my ($self, $key) = @_; return unless exists $self->{$key}; return $self->{$key}; } sub STORE { my ($self, $key, $value) = @_; ($self->{$key} ||= 0 )++; } sub FIRSTKEY { my $self = shift; my $first = keys %$self; each %$self; } sub NEXTKEY { my $self = shift; each %$self; } package main; tie my %hash, 'CountHash'; my @items = ( 1, 2, 3, 3, 3, 4, 5, 5, 5, 5 ); @hash{ @items } = (); is( keys %hash, 5, 'CountHash should not store duplicate keys' ); is( $hash{1}, 1, '... counting individual keys once' ); is( $hash{3}, 3, '... multiple keys multiple times' );

        Hmm. It occurs to me that if the goal's to do it in a single statement, it can be done quite easily:

        #!/usr/bin/perl use strict; use warnings; my @k = qw( a b c a b a ); my %i = do { my %h; map { $_ => ++$h{$_} } @k }; print join " ", %i;

        That's cheating of course, but so is tie. :-)

        Makeshifts last the longest.

Re: How can I populate a hash?
by Fletch (Bishop) on Jan 25, 2005 at 22:12 UTC

    No need to use x @a, just assign an empty list.

    DB<1> @a = qw( a b c d a b ) DB<2> @h{@a} = () DB<3> x keys %h 0 'c' 1 'a' 2 'd' 3 'b'

    Update: Gah, yeah I missed the count part. I agree with below it's a "I want to do iterative task X without iterating" problem. Now I'm going to take a nap before attempting to answer anything else . . .

      That will eliminate duplicates, but it will not count them.