How to access a hash value by index stored in variable - perl

I am working on some code to extract data stored in JSON format from web page. JSON data extracted and decode properly into hash.
The structure of JSON data is very complex, I wrote some assisting code/function which 'traverse' through the hash and helps finds 'index' (location) of a value of interest in the hash.
The 'find' function returns 'index' (location) of the data in the hash which stored in a variable.
I attempt to use this variable (stored 'index') in other operations but so far without any success.
Please see included simple demo snippet code for explanation of the problem.
Thank you,
Polar Bear
use strict;
use warnings;
use JSON qw(decode_json);
my $index;
my $slice;
my $data = decode_json( join '', <DATA> );
printf "TITLE: %-15s TIME: %5s TIMES: %5s FAVORITE: %s\n",
$data->{playList}[1]{title},
$data->{playList}[1]{time},
$data->{playList}[1]{played},
$data->{playList}[1]{favorite} ? "yes" : "no";
$index = '{playList}[1]';
$slice = $data{$index}; # does not pass 'use strict' compilation error
$slice = $data->{$index}; # empty slice
$slice = $data->$index; # Can't call method "{playList}[1]" on unblessed reference at
printf "TITLE: %-15s TIME: %5s TIMES: %5s FAVORITE: %s\n",
$slice->{title},
$slice->{time},
$slice->{played},
$slice->{favorite} ? "yes" : "no";
__DATA__
{
"playList": [
{
"title": "Song name 1",
"time": "3:25",
"played": "240",
"favorite": "1"
},
{
"title": "Song name 2",
"time": "4:12",
"played": "30",
"favorite": "0"
},
{
"title": "Song name 3",
"time": "2:56",
"played": "85",
"favorite": "0"
}
]
}
I expect to access data by utilizing 'index' stored in a variable but I could not find a way to achieve this result. See comments in the code for details.
NOTE: In real life the index looks as following
my $index = "{contents}{twoColumnBrowseResultsRenderer}{tabs}[0]{tabRenderer}{content}{sectionListRenderer}{contents}[0]{itemSectionRenderer}{contents}[0]{playlistVideoListRenderer}{contents}[0]{playlistVideoRenderer}{title}{accessibility}{accessibilityData}{label}";
SOLUTION:
I would like to extend my 'thank you' to Håkon Hægland and lordadmira for offered solution
use Data::Diver qw/Dive/; # or Data::DPath, etc
# Capture web page, extract data JSON, convert to hash, assign hash ref to $data
my $data = ...;
# Find index/location in the hash
#my $index = find($data, $value);
my $index = "{contents}{twoColumnBrowseResultsRenderer}{tabs}[0]{tabRenderer}{content}{sectionListRenderer}{contents}[0]{itemSectionRenderer}{contents}[0]{playlistVideoListRenderer}{contents}[0]{playlistVideoRenderer}{title}{accessibility}{accessibilityData}{label}";
$index =~ s/[{\[]//g; # throw away opening brackets
my #index = split /[}\]]/, $index; # split into array on closing brackets
pop #index for 1..8 # 8 levels to back up to
my $slice = Dive( $data, #index ); # extract hash slice of interest
# extract playlist
my $playlist = $slice->{playlistVideoListRenderer}{contents};
# go through playlist and extract information of our interest
foreach ( #$playlist ) {
my $video = $_->{playlistVideoRenderer};
printf "%s %8s %s\n",
$video->{videoId},
$video->{lengthText}{simpleText},
$video->{title}{simpleText};
}
Both of them referred me to use Data::Dive with help of this module I can do back up few levels from the depth of the hash and extract slice of interest.
It was learned that by utilizing this module the index in form of array is easier to work with. Due this factor I will alter my find function to return an index array.

You can use Data::Diver :
use Data::Diver qw( DiveVal );
use JSON qw(decode_json);
my $data = decode_json( join '', <DATA> );
my $slice = DiveVal( $data, qw( playList 1 ) );
printf "TITLE: %-15s TIME: %5s TIMES: %5s FAVORITE: %s\n",
$slice->{title},
$slice->{time},
$slice->{played},
$slice->{favorite} ? "yes" : "no";
Output:
TITLE: Song name 2 TIME: 4:12 TIMES: 30 FAVORITE: no

I would like to extend my 'thank you' to Håkon Hægland and lordadmira for offered solution
use Data::Diver qw/Dive/; # or Data::DPath, etc
my $data = ...;
my $index = "{contents}{twoColumnBrowseResultsRenderer}{tabs}[0]{tabRenderer}{content}{sectionListRenderer}{contents}[0]{itemSectionRenderer}{contents}[0]{playlistVideoListRenderer}{contents}[0]{playlistVideoRenderer}{title}{accessibility}{accessibilityData}{label}";
$index =~ s/[{\[]//g;
my #index = split /[}\]]/, $index;
pop #index for 1..8 # 8 levels to back up to
my $slice = Dive( $data, #index );
my $playlist = $slice->{playlistVideoListRenderer}{contents};
foreach ( #$playlist ) {
my $video = $_->{playlistVideoRenderer};
printf "%s %8s %s\n",
$video->{videoId},
$video->{lengthText}{simpleText},
$video->{title}{simpleText};
}
Both of them referred me to use Data::Dive with help of this module I can do back up few levels from the depth of the hash and extract slice of interest.
It was learned that by utilizing this module the index in form of array is easier to work with. Due this factor I will alter my find function to return an index array.

In the comments, you said you had a function that finds the an element in your JSON data structure and returns a "path" to that element, and that your question is about finding a higher-level container of that element.
If this was XML, I'd use an XPath to do both the search and finding the right container. But worry not, someone has devlopped an XPath-like language for JSON, and someone has provide this functionality via Perl module JSON::Path.

The problem is that your code is returning all of the keys and elements to find the given element but not the element value itself. The simplest answer is to return a reference to that located value.
... bunch of lookdown code
return \ $this_level->{the_key_I_want}
That way the caller can have direct read/write access to the leaf value.
If you want to use the list of keys and elements directly to access a deep value, you will have to do a string eval. This is NOT recommended unless you 1000% percent trust the data because some joker can name a hash key "; system qw{rm -rf /}".
$index = "{contents}{twoColumnBrowseResultsRenderer}{tabs}[0]{tabRenderer}{content}{sectionListRenderer}{contents}[0]{itemSectionRenderer}{contents}[0]{playlistVideoListRenderer}{contents}[0]{playlistVideoRenderer}{title}{accessibility}{accessibilityData}{label}"
$value = eval "\$HASH$index";
The right way to use a list like that is another lookdown function that takes the list of keys etc and accesses them one by one.
HTH
PS: I slightly misunderstood your original question. Just write in your code:
$slice = $data->{playList}[1];
2nd Edit to actually use $index:
use Data::Diver qw/Dive/; # or Data::DPath, etc
my $data = ...;
my $index = "{contents}{twoColumnBrowseResultsRenderer}{tabs}[0]{tabRenderer}{content}{sectionListRenderer}{contents}[0]{itemSectionRenderer}{contents}[0]{playlistVideoListRenderer}{contents}[0]{playlistVideoRenderer}{title}{accessibility}{accessibilityData}{label}";
my #index = split /[{}[\]]+/, $index;
shift #index;
pop #index for 1..2 # however many levels to back up to
my $slice = Dive( $data, #index );

Related

Perl find out if X is an element in an array

I don't know why small things are too not working for me in Perl. I am sorry for that.
I have been trying it around 2 hrs but i couldn't get the results.
my $technologies = 'json.jquery..,php.linux.';
my #techarray = split(',',$technologies);
#my #techarray = [
# 'json.jquery..',
# 'php.linux.'
# ];
my $search_id = 'json.jquery..';
check_val(#techarray, $search_id);
And i am doing a "if" to search the above item in array. but it is not working for me.
sub check_val{
my #techarray = shift;
my $search_id = shift;
if (grep {$_ eq $search_id} #techarray) {
print "It is there \n";
}else{
print "It is not there \n";
}
}
Output: It always going to else condition and returns "It is not there!" :(
Any idea. Am i done with any stupid mistakes?
You are using an anonymous array [ ... ] there, which as a scalar (reference) is then assigned to #techarray, as its only element. It is like #arr = 'a';. An array is defined by ( ... ).
A remedy is to either define an array, my #techarray = ( ... ), or to properly define an arrayref and then dereference when you search
my $rtecharray = [ .... ];
if (grep {$_ eq $search_id} #$rtecharray) {
# ....
}
For all kinds of list manipulations have a look at List::Util and List::MoreUtils.
Updated to changes in the question, as the sub was added
This has something else, which is more instructive.
As you pass an array to a function it is passed as a flat list of its elements. Then in the function the first shift picks up the first element,
and then the second shift picks up the second one.
Then the search is over the array with only 'json.jquery..' element, for 'php.linux.' string.
Instead, you can pass a reference,
check_val(\#techarray, $search_id);
and use it as such in the function.
Note that if you pass the array and get arguments in the function as
my (#array, $search_id) = #_; # WRONG
you are in fact getting all of #_ into #array.
See, for example, this post (passing to function) and this post (returning from function).
In general I'd recommend passing lists by reference.

Perl: How to append text to a scalar

I have a subroutine in Perl that will read a hash and print out all of the key value pairs within the hash. However, instead of going through the foreach loop and printing each time it has a key, I need the result to be added to one scalar, and then return the scalar with the combined results at the end.
In Java I recall you could easily add additional text to a variable, but I'm not sure how to do this in Perl.
Any thoughts? I'll add my print code below, but I basically want to take that and add it to a scalar and return the combined scalar at the end (let's say $output)
sub printSongs
{
print "Song Database\n\n";
foreach $key (keys %songList)
{
print "Song Title: $key ---- Duration: $songList{$key}\n";
}
}
PS: I tried to search for this answer as it should be relatively simple, but couldn't find anything. Not sure if append is the best word.
The concatenation operator in Perl is .. You can also combine it with an assignment as .=.
sub printSongs
{
print "Song Database\n\n";
foreach $key (keys %songList)
{
$something_combined = $something_combined .
"Song Title: $key ---- Duration: $songList{$key}\n";
}
print $something_combined;
}
You can easily append anything to a variable with a full stop character.
For example: $something = "Something" . $somevar . "Something else" . "etc";
In Java you normally use + to join strings. In Perl you can use .

How to Hash in Perl

I am finding uniques URL in a log file along with the response stamp which can be available using $line[7]. I am using Hash to get the unique URLs.
How can I get the count of Unique URL?
How can I get the average of response time along with the count of Unique URL?
With below code I am getting
url1
url2
url3
but I want it along with the average response time and count of each URL
URL Av.RT Count
url1 10.5 125
url2 9.3 356
url3 7.8 98
Code:
#!/usr/bin/perl
open(IN, "web1.txt") or die "can not open file";
# Hash to store final list of unique IPs
my %uniqueURLs = ();
my $z;
# Read log file line by line
while (<IN>) {
#line = split(" ",$_);
$uniqueURLs{$line[9]}=1;
}
# Go through the hash table and print the keys
# which are the unique IPs
for $url (keys %uniqueURLs) {
print $url . "\n";
}
store a listref in your hashing directory:
$uniqueURLs{$line[9]} = [ <avg response time>, <count> ];
adjust the elements accordingly, eg. the count:
if (defined($uniqueURLs{$line[9]})) {
# url known, increment count,
# update average response time with data from current log entry
$uniqueURLs{$line[9]}->[0] =
(($uniqueURLs{$line[9]}->[0] * $uniqueURLs{$line[9]}->[1]) + ($line[7] + 0.0))
/ ($uniqueURLs{$line[9]}->[1] + 1)
;
$uniqueURLs{$line[9]}->[1] += 1;
}
else {
# url not yet known,
# init count with 1 and average response time with actual response time from log entry
$uniqueURLs{$line[9]} = [ $line[7] + 0.0, 1 ];
}
to print results:
# Go through the hash table and print the keys
# which are the unique IPs
for $url (keys %uniqueURLs) {
printf ( "%s %f %d\n", $url, $uniqueURLs{$url}->[0], $uniqueURLs{$url}->[1]);
}
adding 0.0 will guarantee type coercion from string to float as a safeguard.
Read up on References. Also, read up on modern Perl practices which will help improve your programming skills.
Instead of just using the keys of your hash of unique URLs, you could store information in those hashes. Let's start with just a count of the unique URLs:
#!/usr/bin/env perl
use strict;
use warnings;
use autodie;
use feature qw(say);
use constant {
WEB_FILE => "web1.txt",
};
open my $web_fh, "<", WEBFILE; #Autodie will catch this for you
my %unique_urls;
while ( my $line = <$web_fh> ) {
my $url = (split /\s+/, $line)[9];
if ( not exists $unique_urls{$url} ) { #Not really needed
$unique_urls{$url} = 0;
}
$unique_urls{$url} += 1;
}
close $web_fh;
Now, each key in your %unique_urls hash will contain the number of unique URLs you have.
This, by the way, is your code written in a bit more modern style. The use strict; and use warnings; pragmas will catch about 90% of the standard programming errors. The use autodie; will catch exceptions to things that you forget to check. In this case, the program will automatically die if the file doesn't exist.
The three parameter version of the open command is preferred, and so is using scalar variables for file handles. Using scalar variables for the file handle makes them easier to pass in subroutines, and the file will automatically close if the file handle falls out of scope.
However, we want to store in two items per hash. We want to store the unique count, and we want to store something that will help us find the average response time. This is where references come in.
In Perl, variables deal with single data items. A scalar variable (like $foo) deals with an individual data item. Arrays and Hashes (like #foo and %foo) deal with lists of individual data items. References help you get around this limitation.
Let's look at an array of people:
$person[0] = "Bob";
$person[1] = "Ted";
$person[2] = "Carol";
$person[3] = "Alice";
However, people are more than just first names. They have last names, phone numbers, addresses, etc. Let's take a look at a hash for Bob:
my %bob_hash;
$bob_hash{FIRST_NAME} = "Bob";
$bob_hash{LAST_NAME} = "Jones";
$bob_hash{PHONE} = "555-1234";
We can take a reference to this hash by putting a backslash in front of it. A reference is merely the memory address where this hash is stored:
$bob_reference = \%bob_hash;
print "$bob_reference\n": # Prints out something like HASH(0x7fbf79004140)
However, that memory address is a single item, and could be stored in our array of people!
$person[0] = $bob_reference;
If we want to get to the items in our reference, we dereference it by putting the right data type symbol in front. Since this is a hash, we will use %:
$bob_hash = %{ $person[0] };
Perl provides an easy way to dereference hashes with the -> syntax:
$person[0]->{FIRST_NAME} = "Bob";
$person[0]->{LAST_NAME} = "Jones";
$person[0]->{PHONE} = "555-1212";
We'll use the same technique in %unique_urls to store the number of times, and the total amount of response time. (Average will be total time / number of times).
#!/usr/bin/env perl
use strict;
use warnings;
use autodie;
use feature qw(say);
use constant {
WEB_FILE => "web1.txt",
};
open my $web_fh, "<", WEB_FILE; #Autodie will catch this for you
my %unique_urls;
while ( my $line ( <$web_fh> ) {
my $url = (split /\s+/, $line)[9];
my $response_time = (split /\s+/, $line)[10]; #Taking a guess
if ( not exists $unique_urls{$url} ) { #Not really needed
$unique_urls{$url}->{INSTANCES} = 0;
$unique_urls{$url}->{TOTAL_RESP_TIME} = 0;
}
$unique_urls{$url}->{INSTANCES} += 1;
$unique_urls{$url}->{TOTAL_RESP_TIME} += $response_time;
}
$close $web_fh;
Now we can print them out:
print "%20.20s %6s %8s\n", "URL", "INST", "AVE";
for my $url ( sort keys %unique_urls ) {
my $total_resp_time = $unique_urls{$url}->{TOTAL_RESP_TIME};
my $instances = $unique_urls{$url}->{INSTANCES};
my $average = $total_resp_time / $instances
printf "%-20.20s %-6d %-8.5f\n", $url, $instances, $average";
}
I like using printf for tables.
Instead of setting the value to 1 here:
$uniqueURLs{$line[9]}=1;
Store a data structure indicating the response time and the number of times this URL has been seen (so you can properly calculate the average). You can use an array ref, or hashref if you want. If the key doesn't exist yet, that means it hasn't been seen yet, and you can set some initial values.
# Initialize 3-element arrayref: [count, total, average]
$uniqueURLS{$line[9]} = [0, 0, 0] if not exists $uniqueURLS{$line[9]};
$uniqueURLs{$line[9]}->[0]++; # Count
$uniqueURLs{$line[9]}->[1] += $line[7]; # Total time
# Calculate average
$uniqueURLs{$line[9]}->[2] = $uniqueURLs{$line[9]}->[1] / $uniqueURLs{$line[9]}->[0];
One way you can get count of uniqueURLS is by counting the keys:
print scalar(keys %uniqueURLS); # Print number of unique url's
In your loop, you can print out the url and average time like this:
for $url (keys %uniqueURLs) {
print $url, ' - ', $uniqueURLs[$url]->[2], "seconds \n";
}

Perl: Can't pass an "on-the-fly" array to a sub

strftime(), as per cpan.org:
print strftime($template, #lt);
I just can't figure the right Perl code recipe for this one. It keeps reporting an error where I call strftime():
...
use Date::Format;
...
sub parse_date {
if ($_[0]) {
$_[0] =~ /(\d{4})/;
my $y = $1;
$_[0] =~ s/\d{4}//;
$_[0] =~ /(\d\d)\D(\d\d)/;
return [$2,$1,$y];
}
return [7,7,2010];
}
foreach my $groupnode ($groupnodes->get_nodelist) {
my $groupname = $xp->find('name/text()', $groupnode);
my $entrynodes = $xp->find('entry', $groupnode);
for my $entrynode ($entrynodes->get_nodelist) {
...
my $date_added = parse_date($xp->find('date_added/text()', $entrynode));
...
$groups{$groupname}{$entryname} = {...,'date_added'=>$date_added,...};
...
}
}
...
my $imday = $maxmonth <= 12 ? 0 : 1;
...
while (my ($groupname, $entries) = each %groups) {
...
while (my ($entryname, $details) = each %$entries) {
...
my $d = #{$details->{'date_added'}};
$writer->dataElement("creation", strftime($date_template, (0,0,12,#$d[0^$imday],#$d[1^$imday]-1,#$d[2],0,0,0)));
}
...
}
...
If I use () to pass the required array by strftime(), I get:
Type of arg 2 to Date::Format::strftime must be array (not list) at ./blah.pl line 87, near "))"
If I use [] to pass the required array, I get:
Type of arg 2 to Date::Format::strftime must be array (not anonymous list ([])) at ./blah.pl line 87, near "])"
How can I pass an array on the fly to a sub in Perl? This can easily be done with PHP, Python, JS, etc. But I just can't figure it with Perl.
EDIT: I reduced the code to these few lines, and I still got the exact same problem:
#!/usr/bin/perl
use warnings;
use strict;
use Date::Format;
my #d = [7,13,2010];
my $imday = 1;
print strftime( q"%Y-%m-%dT12:00:00", (0,0,12,$d[0^$imday],$d[1^$imday]-1,$d[2],0,0,0));
Where an array is required and you have an ad hoc list, you need to actually create an array. It doesn't need to be a separate variable, you can do just:
strftime(
$date_template,
#{ [0,0,12,$d[0^$imday],$d[1^$imday],$d[2],0,0,0] }
);
I have no clue why Date::Format would subject you to this hideousness and not just expect multiple scalar parameters; seems senseless (and contrary to how other modules implement strftime). Graham Barr usually designs better interfaces than this. Maybe it dates from when prototypes still seemed like a cool idea for general purposes.
To use a list as an anonymous array for, say, string interpolation, you could write
print "#{[1, 2, 3]}\n";
to get
1 2 3
The same technique provides a workaround to Date::Format::strftime's funky prototype:
print strftime(q"%Y-%m-%dT12:00:00",
#{[0,0,12,$d[0^$imday],$d[1^$imday]-1,$d[2],0,0,0]});
Output:
1900-24709920-00T12:00:00
Normally, it is easy to pass arrays "on-the-fly" to Perl subroutines. But Date::Format::strftime is a special case with a special prototype ($\#;$) that doesn't allow "list" arguments or "list assignment" arguments:
strftime($format, (0,0,12,13,7-1,2010-1900)); # not ok
strftime($format, #a=(0,0,12,13,7-1,2010-1900)); # not ok
The workaround is that you must call strftime with an array variable.
my #time = (0,0,12,13,7-1,2010-1900); # note: #array = ( ... ), not [ ... ]
strftime($format, #time);
I looked again and I see the real problem in this code:
my $d = #{$details->{'date_added'}};
$writer->dataElement("creation", strftime($date_template, (0,0,12,#$d[0^$imday],#$d[1^$imday]-1,#$d[2],0,0,0)));
Specifically #{$details->{'date_added'}} is a dereference. But you're assigning it to a scalar variable and you don't need to dereference in the line below it:
my #d = #{$details->{'date_added'}};
$writer->dataElement("creation", strftime($date_template, (0,0,12,$d[0^$imday],$d[1^$imday]-1,$d[2],0,0,0)));
I've created a regular array for your reference #d and just accessed it as a regular array ( $d[ ... ] instead of #$d[ ... ] )

Converting code to perl sub, but not sure I'm doing it right

I'm working from a question I posted earlier (here), and trying to convert the answer to a sub so I can use it multiple times. Not sure that it's done right though. Can anyone provide a better or cleaner sub?
I have a good deal of experience programming, but my primary language is PHP. It's frustrating to know how to execute in one language, but not be able to do it in another.
sub search_for_key
{
my ($args) = #_;
foreach $row(#{$args->{search_ary}}){
print "#$row[0] : #$row[1]\n";
}
my $thiskey = NULL;
my #result = map { $args->{search_ary}[$_][0] } # Get the 0th column...
grep { #$args->{search_in} =~ /$args->{search_ary}[$_][1]/ } # ... of rows where the
0 .. $#array; # first row matches
$thiskey = #result;
print "\nReturning: " . $thiskey . "\n";
return $thiskey;
}
search_for_key({
'search_ary' => $ref_cam_make,
'search_in' => 'Canon EOS Rebel XSi'
});
---Edit---
From the answers so far, I've cobbled together the function below. I'm new to Perl, so I don't really understand much of the syntax. All I know is that it throws an error (Not an ARRAY reference at line 26.) about that grep line.
Since I seem to not have given enough info, I will also mention that:
I am calling this function like this (which may or may not be correct):
search_for_key({
'search_ary' => $ref_cam_make,
'search_in' => 'Canon EOS Rebel XSi'
});
And $ref_cam_make is an array I collect from a database table like this:
$ref_cam_make = $sth->fetchall_arrayref;
And it is in the structure like this (if I understood how to make the associative fetch work properly, I would like to use it like that instead of by numeric keys):
Reference Array
Associative
row[1][cam_make_id]: 13, row[1][name]: Sony
Numeric
row[1][0]: 13, row[1][1]: Sony
row[0][0]: 19, row[0][1]: Canon
row[2][0]: 25, row[2][1]: HP
sub search_for_key
{
my ($args) = #_;
foreach my $row(#{$args->{search_ary}}){
print "#$row[0] : #$row[1]\n";
}
print grep { $args->{search_in} =~ #$args->{search_ary}[$_][1] } #$args->{search_ary};
}
You are moving in the direction of a 2D array, where the [0] element is some sort of ID number and the [1] element is the camera make. Although reasonable in a quick-and-dirty way, such approaches quickly lead to unreadable code. Your project will be easier to maintain and evolve if you work with richer, more declarative data structures.
The example below uses hash references to represent the camera brands. An even nicer approach is to use objects. When you're ready to take that step, look into Moose.
use strict;
use warnings;
demo_search_feature();
sub demo_search_feature {
my #camera_brands = (
{ make => 'Canon', id => 19 },
{ make => 'Sony', id => 13 },
{ make => 'HP', id => 25 },
);
my #test_searches = (
"Sony's Cyber-shot DSC-S600",
"Canon cameras",
"Sony HPX-32",
);
for my $ts (#test_searches){
print $ts, "\n";
my #hits = find_hits($ts, \#camera_brands);
print ' => ', cb_stringify($_), "\n" for #hits;
}
}
sub cb_stringify {
my $cb = shift;
return sprintf 'id=%d make=%s', $cb->{id}, $cb->{make};
}
sub find_hits {
my ($search, $camera_brands) = #_;
return grep { $search =~ $_->{make} } #$camera_brands;
}
This whole sub is really confusing, and I'm a fairly regular perl user. Here are some blanket suggestions.
Do not create your own undef ever -- use undef then return at the bottom return $var // 'NULL'.
Do not ever do this: foreach $row, because foreach my $row is less prone to create problems. Localizing variables is good.
Do not needlessly concatenate, for it offends the style god: not this, print "\nReturning: " . $thiskey . "\n";, but print "\nReturning: $thiskey\n";, or if you don't need the first \n: say "Returning: $thiskey;" (5.10 only)
greping over 0 .. $#array; is categorically lame, just grep over the array: grep {} #{$foo[0]}, and with that code being so complex you almost certainly don't want grep (though I don't understand what you're doing to be honest.). Check out perldoc -q first -- in short grep doesn't stop until the end.
Lastly, do not assign an array to a scalar: $thiskey = #result; is an implicit $thiskey = scalar #result; (see perldoc -q scalar) for more info. What you probably want is to return the array reference. Something like this (which eliminates $thiskey)
printf "\nReturning: %s\n", join ', ', #result;
#result ? \#result : 'NULL';
If you're intending to return whether a match is found, this code should work (inefficiently). If you're intending to return the key, though, it won't -- the scalar value of #result (which is what you're getting when you say $thiskey = #result;) is the number of items in the list, not the first entry.
$thiskey = #result; should probably be changed to $thiskey = $result[0];, if you want mostly-equivalent functionality to the code you based this off of. Note that it won't account for multiple matches anymore, though, unless you return #result in its entirety, which kinda makes more sense anyway.