8

I have a function which extracts Excel data into an array of hashes like so:


sub set_exceldata {

    my $excel_file_or = '.\Excel\ORDERS.csv';
    if (-e $excel_file_or) {

        open (EXCEL_OR, $excel_file_or) || die("\n can't open $excel_file_or: $!\n");                   
        while () {

            chomp;
            my ( $id, $date, $product, $batchid, $address, $cost ) = split ",";
            my %a = ( id      => $id
                    , date    => $date
                    , product => $product
                    , batchid => $batchid
                    , address => $address
                    , cost    => $cost
                    );
            push ( @array_data_or, \%a );
        }
        close EXCEL_OR;
    }
}

Populating the array of hashes is fine. However, the difficult part is searching for a particular item (hash) in the array. I can't seem to locate items that might have an id or 21, or a batchid of 15, or a cost > $20 etc.

How would I go about implementing such a search facility?

Thanks to all,

2
  • You could just push an anonymous hash ref push(@array,{ id => $id }); Commented Jun 1, 2009 at 13:42
  • While not directly related to your question, you should look at Text::xSV for parsing the .csv file. Just doing a split on "," is not 100% reliable for parsing. There is also a Spreadsheet::ParseExcel module that can parse Excel binaries. Commented Jun 1, 2009 at 15:29

2 Answers 2

22

With the power of grep

my @matching_items = grep {
  $_->{id} == 21
} @array_data_or;

If you know there will be only one item returned you can just do this:

my ($item) = grep {
  $_->{id} == 21
} @array_data_or;

(Untested, and I haven't written one of these in a while, but this should work)

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your insight David. Regarding your solution this returns an array of results whereas because there will really only be one occurence is should return a hash and not an array. Would this solution still be valid when returning a hash instead of an array of hashes?
if you're sure it will return only one value you can use $matching_items[0], or do my $matching_items = (grep { $_->{id} == 21 } @array_data_or)[0];
@DidYouJustDoThat: Assuming that there is only one match is not necessarily safe. Some of your examples (e.g. cost > 20) could return multiple items.
5

If you're sure that the search always returns only one occurence or if you're interested in only the first match then you could use the 'first' subroutine found in List::Util

use List::Util;

my %matching_hash = %{ first { $_->{id} == 21 } @array_data_or };

I enclosed the subroutine call in the %{ } block to ensure that the RHS evaluates to a hash.

4 Comments

The %{} around the call to first() dereferences the hashref from the array, making a (shallow) copy. That could be a problem depending on how the OP uses it. I'd return the hashref instead: $hr = first {$_->{id} == 21} @array_data_or
I too would prefer to return and later use the hashref but according to DidYouJustDoThat's comment to David Doward's reply, the poster is looking for a hash and not a reference.
I think saying "hash" is just sloppy terminology. He wants a single value instead of a list.
Could be, that's why I added the RHS line at the bottom to clarify what I was doing so that the OP could use either the reference value or the entire hash.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.