Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations TouchToneTommy on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

sort array by subkey value 2

Status
Not open for further replies.

yesti

MIS
Dec 8, 2000
166
US
I don't know too much about perl, got this code from someone long gone and have to sort an array by subkey value. The array is constructed as follows:

<within a loop>

$record = {
NAME => $taskname,
COMPDATE => $data{$notamnum}->{ComplianceDate}
};
push @records, $record;

<end of loop>

How do I sort the @records by COMPDATE? Thanks.
 
Change '<=>' to 'cmp' if you are sorting strings instead of numbers. Transpose $a and $b to sort in reverse order.

Code:
[url=http://perldoc.perl.org/functions/my.html][black][b]my[/b][/black][/url] [blue]@sorted[/blue] = [url=http://perldoc.perl.org/functions/map.html][black][b]map[/b][/black][/url] [red]{[/red][blue]$_[/blue]->[red][[/red][fuchsia]0[/fuchsia][red]][/red][red]}[/red]
             [url=http://perldoc.perl.org/functions/sort.html][black][b]sort[/b][/black][/url] [red]{[/red][blue]$a[/blue]->[red][[/red][fuchsia]1[/fuchsia][red]][/red] <=> [blue]$b[/blue]->[red][[/red][fuchsia]1[/fuchsia][red]][/red][red]}[/red]
             [black][b]map[/b][/black] [red]{[/red][red][[/red][blue]$_[/blue], [blue]$_[/blue]->[red]{[/red]COMPDATE[red]}[/red][red]][/red][red]}[/red] [blue]@records[/blue][red];[/red]

Ask questions if you need something explained.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
The Schwartzian transform is an unnecessary step in this sort. Just access the values by their key directly in the sort:

Code:
[url=http://perldoc.perl.org/functions/my.html][black][b]my[/b][/black][/url] [blue]@sorted[/blue] = [url=http://perldoc.perl.org/functions/sort.html][black][b]sort[/b][/black][/url] [red]{[/red][blue]$a[/blue]->[red]{[/red]COMPDATE[red]}[/red] <=> [blue]$b[/blue]->[red]{[/red]COMPDATE[red]}[/red][red]}[/red] [blue]@records[/blue][red];[/red]

- Miller
 
You're correct my friend. It is uneccesary. But it could also be slower. Precalculating the sort keys can greatly increase the performance. On the other hand, if the array is not big, the increase in performance is probably negligible if any.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
Thank you both for your help, the biggest problem I am having is with the syntax. The sort works, but I realized that it wasn't exactly what I wanted. The COMPDATE subkey is a string: 29 sept 2004. How would I sort it as such? I installed and am looking at Date::parse.
 
You can code this yourself if you can manage to get the date into a sortable format, something like:

Code:
[url=http://perldoc.perl.org/functions/use.html][black][b]use[/b][/black][/url] [green]Data::Dumper[/green][red];[/red]	
[url=http://perldoc.perl.org/functions/my.html][black][b]my[/b][/black][/url] [blue]%months[/blue] = [red]([/red]
   [purple]jan[/purple] => [red]'[/red][purple]01[/purple][red]'[/red],
   [purple]feb[/purple] => [red]'[/red][purple]02[/purple][red]'[/red],
   [purple]mar[/purple] => [red]'[/red][purple]03[/purple][red]'[/red],
   [purple]apr[/purple] => [red]'[/red][purple]04[/purple][red]'[/red],
   [purple]may[/purple] => [red]'[/red][purple]05[/purple][red]'[/red],
   [purple]jun[/purple] => [red]'[/red][purple]06[/purple][red]'[/red],
   [purple]jul[/purple] => [red]'[/red][purple]07[/purple][red]'[/red],
   [purple]aug[/purple] => [red]'[/red][purple]08[/purple][red]'[/red],
   [purple]sep[/purple] => [red]'[/red][purple]09[/purple][red]'[/red],
   [url=http://perldoc.perl.org/functions/oct.html][black][b]oct[/b][/black][/url] => [red]'[/red][purple]10[/purple][red]'[/red],
   [purple]nov[/purple] => [red]'[/red][purple]11[/purple][red]'[/red],
   [purple]dec[/purple] => [red]'[/red][purple]12[/purple][red]'[/red]
[red])[/red][red];[/red]
[black][b]my[/b][/black] [blue]@sorted[/blue] = [url=http://perldoc.perl.org/functions/map.html][black][b]map[/b][/black][/url]  [red]{[/red] [blue]$_[/blue]->[red][[/red][fuchsia]0[/fuchsia][red]][/red] [red]}[/red]
             [url=http://perldoc.perl.org/functions/sort.html][black][b]sort[/b][/black][/url] [red]{[/red] [blue]$a[/blue]->[red][[/red][fuchsia]1[/fuchsia][red]][/red] cmp [blue]$b[/blue]->[red][[/red][fuchsia]1[/fuchsia][red]][/red] [red]}[/red]  
             [black][b]map[/b][/black]  [red]{[/red][black][b]my[/b][/black] [blue]@t[/blue] = [url=http://perldoc.perl.org/functions/split.html][black][b]split[/b][/black][/url][red]([/red][red]/[/red][purple][purple][b]\s[/b][/purple]+[/purple][red]/[/red],[blue]$_[/blue]->[red]{[/red]COMPDATE[red]}[/red][red])[/red][red];[/red] [red][[/red][blue]$_[/blue],[red]"[/red][purple][blue]$t[/blue][0][blue]$months[/blue]{[blue]$t[/blue][1]}[blue]$t[/blue][2][/purple][red]"[/red][red]][/red][red]}[/red] [blue]@records[/blue][red];[/red]
				 
[url=http://perldoc.perl.org/functions/print.html][black][b]print[/b][/black][/url] Dumper \[blue]@sorted[/blue][red];[/red]
[tt]------------------------------------------------------------
Core (perl 5.10.0) Modules used :
[ul]
[li]Data::Dumper - stringified perl data structures, suitable for both printing and eval[/li]
[/ul]
[/tt]

the hash keys need to be the exact spelling of the month or you could use a regexp to find the appropriate key but that would less efficient (may not be a concern though). The day has to be zero padded for values less than 10, so 2 would need to be 02, etc. You could also use a hash for that or convert the days using sprintf() to make sure they are always zero padded if necessary.

Or you can use a Date module. The advantage there would be a sinlge interface to all your date manipulation worries, the disadvantage is getting it to work in the first place.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
ok, here's what I got so far.

Earlier in the code, it selectively adds a <font> tag so that when displayed the text is red. I have to strip that out so the Date::parse str2time function can work its magic, hopefully.

I was able to do this with the following code:

Code:
my $temp = $data{$notamnum}->{ComplianceDate} =~ s/<font color=\"#FF0000\">//gi;

$temp = str2time($data{$notamnum}->{ComplianceDate});

How do I merge that with the sort code, if possible?
 
This is all a good lessons why you should not include markup in data and keep dates/times in epoch format as much as possible. But maybe your data is already like this and you can't do anything about that.

Code:
[url=http://perldoc.perl.org/functions/my.html][black][b]my[/b][/black][/url] [blue]@sorted[/blue] = [url=http://perldoc.perl.org/functions/map.html][black][b]map[/b][/black][/url]  [red]{[/red] [blue]$_[/blue]->[red][[/red][fuchsia]0[/fuchsia][red]][/red] [red]}[/red]
             [url=http://perldoc.perl.org/functions/sort.html][black][b]sort[/b][/black][/url] [red]{[/red] [blue]$a[/blue]->[red][[/red][fuchsia]1[/fuchsia][red]][/red] cmp [blue]$b[/blue]->[red][[/red][fuchsia]1[/fuchsia][red]][/red] [red]}[/red]  
             [black][b]map[/b][/black]  [red]{[/red][red]([/red][black][b]my[/b][/black] [blue]$temp[/blue] = [blue]$_[/blue]->[red]{[/red]ComplianceDate[red]}[/red][red])[/red] =~ [red]s/[/red][purple]<font color="#FF0000">[/purple][red]/[/red][purple][/purple][red]/[/red][red]gi[/red][red];[/red]
                   [red][[/red][blue]$_[/blue], [maroon]str2time[/maroon][red]([/red][blue]$temp[/blue][red])[/red][red]][/red]
                  [red]}[/red] [blue]@records[/blue][red];[/red]

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
Couldn't agree with you more. I am tempted to try a rewrite of this code since the list is _mostly_ sorted but not fully and I don't know why. Below are snippets of the str2time output, the entire list is 43 items long.

...
1152266400
1152180000
1154685600
...
1166176800
1165485600
1166349600
...
1168768800
1192442400
1176890400
...
1187690400
1185876000
1189072800
...
 
try '<=>' instead of 'cmp'

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
I did use the numeric sort <=>. Forgot to mention that, sorry.
 
According the Date::parse it uses Time::Local internally. When I pass those unix date strings above to localtime they appear to be wrong because they include the hour and there is no time part in your data string. So the translation to unix time might not be getting done correctly or all your dates are not formatted the same. With some dummy data this solution works. If you still get funky output you have to check the date formats stored in COMPDATE and make sure there isn't something wrong there.

Code:
[url=http://perldoc.perl.org/functions/use.html][black][b]use[/b][/black][/url] [green]Data::Dumper[/green][red];[/red]

[url=http://perldoc.perl.org/functions/my.html][black][b]my[/b][/black][/url] [blue]%month[/blue] = [red]([/red]
   [purple]jan[/purple] => [red]'[/red][purple]00[/purple][red]'[/red],
   [purple]feb[/purple] => [red]'[/red][purple]01[/purple][red]'[/red],
   [purple]mar[/purple] => [red]'[/red][purple]02[/purple][red]'[/red],
   [purple]apr[/purple] => [red]'[/red][purple]03[/purple][red]'[/red],
   [purple]may[/purple] => [red]'[/red][purple]04[/purple][red]'[/red],
   [purple]jun[/purple] => [red]'[/red][purple]05[/purple][red]'[/red],
   [purple]jul[/purple] => [red]'[/red][purple]06[/purple][red]'[/red],
   [purple]aug[/purple] => [red]'[/red][purple]07[/purple][red]'[/red],
   [purple]sep[/purple] => [red]'[/red][purple]08[/purple][red]'[/red],
   [url=http://perldoc.perl.org/functions/oct.html][black][b]oct[/b][/black][/url] => [red]'[/red][purple]09[/purple][red]'[/red],
   [purple]nov[/purple] => [red]'[/red][purple]10[/purple][red]'[/red],
   [purple]dec[/purple] => [red]'[/red][purple]11[/purple][red]'[/red],
[red])[/red][red];[/red]
[black][b]my[/b][/black] [blue]@records[/blue] = [red]([/red]
   [red]{[/red][purple]NAME[/purple] => [red]'[/red][purple]dog[/purple][red]'[/red],[purple]COMPDATE[/purple] =>[red]'[/red][purple]29 sept 2004[/purple][red]'[/red][red]}[/red],
   [red]{[/red][purple]NAME[/purple] => [red]'[/red][purple]dog[/purple][red]'[/red],[purple]COMPDATE[/purple] =>[red]'[/red][purple]1 jan 2004[/purple][red]'[/red][red]}[/red],
   [red]{[/red][purple]NAME[/purple] => [red]'[/red][purple]dog[/purple][red]'[/red],[purple]COMPDATE[/purple] =>[red]'[/red][purple]31 dec 2004[/purple][red]'[/red][red]}[/red],
   [red]{[/red][purple]NAME[/purple] => [red]'[/red][purple]dog[/purple][red]'[/red],[purple]COMPDATE[/purple] =>[red]'[/red][purple]29 dec 2004[/purple][red]'[/red][red]}[/red],
   [red]{[/red][purple]NAME[/purple] => [red]'[/red][purple]dog[/purple][red]'[/red],[purple]COMPDATE[/purple] =>[red]'[/red][purple]1 jan 2005[/purple][red]'[/red][red]}[/red],
   [red]{[/red][purple]NAME[/purple] => [red]'[/red][purple]dog[/purple][red]'[/red],[purple]COMPDATE[/purple] =>[red]'[/red][purple]14 jan 2007[/purple][red]'[/red][red]}[/red],
   [red]{[/red][purple]NAME[/purple] => [red]'[/red][purple]dog[/purple][red]'[/red],[purple]COMPDATE[/purple] =>[red]'[/red][purple]15 oct 2007[/purple][red]'[/red][red]}[/red],
   [red]{[/red][purple]NAME[/purple] => [red]'[/red][purple]dog[/purple][red]'[/red],[purple]COMPDATE[/purple] =>[red]'[/red][purple]18 april 2007[/purple][red]'[/red][red]}[/red],
[red])[/red][red];[/red]


[black][b]my[/b][/black] [blue]@sorted[/blue] = [url=http://perldoc.perl.org/functions/map.html][black][b]map[/b][/black][/url]  [red]{[/red] [blue]$_[/blue]->[red][[/red][fuchsia]0[/fuchsia][red]][/red] [red]}[/red]
             [url=http://perldoc.perl.org/functions/sort.html][black][b]sort[/b][/black][/url] [red]{[/red] [blue]$a[/blue]->[red][[/red][fuchsia]1[/fuchsia][red]][/red] cmp [blue]$b[/blue]->[red][[/red][fuchsia]1[/fuchsia][red]][/red] [red]}[/red]  
             [black][b]map[/b][/black]  [red]{[/red][black][b]my[/b][/black] [red]([/red][blue]$d[/blue],[blue]$m[/blue],[blue]$y[/blue][red])[/red] = [url=http://perldoc.perl.org/functions/split.html][black][b]split[/b][/black][/url][red]([/red][red]/[/red][purple][purple][b]\s[/b][/purple]+[/purple][red]/[/red],[blue]$_[/blue]->[red]{[/red]COMPDATE[red]}[/red][red])[/red][red];[/red]
                   [blue]$d[/blue] = [blue]$d[/blue]<[fuchsia]10[/fuchsia] ? [red]"[/red][purple]0[blue]$d[/blue][/purple][red]"[/red] : [blue]$d[/blue][red];[/red]
                   [blue]$m[/blue] = [blue]$month[/blue][red]{[/red][url=http://perldoc.perl.org/functions/lc.html][black][b]lc[/b][/black][/url] [url=http://perldoc.perl.org/functions/substr.html][black][b]substr[/b][/black][/url] [blue]$m[/blue],[fuchsia]0[/fuchsia],[fuchsia]3[/fuchsia][red]}[/red][red];[/red]
                   [red][[/red][blue]$_[/blue],[red]"[/red][purple][blue]$y[/blue][blue]$m[/blue][blue]$d[/blue][/purple][red]"[/red][red]][/red][red]}[/red] [blue]@records[/blue][red];[/red]
                
[url=http://perldoc.perl.org/functions/print.html][black][b]print[/b][/black][/url] Dumper \[blue]@sorted[/blue][red];[/red]
[tt]------------------------------------------------------------
Core (perl 5.10.0) Modules used :
[ul]
[li]Data::Dumper - stringified perl data structures, suitable for both printing and eval[/li]
[/ul]
[/tt]


output:

Code:
$VAR1 = [
          {
            'COMPDATE' => '1 jan 2004',
            'NAME' => 'dog'
          },
          {
            'COMPDATE' => '29 sept 2004',
            'NAME' => 'dog'
          },
          {
            'COMPDATE' => '29 dec 2004',
            'NAME' => 'dog'
          },
          {
            'COMPDATE' => '31 dec 2004',
            'NAME' => 'dog'
          },
          {
            'COMPDATE' => '1 jan 2005',
            'NAME' => 'dog'
          },
          {
            'COMPDATE' => '14 jan 2007',
            'NAME' => 'dog'
          },
          {
            'COMPDATE' => '18 april 2007',
            'NAME' => 'dog'
          },
          {
            'COMPDATE' => '15 oct 2007',
            'NAME' => 'dog'
          }
        ];



------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
I printed out the data from COMPDATE for a section that was being sorted wrong. I can't see anything obviously wrong with the date string getting passed to str2time, I dunno. Anyhow, the first is raw data, second is after the search/replace, third is after the str2time function is ran on it:

<font color="#FF0000">07 jul 2006
07 jul 2006
1152266400
-----------------------------------
<font color="#FF0000">06 jul 2006
06 jul 2006
1152180000
-----------------------------------
<font color="#FF0000">04 aug 2006
04 aug 2006
1154685600

I tried to merge your most recent code to include the search/replace (and take out the str2time) and came up with:

Code:
my @records = map { $_->[0] }
              sort { $a->[1] cmp $b->[1] }  
              map {(my $temp = $_->{ComplianceDate}) =~ s/<font color="#FF0000">//gi;
            	my ($d,$m,$y) = split(/\s+/,$temp);
                $d = $d<10 ? "0$d" : $d;
                $m = $month{lc substr $m,0,3};
                [$_,"$y$m$d"]
              } @records;

This isn't working either (no errors though) so just making sure I didn't screw it up. Thanks for all your help thus far!
 
Load Data::Dumper and print your array of arrays (@records) so we can see what it looks like.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
I commented out the code that adds the <font> tag, @records looks like:

Code:
$VAR1 = [
          {
            'COMPDATE' => '29 nov 2004',
          }
        ];

Previously, it had the <font> tag in there:

Code:
$VAR1 = [
          {
            'COMPDATE' => '<font color="#FF0000">29 nov 2004',
          }
        ];

I copy pasted your code from above (omitted the month hash):

Code:
my @records = map  { $_->[0] }
             sort { $a->[1] cmp $b->[1] }  
             map  {my ($d,$m,$y) = split(/\s+/,$_->{COMPDATE});
                   $d = $d<10 ? "0$d" : $d;
                   $m = $month{lc substr $m,0,3};
                   [$_,"$y$m$d"]} @records;

and viola (the bigger violin), it works. This page is separated into .plx and .tmpl files so I gotta figure out how to put the red color back in there (if COMPDATE < NOW) and, unless you happen to know off the top of your head how I could do that, that is a question for another thread. Many thanks.
 
it will not work properly without the month hash. But I need to see how the actual data you are trying to sort looks, so post maybe 10 records output from Data::Dumper. Make sure to put a slash \ before the dataset you are going to display or the output will not be correct.

print Dumper \@records;

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
Sorry, I meant I omitted the month hash in my post here. It works properly, the array is sorted by COMPDATE:

Code:
$VAR1 = [
          {
            'COMPDATE' => '29 nov 2004'
          },
          {
            'COMPDATE' => '05 feb 2006'
          },
          {
            'COMPDATE' => '03 mar 2006'
          },
          {
            'COMPDATE' => '03 mar 2006'
          },
          {
            'COMPDATE' => '03 mar 2006'
          },
          {
            'COMPDATE' => '03 mar 2006'
          },
          {
            'COMPDATE' => '08 may 2006'
          },
          {
            'COMPDATE' => '08 may 2006'
          },
          {
            'COMPDATE' => '26 may 2006'
          },
          {
            'COMPDATE' => '06 jul 2006'
          }
        ];

To turn the font red on the tmpl page, what code can I use to compare COMPDATE to the current date?

Code:
[% FOREACH notam = records %]
<td valign="bottom">[% notam.COMPDATE %]</td>
[% END %]
 
If you use this:

Code:
my @sorted = map  { $_->[0] }
             sort { $a->[1] cmp $b->[1] }  
             map  {(my $date = $_->{COMPDATE}) =~ s/<font color="#FF0000">//;
                   my ($d,$m,$y) = split(/\s+/,$date);
                   $d = $d<10 ? "0$d" : $d;
                   $m = $month{lc substr $m,0,3};
                   [$_,"$y$m$d"]} @records;

the font tag will still be in there if it was there to begin with. That will be much easier.

Next time keep your dates in epoch format and all the sorting/comparing will be easy. Only convert them to human readable format when really necessary, like when humans need to read them. [wink]

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
That worked like a charm, thanks! I can follow the logic but I could've never written that on my own even with extensive internet searching and cursing.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top