Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Sorting associative array

18 views
Skip to first unread message

Tony Bowden

unread,
Dec 14, 1995, 3:00:00 AM12/14/95
to
version 4.0.1.7, Patch level: 35


If I have an associative array of names -> numbers:

e.g.

$size{fred}= 6
$size{joe} = 8
etc...

How do I print the top 10 (for instance) by value:

I can sort by name OK:
foreach $i (sort {$b <=> $a} keys (size)) {
print "$i : $size{$i}\n";
}

but if I use
foreach $i (sort {$b <=> $a} values (size)) {

what do I print? Can I refer backwards to an Assoc. array??

or how should I best sort?

Tony
--
------------------------------------------------------------------------------
Tony Bowden | Mail: t.bo...@qub.ac.uk or aj...@yfn.ysu.edu
Belfast | URL : http://boris.qub.ac.uk/tony/ for Sam Phillips, Manics,
N. Ireland | Radiohead, Matthew Fox, Jack T Chick, Innocence Mission & U2
------------------------------------------------------------------------------
to every solution there is a simple problem..............
.......and it's usually wrong
------------------------------------------------------------------------------


Tim Goodwin

unread,
Dec 15, 1995, 3:00:00 AM12/15/95
to
In article <1995Dec14....@queens-belfast.ac.uk>,
Tony Bowden <p949...@qub.ac.uk> wrote:
>If I have an associative array of names -> numbers: [...]

>How do I print the top 10 (for instance) by value:

See the FAQ, question 5.6.

Tim.
--
Tim Goodwin | "After all, what did Brunel, Watt, Boulton and
Unipalm PIPEX | Telford do that was complex? I could have built
Cambridge, UK | the Great Western Railway on my own." -- Ian Batten

Yanping Yuan

unread,
Dec 15, 1995, 3:00:00 AM12/15/95
to

try the following codes.

Tony Bowden (p949...@qub.ac.uk) wrote:
: version 4.0.1.7, Patch level: 35


: If I have an associative array of names -> numbers:

: e.g.

: $size{fred}= 6
: $size{joe} = 8
: etc...

: How do I print the top 10 (for instance) by value:

: I can sort by name OK:


: foreach $i (sort {$b <=> $a} keys (size)) {
: print "$i : $size{$i}\n";
: }

: but if I use
: foreach $i (sort {$b <=> $a} values (size)) {

foreach $i (sort {$size{$a} <=> $size{$b}} keys %size) {
# blablabla...
}

:

: what do I print? Can I refer backwards to an Assoc. array??

: or how should I best sort?


:

Michael J. Stok

unread,
Dec 16, 1995, 3:00:00 AM12/16/95
to
In article <1995Dec14....@queens-belfast.ac.uk>,
Tony Bowden <p949...@qub.ac.uk> wrote:

>If I have an associative array of names -> numbers:
>
>e.g.
>
>$size{fred}= 6
>$size{joe} = 8
>etc...
>
>How do I print the top 10 (for instance) by value:

One good way to start would be to haul the FAQ off a CPAN site like
ftp://ftp.demon.co.uk/pub/mirrors/perl/CPAN/ and at least read part 0
which is the table of contents...

@sortedKeys = sort {$size{$a} <=> $size{$b} || $a cmp $b} keys %size;

is a way to get a list of keys sorted by size, getting the top 10 is left
as an exercise for the reader...

Hope this helps,

Mike


--
Mike Stok | The "`Stok' disclaimers" apply.
st...@pencom.com | Pencom Systems Administration (work)
st...@cybercom.net | Cyber Access (play)
http://www.cybercom.net/~stok/ | The inevitable WWW page (?)

Tom Phoenix

unread,
Dec 17, 1995, 3:00:00 AM12/17/95
to Tony Bowden
On 14 Dec 1995, Tony Bowden asked (using different words) the following
question from the Perl FAQ:

5.6) How do I sort an associative array by value instead of by key?

You have to declare a sort subroutine to do this, or use an inline
function. Let's assume you want an ASCII sort on the values of the
associative array %ary. You could do so this way:

foreach $key (sort by_value keys %ary) {
print $key, '=', $ary{$key}, "\n";
}
sub by_value { $ary{$a} cmp $ary{$b}; }

If you wanted a descending numeric sort, you could do this:

sub by_value { $ary{$b} <=> $ary{$a}; }

You can also inline your sort function, like this, at least if
you have a relatively recent patchlevel of perl4 or are running perl5:

foreach $key ( sort { $ary{$b} <=> $ary{$a} } keys %ary ) {
print $key, '=', $ary{$key}, "\n";
}

If you wanted a function that didn't have the array name hard-wired
into it, you could so this:

foreach $key (&sort_by_value(*ary)) {
print $key, '=', $ary{$key}, "\n";
}
sub sort_by_value {
local(*x) = @_;
sub _by_value { $x{$a} cmp $x{$b}; }
sort _by_value keys %x;
}

If you want neither an alphabetic nor a numeric sort, then you'll
have to code in your own logic instead of relying on the built-in
signed comparison operators "cmp" and "<=>".

Note that if you're sorting on just a part of the value, such as a
piece you might extract via split, unpack, pattern-matching, or
substr, then rather than performing that operation inside your sort
routine on each call to it, it is significantly more efficient to
build a parallel array of just those portions you're sorting on, sort
the indices of this parallel array, and then to subscript your original
array using the newly sorted indices. This method works on both
regular and associative arrays, since both @ary[@idx] and @ary{@idx}
make sense. See page 245 in the Camel Book on "Sorting an Array by a
Computable Field" for a simple example of this.

For example, here's an efficient case-insensitive comparison:

@idx = ();
for (@data) { push (@idx, "\U$_") }
@sorted = @data[ sort { $idx[$a] cmp $idx[$b] } 0..$#data];

You can find this part of the FAQ at

http://gopher.metronet.com:70/0/perlinfo/faq/5.6

Hope this helps!

-- Tom Phoenix http://www.teleport.com/~rootbeer/
root...@teleport.com PGP Skribu al mi per Esperanto!
Randal Schwartz Defense: Send mail to <fu...@stonehenge.com>
Web - CGI - Perl custom programming solutions and assistance


Mark Hunnibell

unread,
Dec 17, 1995, 3:00:00 AM12/17/95
to
Tom Phoenix <root...@teleport.com> wrote:
>On 14 Dec 1995, Tony Bowden asked (using different words) the following
>question from the Perl FAQ:
>
>5.6) How do I sort an associative array by value instead of by key?

I didn't see the original post, but I have a related question that I
don't think is in the FAQ (yes, I checked). I have an array that contains
file names and a count number of each filename, like so:

count of $file = $files{$file}.

I've manage to do a nice sort and print of the count using something like
this, which you quoted from the FAQ:

> foreach $key ( sort { $ary{$b} <=> $ary{$a} } keys %ary ) {
> print $key, '=', $ary{$key}, "\n";
> }

However, I find I have a number of $files values that have a count of the
same number. I'd like to be able to make a REVERSE sort of the value in
$files{file} but then make the sort on $file in alphabetical order (i.e.
cmp). This makes the resulting output quite a bit more aesthetically
pleasing. I tried messing around with various sprintf uses to combine the
$files{file} value and the $file string, but it is didn't work and was
really doomed from the start since I have to pick the order of the sort
({$b} cmp {$a} *or* {$a} cmp {$a}) so I cannot fingure out how to do one
sort first and then make a second sort key.

I've thought of nesting another foreach loop inside the first, with a
conditional to only print the $file names that matched the current value
of $files{file}, but I wondered if there was a niftier way.

Thanks for including the URL to the FAQ in your answer Tom... it's so
much nicer than just "read the FAQ".

Cheers

Mark Hunnibell
ma...@connix.com


Tom Christiansen

unread,
Dec 18, 1995, 3:00:00 AM12/18/95
to
[courtesy cc of this posting sent to cited author via email]

In comp.lang.perl.misc,
Mark Hunnibell <ma...@connix.com> writes:

Perhaps you should read my FMTEYEWTK on Sorting:

$CPAN/doc/everything_to_know/sorting
ftp://perl.com/perl/info/everything_to_know/sorting
http://perl.com/all_about/sort.html

Or here's the current working sort doc from the man page:

--tom

=item sort SUBNAME LIST

=item sort BLOCK LIST

=item sort LIST

Sorts the LIST and returns the sorted list value. Nonexistent values
of arrays are stripped out. If SUBNAME or BLOCK is omitted, sorts
in standard string comparison order. If SUBNAME is specified, it
gives the name of a subroutine that returns an integer less than, equal
to, or greater than 0, depending on how the elements of the array are
to be ordered. (The <=> and cmp operators are extremely useful in such
routines.) SUBNAME may be a scalar variable name, in which case the
value provides the name of the subroutine to use. In place of a
SUBNAME, you can provide a BLOCK as an anonymous, in-line sort
subroutine.

In the interests of efficiency the normal calling code for subroutines is
bypassed, with the following effects: the subroutine may not be a
recursive subroutine, and the two elements to be compared are passed into
the subroutine not via @_ but as the global variables $main::a and
$main::b (see example below). They are passed by reference, so don't
modify $a and $b. And don't try to declare them as lexicals either.

Examples:

# sort lexically
@articles = sort @files;

# same thing, but with explicit sort routine
@articles = sort {$a cmp $b} @files;

# now case-insensitively
@articles = sort { uc($a) cmp uc($b)} @files;

# same thing in reversed order
@articles = sort {$b cmp $a} @files;

# sort numerically ascending
@articles = sort {$a <=> $b} @files;

# sort numerically descending
@articles = sort {$b <=> $a} @files;

# sort using explicit subroutine name
sub byage {
$age{$a} <=> $age{$b}; # presuming integers
}
@sortedclass = sort byage @class;

sub backwards { $b cmp $a; }
@harry = ('dog','cat','x','Cain','Abel');
@george = ('gone','chased','yz','Punished','Axed');
print sort @harry;
# prints AbelCaincatdogx
print sort backwards @harry;
# prints xdogcatCainAbel
print sort @george, 'to', @harry;
# prints AbelAxedCainPunishedcatchaseddoggonetoxyz

# inefficiently sort by descending numeric compare using
# the first integer after the first = sign, or the
# whole record case-insensitively otherwise

@new = sort {
($b =~ /=(\d+)/)[0] <=> ($a =~ /=(\d+)/)[0]
||
uc($a) cmp uc($b)
} @old;

# same thing, but much more efficiently;
# we'll build auxiliary indices instead
# for speed
@nums = @caps = ();
for (@old) {
push @nums, /=(\d+)/;
push @caps, uc($_);
}

@new = @old[ sort {
$nums[$b] <=> $nums[$a]
||
$caps[$a] cmp $caps[$b]
} 0..$#old
];

# same thing using a Schwartzian Transform (no temps)
@new = map { $_->[0] }
sort { $b->[1] <=> $a->[1]
||
$a->[2] cmp $b->[2]
} map { [$_, /=(\d+)/, uc($_)] } @old;
--
Tom Christiansen Perl Consultant, Gamer, Hiker tch...@mox.perl.com
The use of COBOL cripples the mind; its teaching should, therefore, be
regarded as a criminal offense.
--E. W. Dijkstra (1982)

Pablo Sanchez

unread,
Dec 19, 1995, 3:00:00 AM12/19/95
to
> [ FAQ question asked ... ]

Thanks to kind folks for not bludgeoning me and instead pointing me
to the FAQ and old news articles.

In short: got the answer! :-)

Pablo Sanchez | Ph # (415) 933.3812 Fax # (415) 933.2821
pa...@sgi.com | Pg # (800) 930.5635 -or- pab...@corp.sgi.com
===============================================================================
"I am accountable for my actions." http://reality.sgi.com/employees/pablo_corp
- pablo

Roderick Schertler

unread,
Dec 21, 1995, 3:00:00 AM12/21/95
to Mark Hunnibell
On 17 Dec 1995 23:58:26 GMT, Mark Hunnibell <ma...@connix.com> said:

> Tom Phoenix <root...@teleport.com> wrote:
>>
>> foreach $key ( sort { $ary{$b} <=> $ary{$a} } keys %ary ) {
>> print $key, '=', $ary{$key}, "\n";
>> }
>
> However, I find I have a number of $files values that have a count of the
> same number. I'd like to be able to make a REVERSE sort of the value in
> $files{file} but then make the sort on $file in alphabetical order (i.e.
> cmp).

To do a multi-key sort expand the sort expression so that it returns the
proper order. The only time you care about comparing file names is when
the files are the same size, which is when <=> returns 0. Since Perl's
|| returns the value which made it true (rather than a boolean as C's ||
does) you can use

sort { $files{$b} <=> $files{$a} || $a cmp $b } keys %files

to do what you want.

--
Roderick Schertler
rode...@gate.net


0 new messages