9

I have a perl array of to-do tasks that looks like this:

@todos = (
  "1 (A) Complete online final @evm4700 t:2010-06-02",
  "3 Write thank-you t:2010-06-10",
  "4 (B) Clean t:2010-05-30",
  "5 Donate to LSF t:2010-06-02",
  "6 (A) t:2010-05-30 Pick up dry cleaning",
  "2 (C) Call Chris Johnson t:2010-06-01"
);

That first number is the task's ID. If a task has ([A-Z]) next to, that defines the task's priority. What I want to do is sort the tasks array in a way that places the prioritized items first (and in order of descending priority, from A - Z):

@todos = (
  "1 (A) Complete online final @evm4700 t:2010-06-02",
  "6 (A) t:2010-05-30 Pick up dry cleaning",
  "4 (B) Clean t:2010-05-30",
  "2 (C) Call Chris Johnson t:2010-06-01"
  "3 Write thank-you t:2010-06-10",
  "5 Donate to LSF t:2010-06-02",
);

I cannot use a regular sort() because of those IDs next to the tasks, so I'm assuming that some sort of customized sorting subroutine is needed. However, my knowledge of how to do this efficiently in perl is minimal.

Thanks, all.

5 Answers 5

14

Sounds like you want the Schwartzian transform:

@todos =
    map  { $_->[0] }
    sort { $a->[1] cmp $b->[1] or $a->[0] cmp $b->[0] }
    map  { [ $_, /^\d+ \(([[:alpha:]])\)/ ? $1 : "[" ] }
    @todos;

"[" is the character after "Z"; giving this "priority" to otherwise unprioritized items will sort them after the prioritized items.

Alternately, and perhaps more easily graspable:

@todos =
    map { substr $_, 1 }
    sort
    map { (/^\d+ \(([[:alpha:]])\)/ ? $1 : "[") . $_ }
    @todos;
Sign up to request clarification or add additional context in comments.

7 Comments

@Sean : While a Schwartzian transform is groovy (and applicable), it's very hard to follow, especially given that the OP is a beginner.
@Sean - this is a cool solution; thank you. Do you need to escape that ] ater :alpha:? @Zaid - even though I am a beginner, @Sean gave me that link that explains the Schwartzian transform, so I can understand it. :)
@ABach: No, no escaping needed; this is a POSIX character class. (See the perlre man page.) ...But I did make a typo ("[:alpha:]]" instead of "[[:alpha:]]", which I just fixed.
10 should go after 9 (you need numerical sort)
@ABach : It's fine by me if it's fine by you! Just remember that Perl doesn't have to be so hard to follow...
|
3

Here's a version that is fairly explicit about how it works:

my @sorted_todos = sort {
    my ($right_prio) = ($b =~ /^\d+\s+\(([A-Z])\)/);
    return -1 unless defined $right_prio;
    my ($left_prio) = ($a =~ /^\d+\s+\(([A-Z])\)/);
    return 1 unless defined $left_prio;
    return $left_prio cmp $right_prio;
} @todos;

Comments

0

Here's fixed @Sean's solution that uses numerical sort for task IDs (thus 10th task goes after 9th as it should):

my @sorted_todos =  map  { $_->[0] }
    sort { $a->[1][1] cmp $b->[1][1] # A
                       || 
           $a->[1][0] <=> $b->[1][0] # 1
    } map  { [ $_, /^(\d+) \(([[:alpha:]])\)/ ? [$1, $2] : [0, "zz"]] }  @todos;

2 Comments

For something this minor, editing Sean's answer seems sufficient. You're not exactly lacking for rep.
@rjh: read comments to Sean's answer the OP doesn't want the solution I provided so It would be wrong on my part to edit Sean's answer. On the other hand my answer is useful for others who would search for "custom sort in perl" in the future.
0
use Sort::Key 'keysort';

my @sorted = keysort { /^\d+\s+\(([A-Z])\)/ ? $1 : 'ZZ' } @todos;

Comments

0

Much simpler solution:

sort {($a =~ /\((.)\)/)[0] cmp ($b =~ /\((.)\)/)[0]} @todos;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.