http://qs1969.pair.com?node_id=256884

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

I have various items stored in a hash with the price as the key:

my %ITEMS = ( 29.95 => 'item 1', 38.55 => 'item 2', 45.20 => 'item 3', # Oh oh! 58.29 => 'item 4', );

If you must know, the "items" are really subscription orders for a magazine, so there is no chance that two "items" would have the same price.

The problem comes when you want to access the hash.

print $_, "\n" foreach (keys %ITEMS); __OUTPUT__ 58.29 38.55 45.2 29.95

When accessed in numeric context, the '45.20' key drops the trailing zero, but keeps it in string context. The solution I came up with was to have two entries pointing to the same string, one in numeric context and the other in string:

my %ITEMS = ( 29.95 => 'item 1', 38.55 => 'item 2', "45.20" => 'item 3', # String 45.2 => 'item 3', # Number 58.29 => 'item 4', ); print $_, "\n" foreach (keys %ITEMS); __OUTPUT__ 58.29 38.55 45.2 45.20 29.95

This solution forces you to update two hardcoded hash keys with identicial data.

Does anybody see a cleaner solution?

----
I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
-- Schemer

Note: All code is untested, unless otherwise stated

Replies are listed 'Best First'.
Re: Floats with trailing zeros as a hash key
by robartes (Priest) on May 09, 2003 at 15:05 UTC
    Hmm,

    storing these with the prices as keys, as opposed to the more natural way of doing it the other way around, seems a bit funny to me. Do you perhaps want fast lookups of the prices?

    That said, there is no real need to store the two keys. Perl is smart enough to use the key in correct context. If you want to make sure the number gets printed with the trailing zero, use printf:

    $ perl -e 'printf ("%0.2f", 45.2)' 45.20

    This way, there's no need to store two versions of the key. Let Perl convert to the correct context if needed, and use printf if you want to pretty print.

    CU
    Robartes-

      Do you perhaps want fast lookups of the prices?

      Yes, exactly. This actually a rewrite of another script. The orginal programmer used a long series of if/elsif statements. This is how I cleaned it up.

      This way, there's no need to store two versions of the key.

      The real problem comes like this:

      use CGI qw(:standard); my $input_item = param('item') || 0; my $subtotal; my $item_name; if(exists $ITEMS{$input_item}) { $subtotal = $input_item; $item_name = $ITEMS{$input_item}; }

      ----
      I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
      -- Schemer

      Note: All code is untested, unless otherwise stated

        Hmmm, I think robartes is on to something, the main problem is that you are using a price as a key. It only happens to work because all the prices are different, which is hardly a logical necessity. If at all possible, change the web page so that you use some artificial key, from which you can look up the name and the price.
        You could try canonicising (sp? Heck, is that even a word?) the prices before storing them, e.g. like so:
        my $key = sprintf("%0.4f", $price); $items{$key}=$item;
        You basically force the numbers in a certain format (rounded to 4 decimals and zero padded in this case). That way, you are guaranteed that 45.20 and 45.2 will end up as 45.2000 and thus in the same hash entry.

        Now remind me again why you want to put the prices in the keys? What happens if you have two items with equal prices?

        CU
        Robartes-

Re: Floats with trailing zeros as a hash key
by kilinrax (Deacon) on May 09, 2003 at 14:57 UTC
    Use printf rather than print to output the hash keys.
    printf "%.2f\n", $_ for keys %ITEMS; __OUTPUT__ 58.29 38.55 45.20 29.95

      The prints I had in the example were only that: an example. The real script is accessing the hash based on some user input (with validation, of course), not printing out all the keys just for fun.

      ----
      I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
      -- Schemer

      Note: All code is untested, unless otherwise stated

        Then use sprintf? The internal representation of numbers shouldn't really matter - you can always convert them to the right format if needs be.

        If it is important, then initialise them all as strings for consistency, at least.

Re: Floats with trailing zeros as a hash key
by jkahn (Friar) on May 09, 2003 at 22:59 UTC

    I am going to guess that my suggestion (below) is too big a rewrite for the code you're working on, but let me make it anyway:

    Store prices as integer cents.

    There are several reasons that C programs usually store prices as integer amounts of pennies, and then convert to dollars at the last minute:

    • Fractions of a penny don't make sense (cents?)
    • no roundoff issues

    This might be one of those cases for you, too. 4520 pennies will never get rounded to 452 pennies, but 45.20 dollars (or euros) will get rounded to 45.2 by internal number-to-string conversions.

    Just my $0.02 er, 2¢

      Wow, that's a good idea. Wish I had heard it a week ago (:

      ----
      I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
      -- Schemer

      Note: All code is untested, unless otherwise stated

Re: Floats with trailing zeros as a hash key
by artist (Parson) on May 09, 2003 at 14:57 UTC
    printf ("%.2f\n","$_") for keys %ITEMS;

    artist
    =================================
    Beautify your existence.

Re: Floats with trailing zeros as a hash key
by Abigail-II (Bishop) on May 09, 2003 at 16:25 UTC
    I don't see what the problem is. You have exactly the same issue if you do print 45.20. That trailing 0 'disappears' as well. If you do not want this numeric behaviour, always treat it as a string, and quote it. Or always use (s)printf when printing your numbers.

    Perl will change numbers into strings and strings into numbers if necessary, that is, when values are used. But you can't expect Perl to treat your numeric literals as string literals.

    Abigail

Re: Floats with trailing zeros as a hash key
by cbro (Pilgrim) on May 09, 2003 at 16:51 UTC
    If you don't mind predefining your keys:
    my $key1 = sprintf "%.2f", 45.20; my %ITEMS = ( $key1 => 'item 1');
    This will work. I tested it as follows:
    $key1 = sprintf "%.2f", 52.30; my %ITEMS = ( $key1 => 'item 1', 45.25 => 'item 2' ); my $input = <STDIN>; chomp ($input); if (exists $ITEMS{$input}) { print $ITEMS{$input} . "\n"; }
    It printed "item 1" when I entered 52.30.
Re: Floats with trailing zeros as a hash key
by Anonymous Monk on May 09, 2003 at 14:52 UTC
    Yes. Use a database.

      This is a tiny little script sitting on a web server taking orders for a grand total of 5 items. A database would be massive overkill.

      And in any case, I'm more intrested in a theoretical solution to mixing string and numeric contexts in hash keys.

      ----
      I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
      -- Schemer

      Note: All code is untested, unless otherwise stated