views:

146

answers:

3

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.

+8  A: 

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;
Sean
@Sean : While a Schwartzian transform is groovy (and applicable), it's very hard to follow, especially given that the OP is a beginner.
Zaid
@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
@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.
Sean
@Sean - looks great. Thanks. :)
ABach
`10` should go after `9` (you need numerical sort)
J.F. Sebastian
@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...
Zaid
@J.F. Sebastian: this isn't necessary; those IDs to do not factor into the sorting scheme I need. I updated my original question to specify that the tasks should be ordered by descending priority (from A - Z, followed by no priority items).
ABach
A: 

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;
J.F. Sebastian
For something this minor, editing Sean's answer seems sufficient. You're not exactly lacking for rep.
rjh
@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.
J.F. Sebastian
+2  A: 

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;
darch