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

Please help oh wise monks. I am trying to sort fields in a hash, but I am not sure what type of data is going to be in the field. I tried to compare for both alpha and numeric, but it does not seem to be working. Any help, as always, is appreciated.
foreach $record ( sort { $a->{$sort_by} cmp $b->{$sort_by} || $a- +>{$sort_by} <=> $b->{$sort_by} } @claim_records ) { print STDOUT $record->{'claim_number'}, "\n"; }

AAAAAARRRRRRGGGGGGGHHHHHHH!!!!!!!
Prince99

Too Much is never enough...

Replies are listed 'Best First'.
Re: Sorting a hash
by jeroenes (Priest) on Apr 11, 2001 at 17:54 UTC
    The numeric compare only is executed when the cmp fails because the two fiels are identical.

    The solution is: If you have numerics, zero-pad them using sprintf "%05i" for example. The cmp only missorts on numbers of different digit lengths.

    Jeroen
    "We are not alone"(FZ)

Re: Sorting a hash
by davorg (Chancellor) on Apr 11, 2001 at 17:59 UTC

    Maybe you could try putting the numeric comparison first, as that's most likely to return 0.

    --
    <http://www.dave.org.uk>

    "Perl makes the fun jobs fun
    and the boring jobs bearable" - me

(tye)Re: Sorting a hash
by tye (Sage) on Apr 11, 2001 at 21:01 UTC

    For some fairly simple answers to this problem, see my responses to How do I do a natural sort on an array?. I plan to update these and add some more answers but I haven't gotten back to that yet, sorry.

            - tye (but my friends call me "Tye")
Re: Sorting a hash
by stephen (Priest) on Apr 11, 2001 at 20:36 UTC
    I've made some changes to your code. Once again, I shortened some fields down for simplicity. Bear in mind that the method I chose here isn't necessarily the most efficient or elegant, but I didn't want to be TOO confusing. :)
    use strict; use vars qw(@Claim_Fields %Claim_Table); @Claim_Fields = ( [ 'claim_batch_number', 'N', 'A8' ], [ 'claim_number', 'N', 'A6' ], [ 'claim_type', 'L', 'A2'], [ 'payment_direct', 'N', 'A2'], ); %Claim_Table = get_table();
    A few things going on here. First, I added 'use strict', which will throw a bunch of errors around if anyone tries to use variables improperly. Next, I turned CLAIM_KEYS into a list of lists called @Claim_Fields. (I could have used a constant here, but one strange thing at a time.) Later on, I'm going to need to look up what type of sortings is needed on a given field, so I call get_table() to get a hash table. We'll define get_table() further down.
    Main: { my $data_file = $ARGV[0]; my $sort_by = $ARGV[1]; open(CLAIMS, $data_file) or die "CANNOT OPEN FILE: $!"; my @claim_records = (); while (<CLAIMS>) { push(@claim_records, read_record($_)); } close(CLAIMS);
    Made some changes here. First, you had $data_file (formerly $DAT) and $sort_by hard-coded in your script. If you use @ARGV, you can specify them on the command line when you run the script. I added a '$!' to your die message on opening the data file, so that if it fails, it will print a more useful error message.

    Also, I moved your open and close out of subroutines, since it's good practice to close your handles in the same routine in which you opened them.

    &Sort_File($sort_by, @claim_records); }

    One of the things that was failing in your original script was that @claim_records wasn't getting passed to @sort_file. I turned $sort_by and @claim_records into arguments. Look at perlman:perlsub.

    sub Sort_File { my ($sort_by, @claim_records) = @_; my $sort_sub; # If the sorting type is numeric if ( $Claim_Table{$sort_by} eq 'N' ) { # Create sub for numeric sort $sort_sub = sub { $a->{$sort_by} <=> $b->{$sort_by} }; } # End if numeric # Otherwise it's lexical else { # Create sub for lexical sort $sort_sub = sub { $a->{$sort_by} cmp $b->{$sort_by} }; } foreach my $record ( sort $sort_sub @claim_records ) { print STDOUT $record->{'claim_number'}, "\n"; } }
    The first line reads $sort_by and @claim_records in as arguments. Then, we look at the %Claim_Table which we initialized at the top of the program and get the sort type. Next, we define an anonymous subroutine (see perlman:perlsub again) based on the 'L' or 'N' value in the table up top. If it's 'L', our subroutine will sort lexically, 'N' numerically. Well, it'll actually sort lexically if the value is anything other than 'N', but close enough.

    Finally, we close up with the original read_record() method, modified slightly to call the new get_fields() and get_template() subroutines. See perlman:perlref for more about handling lists of lists.

    sub read_record { my ($line) = @_; my %claim_record = (); @claim_record{ get_fields() } = unpack( get_template(), $line); return \%claim_record; } ## ## Returns all of the field names in @Claim_Fields ## sub get_fields { my @fields = (); foreach my $claim_field ( @Claim_Fields ) { push(@fields, $claim_field->[0]); } return @fields; } ## ## Builds the template from the template fields in @Claim_Fields ## sub get_template { my $template = ''; foreach my $claim_field ( @Claim_Fields ) { $template .= $claim_field->[2]; } return $template; } ## ## Builds a hash table for lookup from @Claim_Fields ## sub get_table { my %table = (); foreach my $claim_field ( @Claim_Fields ) { $table{$claim_field->[0]} = $claim_field->[1]; } return %table; }
    Anyway, that should get you out of the mess I put you into with my last post. :) Best of luck.

    stephen

Re: Sorting a hash
by suaveant (Parson) on Apr 11, 2001 at 18:25 UTC
    if you don't care about efficiency too much then you can check each element to see if it is a number and handle it accordingly.
    Give us some examples of your data and how it would sort appropriately...
                    - Ant
      Here is what I am doing. The file format is one long line of data that is supposed to be broken into records of 434 characters. I need to read all the records into a hash and then be able to sort the hash(so to speak) based on one of the fields. Once the field is sorted I need to be able to print all the fields that are associated with it. Any suggestions would be appreciated. Sorry for it being such a long message, but I figure the more I put the better you all are able to assist me.
      $DAT = "/where/my/file/is"; use constant CLAIM_TEMPLATE => 'A8A6A2A2A6A4A30A4A4A8A8A2A11A25A1A8A3A +8A8A8A8A6A8A8A8A1A10A2A20A2A14A6A4A8A16A20A1A2A1A6A1A14A2A2A8A8A2A8A2 +A1A16A8A1A1A1A10A5A20A15A1A1'; use constant CLAIM_KEYS => qw( claim_batch_number claim_number claim_type payment_direct pharmacy_number chain_number pharmacy_name reject_code_1 reject_code_2 rx_number rx_date drug_type_code national_drug_code product_name newrefill_indicator metric_quantity days_supply ingredient_cost_billed ingredient_cost_paid dispensing_fee copay tax total_amount_paid ucr_amount member_birthdate member_sex cardholder_number member_number alternate_card_number patient_relationship physician_number diagnosis_code pdm_system_number pdm_sponsor_number pdm_group_number group_number generic_code mac_number daw_indicator therapeutic_class_code rx_otc_code gpi exception_code override_code period_ending paid_date compound_code batch_date claim_counter mail_order benefit_code awp claim_indicator drug_preference_indicator pricing_indicator drug_manufacturer controlled_substance patient_last_name patient_first_name patient_middle_initial third_party_code ); Main: { &Open_File; my @claim_records = (); while (<CLAIMS>) { push(@claim_records, read_record($_)); } my $sort_by = 'claim_number'; &Sort_File(); &Close_File; } sub Sort_File { my $sort_by = 'claim_number'; foreach $record ( sort { $a->{$sort_by} <=> $b->{$sort_by} || $a{$ +sort_by} cmp $b{$sort_by} } @claim_records ) { print STDOUT $record->{'claim_number'}, "\n"; } } sub read_record { my ($line) = @_; my %claim_record = (); @claim_record{ CLAIM_KEYS } = unpack(CLAIM_TEMPLATE, $line); return \%claim_record; } sub Open_File { open(CLAIMS, $DAT) || die "CANNOT OPEN FILE"; } sub Close_File { close(CLAIMS); }
      Update: The claim_number example data includes:
      003549
      000081
      004951
      001249
      000059
      Hope this help.
      Prince99

      Too Much is never enough...
        ahhh. I see, you are trying to sort by a field in claim records... hmmm... can you keep track of which records are numeric? then you could do...
        %numeric = (mac_number => 1, generic_code => 1); foreach $record ( sort { if($numeric($sort_by)) { return $a->{$sort_by +} <=> $b->{$sort_by} } else { return $a{$sort_by} cmp $b{$sort_by} } +} @claim_records ) { print STDOUT $record->{'claim_number'}, "\n"; }
        you may want to move your sort code into a subroutine and just call sort with the sub name instead of direct code, make your code easier to read since the code in the sort is getting long
                        - Ant