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,

Axeman
  • 29,660
  • 2
  • 47
  • 102
  • You could just push an anonymous hash ref push(@array,{ id => $id }); – Brad Gilbert Jun 01 '09 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. – jiggy Jun 01 '09 at 15:29

2 Answers2

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)

Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • 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? –  Jun 01 '09 at 10:37
  • 1
    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]; – Nathan Fellman Jun 01 '09 at 10:39
  • @DidYouJustDoThat: Assuming that there is only one match is not necessarily safe. Some of your examples (e.g. cost > 20) could return multiple items. – Michael Carman Jun 01 '09 at 13:09
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.

aks
  • 24,359
  • 3
  • 32
  • 35
  • 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` – Michael Carman Jun 01 '09 at 13:07
  • 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. – aks Jun 01 '09 at 13:17
  • I think saying "hash" is just sloppy terminology. He wants a single value instead of a list. – Michael Carman Jun 01 '09 at 13:50
  • 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. – aks Jun 01 '09 at 13:56