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

Is there a tricky way to initialize a hash based on a string (or array), where the keys are each value listed in the string and the corresponding values are their numbers in the sequence (or array index)?

I´m sure there is a better way than the for in:

my $tokens = "32,15,4,72,13,28,14"; my %code; my $i = 0; for my $t (split(/,/, $tokens)) { $code{$t} = $i++; }

Replies are listed 'Best First'.
Re: Convert a string into a hash
by Your Mother (Archbishop) on Aug 14, 2009 at 21:41 UTC

    I suspect all you want is-

    my @code = split /,/, $tokens;

    If you're keeping hash with indices as values, it makes more sense to use an array. There's also this if you really don't care about the indices-

    my %code = map { $_ => 1 } split /,/, $tokens;

    Update: there's also this but I think your original version is easier for most to read-

    my %code; my @code = split /,/, $tokens; $code{$code[$_]} = $_ for 0 .. $#code;

      I do need the corresponding values, because I'll sort a given list of tokens from this set. johngg got the idea!

      This was the best I could do:

      my %code = ( my $i = 0 or map { $_ => $i++ } split /,/, $tokens );

      but I wanted to have no temporary variables... ;-)

        Conditional my isn't allowed. Besides, your code doesn't compile. You want
        my %code = do { my $i = 0; map { $_ => $i++ } split /,/, $tokens };

        If you really want no explicitely declared temp variables, you can use the following:

        my %code = sub { map { $_[$_] => $_ } 0..$#_ }->( split /,/, $tokens ) +;

        But honestly, there's no need to limit the scope that badly. Either of the following are quite suitable:

        my $i = 0; my %code = map { $_ => $i++ } split /,/, $tokens;
        my @code = split /,/, $tokens; my %code = map { $code[$_] => $_ } 0..$#code;

        I can see how you can do that but not why you'd want to do it. The array is sorted already. There is no information in a hash with index values that isn't in an array. So, unless I'm missing something, it buys you nothing except a speed penalty and obfuscates a simple operation/need. If you could explain what you're going to do with it in the end, you might get some more interesting answers approaches. :)

Re: Convert a string into a hash
by johngg (Canon) on Aug 14, 2009 at 22:03 UTC

    You could declare and populate the hash (along with the sequence counter) all in one go using a do block.

    $ perl -e ' > my $tokens = q{32,15,4,72,13,28,14}; > my %code = do{ > my $i = 0; > map { $_ => $i ++ } split m{,}, $tokens; > }; > print qq{$_ => $code{ $_ }\n} > for sort { $code{ $a } <=> $code{ $b } } keys %code;' 32 => 0 15 => 1 4 => 2 72 => 3 13 => 4 28 => 5 14 => 6 $

    I hope this is helpful.

    Cheers,

    JohnGG

Re: Convert a string into a hash
by Marshall (Canon) on Aug 15, 2009 at 00:52 UTC
    The code below demonstrates that $x (scalar), @x (list), %x (hash) are different things.
    Your instinct that anything with Perl using array indices is probably wrong, is correct.
    #!/usr/bin/perl -w use strict; use Data::Dumper; my $tokens = "32,15,4,72,13,28,14"; my @tokens = (split/,/,$tokens); my %tokens = map {$_ => 1}@tokens; print Dumper (\%tokens); __END__ Prints: $VAR1 = { '4' => 1, '32' => 1, '28' => 1, '72' => 1, '13' => 1, '14' => 1, '15' => 1 };
    Reconsidering your requirements, and I will say that this is bizarre:
    #!/usr/bin/perl -w use strict; use Data::Dumper; my $tokens = "32,15,4,72,13,28,14"; my @tokens = (split/,/,$tokens); my $i=0; my %tokens = map {$_ => $i++}@tokens; print Dumper (\%tokens); __END__ Prints: VAR1 = { '4' => 2, '32' => 0, '28' => 5, '72' => 3, '13' => 4, '14' => 6, '15' => 1 };
    Now again of course since this initialization, why would you need the scalar string at all?
    my @tokens = qw (32 15 4 72 13 28 14); my $i=0; my %tokens = map {$_ => $i++}@tokens;
    yields the same result as above.

    Ok, I am going to go "crazy" here and ask why you want a hash in the first place? I am at a loss the see the usefulness of a hash here. Why do you think that you need it?

      Ok, I am going to go "crazy" here and ask why you want a hash in the first place?

      I just want to iterate the elements of another list of tokens, but in the same order them appear in the main list of tokens. Simplified:

      my $tokens = "32,15,4,72,13,28,14"; my %code = do { my $i = 0; map { $_ => $i++ } split /,/, $tokens }; my $list = "4,13,15"; my $ok; for my $token (sort { $code{$a} <=> $code{$b} } split /,/, $list) { print "$token "; # more code with $token... last if $ok; } __END__ 15 4 13

      Obviously, both $tokens and $list are dynamically loaded from somewhere else...

        This is a straight-forward implementation.
        Post again if I didn't get it right....
        #!/usr/bin/perl -w use strict; my $tokens = "32,15,4,72,13,28,14"; my @tokens = split (/,/, $tokens); my $list = "4,13,15"; my @list = split (/,/,$list); my %list = map {$_ => 1}@list; my @ordered_nums = grep{$list{$_}}@tokens; print "@ordered_nums\n"; __END__ PRINTS: 15 4 13
        Ooops, mess up and double post...sorry.... This is a straight-forward implementation.
        Post again if I didn't get it right....
        #!/usr/bin/perl -w use strict; my $tokens = "32,15,4,72,13,28,14"; my @tokens = split (/,/, $tokens); my $list = "4,13,15"; my @list = split (/,/,$list); my %list = map {$_ => 1}@list; my @ordered_nums = grep{$list{$_}}@tokens; print "@ordered_nums\n"; __END__ PRINTS: 15 4 13
        Update: I would perhaps replace "list" with "list_order". Matter of choice.