Problems? Is your data what you think it is? PerlMonks

### sorting based on a list

 on May 09, 2001 at 18:05 UTC Need Help??
mbond has asked for the wisdom of the Perl Monks concerning the following question:

I have a list, lets call it:

@list = qw(a z b y c x);

and i have another list ... lets call this one:

@list2 = qw(zulu charlie xray yankee bravo alpha);

here's my problem:

I want to sort @list2 based on the order in @list, is there an easy way to do this using the sort function, or do i just have to loop through it like:
```foreach \$a (@list) {
foreach (@list2) {
push(@list3,\$_) if substr(\$_,0,1) eq "\$a";
}
}

thanks,

mbond

Replies are listed 'Best First'.
Re: sorting based on a list
by merlyn (Sage) on May 09, 2001 at 18:36 UTC
```my @indicies = sort { \$list[\$a] cmp \$list[\$b] } 0..\$#list;
my @rearranged = @list2[@indicies];

update: Hold on. I solved the problem he stated. Not the problem he had. OK, nothing cannot be solved without a bit of indirection:
```my @indicies = sort { \$list[\$a] cmp \$list[\$b] } 0..\$#list;
my @rearranged;
@rearranged[@indicies] = sort @list2;
There. Make a sorted @list2 be disordered in the same way that @list is.

Also see my other solution.

Umm.. this returns zulu xray bravo alpha yankee charlie

that's not ordered right...

Update Yup, all better now
- Ant

Re: sorting based on a list
by merlyn (Sage) on May 09, 2001 at 19:23 UTC
Hmm. I just realized there's another interpretation of the question that might be more valid than my other solution.

Suppose the question means: given elements of @list2 which begin with the letters of @list, rearrange them so that the beginning letters are sorted in that order. Thus, the length of @list2 in no way correlates with the length of @list, and this was merely a concidence in the sample set.

To that, I'll have to add two arbitrary decisions: if the word doesn't begin with one of the letters, put it first, and within a given initial letter, sort alphabetically.

The code for that would look like this:

```my %mapping; @mapping{@list} = 1..@list;

my @rearranged =
map { \$_->[0] }
sort { \$a->[1] <=> \$b->[1] or \$a->[0] cmp \$b->[0] }
map { [\$_, \$mapping{substr(\$_,0,1)} || 0] } @list2;
There. One of them there, Schwartzian thingies.

And yes, I see other similar solutions in this thread, but I bet those repeated index() calls inside the sort comparator are gonna make it doggy slow for dozens of elements. By caching the reverse map, as well as the sort position, we save mondo time.

And here, for completeness, is a (presumably much slower) solution based on the opposite assumptions (extra letters last, and continue the comparison down the string). This is, of course, more or less equivalent to rewriting cmp with your own alphabet. It could undoubtedly be done faster with an iterative solution, but then it could also be done faster in C.

```use strict;
use vars '\$sortstr';
my @list = qw(a z b y c x);
my @list2 = qw(zulu zuzu zucu charlie fred xray yankee bravo alpha);

\$sortstr = join('', @list);
my @sorted_list =  sort mycmp @list2;

sub mycmp {
my (\$A, \$B)= @_ ? @_ : (\$a,\$b);
my \$temp;
my \$Aspot = (\$temp = index(\$sortstr,substr(\$A,0,1))) < 0 ?
length(\$sortstr) : \$temp;
my \$Bspot = (\$temp = index(\$sortstr,substr(\$B,0,1))) < 0 ?
length(\$sortstr) : \$temp;

return
\$Aspot <=> \$Bspot
||
mycmp(substr(\$A,1),substr(\$B,1))
||
\$A cmp \$B
}

Sorts to:
alpha zuzu zucu zulu bravo yankee charlie xray fred

If God had meant us to fly, he would *never* have give us the railroads.
--Michael Flanders

Re: sorting based on a list
by davorg (Chancellor) on May 09, 2001 at 18:16 UTC

Here's one suggestion using hash slices:

```#!/usr/bin/perl -w
use strict;

my @list = qw(a z b y c x);

my @list2 = qw(zulu charlie xray yankee bravo alpha);

my %hash;

@hash{@list} = @list2;

my @sorted = @hash{sort @list};

print "@sorted\n";

Update: OK. Misunderstood the question. In that case it's:

```@hash{sort @list} = sort @list2;

my @sorted = @hash{@list};

but merlyn's solution is better :(

--
<http://www.dave.org.uk>

"Perl makes the fun jobs fun
and the boring jobs bearable" - me

This doesn't work... he wants to sort the words in order of their first letters based on the @list array... your thing assigns the characters to the items in list 2, then orders that list and reads them, returning...

zulu xray bravo alpha yankee charlie
alpha zulu bravo yankee charlie xray
- Ant

what's @hash{@list}?? and wouldn't hasing it up kill duplicates?? wouldn't someone need those duplicates??

more: I ran this and got: zulu xray bravo alpha yankee charlie
is this considered sorted??

He who asks will be a fool for five minutes, but he who doesn't ask will remain a fool for life.

@hash{@list} is the list of hash values corresponding to the keys in @list.

And, yes, it does break if @list contains duplicates.

--
<http://www.dave.org.uk>

"Perl makes the fun jobs fun
and the boring jobs bearable" - me

Re: sorting based on a list
by Albannach (Prior) on May 09, 2001 at 18:13 UTC

```use strict;
use warnings;

my \$sortorder = 'azbycx';
my @list2 = qw(zulu charlie xray yankee bravo alpha);

print join ',', @list2,"\n";
print join ',', sort {index(\$sortorder,substr(\$a,0,1)) <=> index(\$sort
+order,substr(\$b,0,1))} @list2
Update: Me stupid! This only sorts by the first character so it won't re-order qw(zvlu zulu) correctly (and I should have added a call to lc around the substr)! Note that alfie fixed the former bug here, if you add ChemBoy's fix (<=> to cmp) to that ;-).

--
I'd like to be able to assign to an luser

Re: sorting based on a list
by alfie (Pilgrim) on May 09, 2001 at 18:19 UTC
If you are really going to sort by the first letter only in a special order you might try it with this:
```my \$sortstr = join('', @list);
my @sorted_list = sort {
index(\$sortstr,substr(\$a,0,1)) <=> index(\$sortstr,substr(\$b,0,1))
||
\$a <=> \$b
} @list2;
The first will compare the first letter only, the later will compare if the first letter is equal.
--
use signature; signature(" So long\nAlfie");

Nice idea--I propose the following modification, to dump extra words at the end (and to use string comparison, of course):

```my \$sortstr = join('', @list);
my @sorted_list = sort {
rindex(\$sortstr,substr(\$b,0,1)) <=> rindex(\$sortstr,substr(\$a,0,1))
||
\$a cmp \$b
} @list2;

Of course, you might want words that start with letters not in the list to be at the front, in which case, alphie's solution is preferable (aside from using <=> instead of cmp).

Update: doh! It doesn't do what I thought it did! Fix coming...
Fix here:

```my \$sortstr = join('', @list);
my @sorted_list = sort {
my (\$tmp1,\$tmp2);
((\$tmp1 = index(\$sortstr,substr(\$a,0,1))) >= 0 ? \$tmp1 : length(\$sort
+str))
<=>
((\$tmp2 = index(\$sortstr,substr(\$b,0,1))) >= 0 ? \$tmp2 : length(\$sort
+str))
||
\$a cmp \$b
} @list2;

If God had meant us to fly, he would *never* have give us the railroads.
--Michael Flanders

Re: sorting based on a list
by suaveant (Parson) on May 09, 2001 at 18:23 UTC
you could also do...
```my(%order);
@order{qw(a z b y c x)} = (0..5);
for(sort { \$order{substr(\$a,0,1)} <=> \$order{substr(\$b,0,1)} } @list2)
+ {...
I doubt this is faster than Albannach's, but it is another way.
- Ant
Re: sorting based on a list
by SilverB1rd (Scribe) on May 09, 2001 at 18:20 UTC
I would suggest using a hash for that. You can then sort the hash or pull the words out by letter.
```my %foo = { 'a' => 'alpha', 'b' => 'bravo', 'c' => 'charlie'};

foreach \$x (sort(keys(%foo))) {
print \$foo{\$x}\n;
}

print \$foo{'b'},\$foo{'a'},\$foo{'c'};

------
The Price of Freedom is Eternal Vigilance

Create A New User
Node Status?
node history
Node Type: perlquestion [id://79086]
Approved by root
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (5)
As of 2018-06-19 06:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
Should cpanminus be part of the standard Perl release?

Results (111 votes). Check out past polls.

Notices?