in reply to Ternary Operator: Condition-Negation
When I first saw this post I was struck by the non-perlishness of the following loop...
# eliminate duplicates in history and keep only the most recent for(my $i=scalar(@history)-1;$i>=0;$i--) { $_ = $history[$i]; !$history{$_} ? $history{$_}=1 : splice(@history,$i--,1); }
...and thought I'd have a crack at "improving it".
What I arrived at after going all round the houses was:
{ my (%history, $histcount); # Should I initialise $histcount = 0 or + is that "Cargo cult"? sub addHist { $history{ $_[0] } = $histcount++; } sub getHist { return = grep{ $history{ $_ } == ($histcount - $_[0]); + } keys %history; } } ... # get $newvalue from somewhere addHist( $newvalue ); ... # deside we want the $n'th previous value my $last_nth = getHist( $n ); ...
If anyone is interested in seeing the route by which I arrived at this, or fancies answering a couple of question arising...
First, understand what it is doing. (Excessive comments are just for my annotation)
for ( my $i=$#history; $i >=0; $i-- ) { # for $i = (last index of @hi +story) to 0 $_ = $history[$i]; # get the value from the posi +tion in @history !$history{$_} # test if that value has been + seen before ? $history{$_} = 1 # if it hasn't record it : splice @history, $i--, 1) # if it has remove it from @h +istory # (reducing $i to compensate +for its remove; }
Simple improvement?
for my $i ($#history .. 0 ) { !exists $history{ history[$i] } # use exists as testing its v +alue against zero will # autovivify (which is why we + need to set its value to 1) ? $history{ $history[$i] }++ # autovivify and increment. ( +By using exists, the # setting to 1 is redundant b +ut... : splice @history, $i--, 1); # the -- in this version woul +d be bad!! # Thou shalt not modify the i +terator in a for loop! }
Try again
for ($#history .. 0) { my $temp = pop @history; if (!exists $history{ $temp }) { $history{ $temp }++; unshift @history; } # else nothing to do we've already removed it. }
Simplify?
for ($#history .. 0) { my $temp = pop @history; next if exists $history{ $temp }; $history{ $temp } ++; unshift @history; }
still bugged by that temp var! So...
for ( push @history, '~~SOME UNLIKELY VALUE~~', my $value= shift @hist +ory; $value ne '~~SOME UNLIKELY VALUE~~'; $value = shift @history ) { push @history, $value, $history{ $value }++ unless exists $history +{ $value } }
Of course, that duplication of the sentinel value is bad so
use constant SENTINEL => '~~SOME UNLIKELY VALUE~~'; for ( push @history, SENTINEL, my $value= shift @history; $value ne SENTINEL; $value = shift @history ) { push @history, $value, $history{ $value }++ unless exists $history +{ $value } }
But all I've really done is to disguise the temp var and I'm back to using the clunky for(;;) loop:(
Then I thought. Ah, the problem is allowing duplicates to exist in the first place! That is also what is forcing the need for a loop and a temporary hash to eliminate them! Why use an array to store them anyway, why not just store the history in the hash?
my %history; .... # get a new value here and only add it if its not there. $history{ $newvalue } = 1 unless exists $history{ $newvalue }; ...
That seemed great until I tried to recover the last value - returns from keys %hash aren't ordered. Drat!
Ah! But that value 1 isn't doing much of anything useful. Ok!So I'll maintain a count.
my (%history, $histcount); .... # get a new value here and only add it if its not there. $history{ $newvalue } = $histcount++ unless exists $history{ $newvalue + };
but I don't need the conditional, cos if it already exists, we want to promote it to the top of the list. Just need...
$history{ $newvalue } = $histcount++; ...
That's simple, promotes and eliminates dups! Then to retrieve the nth previous value
There's no direct way to go from a hash value back to its key so we need a loop.
for my $i (keys %history) { last if $history{ $i } == ($histcount - $n); } my $last_nth = $history{ $i };
Doesn't work cos $i is local (Sorry! Lexically scoped) to the for(){}. Its a shame that none of these work either
# Can't use multiple modifiers. Basic Plus2 was nice! my last_nth = do { $_ for (keys %history) } while ( $history{ $_ } == +($histcount - $n) ); my $last_nth = $_ if $history{ $_ } == n for (keys %history); # Again +$_ is local
Could do
my $last_nth; for my $i (keys %history) { last if ($last_nth = $history{ $i }) == ($histcount - $n); }
Should work ok, but seems clunky. I've traded an array, a temporary hash and a loop with a temp var when adding for a hash and a loop with a temp var when retrieving. Maybe map or grep would help?
my last_nth = grep{ $history{ $_ } == ($histcount - $n } keys %history +;
Tells me its there, but not what or where
my ($last_nth) = grep{ $history{ $_ } == ($histcount - $n); } keys %hi +story;
Finally. But is this "good code"?
Eliminating all the false starts and clunky code this translates into
my (%history, $histcount); # To add $newvalue $history{ $newvalue } = $histcount++ unless exists $history{ $newvalue + }; # To retrieve the last-but-$n'th my ($last_nth) = grep{ $history{ $_ } == ($histcount - $n); } keys %hi +story;
Not too bad, but it would probably be better if the above where subs addHist() and getHist() but they would either have to access %history and $histcount from external to themselves
my (%history, $histcount); # Should I initialise $histcount = 0 or + is that "Cargo cult"? sub addHist { $history{ $_[0] } = $histcount++; } sub getHist { return = grep{ $history{ $_ } == ($histcount - $_[0]); } + keys %history; } # then ... # get $newvalue from somewhere addHist( $newvalue ); ... # deside we want the $n'th previous value my $last_nth = getHist( $n ); ...
...or these would have to be passed on each call
my (%history, $histcount); # Should I initialise $histcount = 0 or + is that "Cargo cult"? sub addHist { my ($hashref, $histcount, $new ) = @_; # Naming our parameters ma +kes things clearer # validate $hashref->{ $new } = $histcount++; } sub getHist { my ($hashref, $histcount, $new ) = @_; #validate return = grep{ $hashref->{ $_ } == ($histcount - $n); } keys %hist +ory; }
then
... # get $newvalue from somewhere addHist( \%history, $histcount, $newvalue ); ... # deside we want the $n'th previous value my $last_nth = getHist( \%history, $histcount, $n ); ...
Better, but in terms of a single program using a single history list, this seems like a step backwards. Maybe this is a good time to think closures?
{ my (%history, $histcount); # Should I initialise $histcount = +0 or is that "Cargo cult"? sub addHist { $history{ $_[0] } = $histcount++; } sub getHist { return = grep{ $history{ $_ } == ($histcount - $_[0] +); } keys %history; } }
then as before, but nobody can mess with our hash or its pointer accidently!
... # get $newvalue from somewhere addHist( $newvalue ); ... # deside we want the $n'th previous value my $last_nth = getHist( $n ); ...
Now the questions arising.
Is one of these solutions better than another?
If the last one is good, is it worth putting this into a seperate package using OO the thing?
Is there a better way?
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re(2): Ternary Operator: Condition-Negation
by Arien (Pilgrim) on Aug 28, 2002 at 23:23 UTC |