[comp.lang.perl] Sortin based on part of an associative array?

jas@ISI.EDU (Jeff Sullivan) (04/11/91)

I have an situation where I'm storing a "structure" of information
about various files in a SPLITtable string using an associative array.
For example:

$Images{"TEST.IMG"} = "120#55#4#GIF"

which might mean that the file TEST.IMG is 120x55 pixels, 4 bitplanes
deep, and is a GIF format file.

What I want to do is write a sort routine that lets me sort the
%Images elements by image length or width.  Here's what I have:

sub bylen {

local ($alen, $awid, $adep, $afmt, $blen, $bwid, $bdep, $bfmt);

	($alen, $awid, $adep, $afmt) = split('#', $Images{$a});
	($blen, $bwid, $bdep, $bfmt) = split('#', $Images{$b});

	$alen cmp $blen;

}


However, when I try to do this (on MS-PERL PL 18), I get a syntax
error at the line containing "$alen cmp $blen;"

Why?

Can't I do this?  Is there a better way?

jas
--
--------------------------------------------------------------------------
Jeffrey A. Sullivan		| Senior Systems Programmer
jas@venera.isi.edu		| Information Sciences Institute
jas@isi.edu                    	| University of Southern California

tchrist@convex.COM (Tom Christiansen) (04/11/91)

From the keyboard of jas@ISI.EDU (Jeff Sullivan):
:
:I have an situation where I'm storing a "structure" of information
:about various files in a SPLITtable string using an associative array.
:For example:
:
:$Images{"TEST.IMG"} = "120#55#4#GIF"
:
:which might mean that the file TEST.IMG is 120x55 pixels, 4 bitplanes
:deep, and is a GIF format file.
:
:What I want to do is write a sort routine that lets me sort the
:%Images elements by image length or width.  Here's what I have:
:
:sub bylen {
:
:local ($alen, $awid, $adep, $afmt, $blen, $bwid, $bdep, $bfmt);
:
:	($alen, $awid, $adep, $afmt) = split('#', $Images{$a});
:	($blen, $bwid, $bdep, $bfmt) = split('#', $Images{$b});
:
:	$alen cmp $blen;
:
:}
:
:
:However, when I try to do this (on MS-PERL PL 18), I get a syntax
:error at the line containing "$alen cmp $blen;"
:
:Why?

Because you're running PL 18.  The cmp operator wasn't in the language
then.  You could use ($alen - $blen) instead of ($alen cmp $blen).

:Can't I do this?  Is there a better way?

Yes: only run one split per element, instead of two per compare.
Something like this (untested):

    sub sort_by_len { &sort_by_field(*Images, $[); } 
    sub sort_by_wid { &sort_by_field(*Images, $[+1); } 
    sub bynum { $a - $b; }

    sub sort_by_field { 
	local(*table, $field) = @_;
	local($_, @keys);
	for (keys %table) {
	    push(@keys, (split(/#/, $table{$_}))[$field]);
	} 
	sort bynum @keys;
    } 

@Images{&sort_by_len} is all the elements in length order; 
@Images{&sort_by_wid} is all the elements in width order.


--tom

chrise@hpnmdla.hp.com (Chris Eich) (04/16/91)

In comp.lang.perl, tchrist@convex.COM (Tom Christiansen) writes:

    sub sort_by_field {
	local(*table, $field) = @_;
	local($_, @keys);
	for (keys %table) {
	    push(@keys, (split(/#/, $table{$_}))[$field]);
	} 
	sort bynum @keys;
    } 

    @Images{&sort_by_len} is all the elements in length order; 
    @Images{&sort_by_wid} is all the elements in width order.

This doesn't seem to work; sort_by_len returns the lengths, not the keys
of the original table.

You seem to want sort_by_field to return the keys of the original table,
so how about this:

    sub sort_by_field {
	local(*Table, $Field) = @_;
	local(%Keys);

	foreach (keys %Table) {
	    $Keys{(split(/:/, $Table{$_}))[$Field]} = $_;
	}
	@Table{@Keys{sort by_num keys %Keys}};
    }

Chris

tchrist@convex.COM (Tom Christiansen) (04/16/91)

From the keyboard of chrise@hpnmdla.hp.com (Chris Eich):
    [quoting some faulty code of mine, deleted here]

:This doesn't seem to work; sort_by_len returns the lengths, not the keys
:of the original table.
:
:You seem to want sort_by_field to return the keys of the original table,
:so how about this:
:
:    sub sort_by_field {
:	local(*Table, $Field) = @_;
:	local(%Keys);
:
:	foreach (keys %Table) {
:	    $Keys{(split(/:/, $Table{$_}))[$Field]} = $_;
:	}
:	@Table{@Keys{sort by_num keys %Keys}};
:    }

After staring at it until it comes into focus, yes, this is much better,
as in I think it even works, unlike my original.  Mind you that I *did*
say "something like this (untested)".  I consider Chris's solution here
"something like this (tested)". :-)

I know, I know: untested code never runs.

This little gem is actually a pretty nice example of several things:  a
good time to use pass-by-name, inverting a table, and sorting with a
subroutine.  I also like the double @{} reference here, because I think to
understand it, you really need to get your $'s and @'s and %'s down cold.

--tom

chrise@hpnmdla.hp.com (Chris Eich) (04/17/91)

It's a sad day when I can't get a correction correct.  :-)

I had my sort_by_field return:

        @Table{@Keys{sort by_num keys %Keys}};

But it should have returned just:

        @Keys{sort by_num keys %Keys};

Chris "it was Monday, OK?"