in reply to Re^5: Convert a string into a hash
in thread Convert a string into a hash

There is a list of available tokens, where one token is "better" than other if it appears previously on that ranking list. From the tokens list given in the first post of this thread, 4 is "better" than 13, and 15 is the "best" of that three tokens.

The list of tokens is defined once at startup, based on config and user params.

There is another list from where I must select the "best" token. This list changes on each iteration based on input.

... and the requirement changed again!!! on each list from the input can appear other tokens than the valid ones(from tokens list), so I need to discard them. If none of the input's tokens is on the ranking, I must take a default one (the last from the ranking, better than nothing).

Constructing the hash once for the ranking:

#!perl -w use strict; my $tokens = "32,15,4,72,13,28,14"; my %ranking = do { my $i; map { $_ => $i++ } split /,/, $tokens }; my ($default) = ($tokens =~ m/,(\d+)$/); # last token while (my $list = <DATA>) { chomp $list; my $best = $default; for my $b (split /,/, $list) { next unless defined $ranking{$b}; $best = $b if $ranking{$b} < $ranking{$best}; } print "$best\n"; } __DATA__ 4,13,15 4,50,15,13 50,60,70

Using your approach, where the hash must be constructed for each input line:

#!perl -w use strict; my $tokens = "32,15,4,72,13,28,14"; my @ranking = split (/,/, $tokens); my $default = $ranking[$#ranking]; # last token while (my $list = <DATA>) { chomp $list; my %list = map {$_ => 1} split /,/, $list; my $best = ( grep {$list{$_}} @ranking )[0] || $default; print "$best\n"; } __DATA__ 4,13,15 4,50,15,13 50,60,70

Both examples should print 15, 15 and 14.

So, which approach is better? Speed is not an issue (actually, input is retrieved from an external website, one html page, one data row of tokens). Size of tokens lists (initial and input) is not so long at the time (less than 15 values).

A better (obfuscated) way to write the for of the first program? The next unless defined condition could be removed if the $i variable from the ranking population is initialized with a high value and then decrease the counter on each iteration of the map, but a message will be displayed because of use warnings.

Replies are listed 'Best First'.
Re^7: Convert a string into a hash
by Marshall (Canon) on Aug 19, 2009 at 00:49 UTC
    There is nothing wrong with either approach. A few comments:
    - I would forget about do{}, seldom is that needed in code like this. If there is some need to restrict the scope of my $i, that points to some other problem in the overall code.
    - I would use slice (@ranking)[-1] instead of using something like $ranking[$#ranking].
    - I started $i at 1 to avoid any ambiguity about undef vs zero value.

    Have fun! you are on your way with several ideas that produce correct results. All will run WAY fast enough for your application.

    #!/usr/bin/perl -w use strict; my $ranking_string = "32,15,4,72,13,28,14"; my @ranking = split (/,/, $ranking_string); my $i =1; my %ranking_hash = map {$_ => $i++} @ranking; my $default_best_num = (@ranking)[-1]; while (my $list = <DATA>) { chomp $list; my $best_num = $default_best_num; foreach my $num (split /,/, $list) { next unless $ranking_hash{$num}; $best_num = $num if $ranking_hash{$num} < $ranking_hash{$best_num +}; } print "$best_num\n"; } #prints: #15 #15 #14 __DATA__ 4,13,15 4,50,15,13 50,60,70
    The requirements for this app seem to be a moving target. I thought I'd demo a completely different approach for you. I'm not recommending this for your current requirements, but sounds like something may pop up soon that might need this. This shows "hey, 15 is the "best" one, but what is second best one", etc. This uses the very powerful sort functions of Perl.
    #!/usr/bin/perl -w use strict; my $ranking_string = "32,15,4,72,13,28,14"; my @ranking = split (/,/, $ranking_string); my $i =1; my %ranking_hash = map {$_ => $i++} @ranking; my $default_best_num = (@ranking)[-1]; while (my $line = <DATA>) { chomp $line; my @ranking = sort by_min_ranking grep {$ranking_hash{$_}} (split /,/, $line); if (@ranking){print "@ranking - first one is \"best\"\n"} else {print "$default_best_num - default used\n"} } sub by_min_ranking { $ranking_hash{$a} <=> $ranking_hash{$b} } #prints: #15 4 13 - first one is "best" #15 4 13 - first one is "best" #14 - default used