snafu has asked for the wisdom of the Perl Monks concerning the following question:

Good day fellow monks! I have been racking my brains on this one today. Before I ask my question let me show you the list I am trying to crunch:

100!100!key!date!2.. 100!100!key!store! 110!110!P\d\d\d_.*\.\d{6,}\.EMRemr 120!125! 1!yeada 130!132! 140!133! 150!134! 500!blah 160!135!arbor_ama 503!blah 170!136! 505!blah 180!137! 190!138!
All of that is in an array (by line, of course). I need to sort it by the number in the first field delimited by the '!' without actually affecting the elements in the original array.

I don't know *why* I can't think my way through this. Here is what I have tried:

my @array = (all that stuff above); @array = map { $_[0] = (split(/\!/,$_))[0]; } sort { $a <=> $b; } @array;
Which sorts great but it alters the elements in the original array such that all I get returned is the first field delimited by '!'.

The above code returns:

1 100 100 110 120 130 140 150 160 170 180 190 500 503 505

when what I need is:

1 100!100!key!date!2.. 100!100!key!store! 110!110!P\d\d\d_.*\.\d{6,}\.EMRemr 120!125! 130!132! 140!133! 150!134! 160!135!arbor_ama 170!136! 180!137! 190!138! 500!blah 503!blah 505!blah

I know I am missing something. What is it? :) sort{} is still a challenge to me sometimes.

TIA guys!

_ _ _ _ _ _ _ _ _ _
- Jim
Insert clever comment here...

  • Comment on Sort based on a delimited field in array elements without affecting original array elements
  • Select or Download Code

Replies are listed 'Best First'.
Re: Sort based on a delimited field in array elements without affecting original array elements
by Kanji (Parson) on Mar 01, 2002 at 23:14 UTC

    Take a look at the Schwartzian or Guttman Rosler Transforms, both of which are ideal for this kind of (and more advanced!) sorting ...

    @array = map { $_->[0] } # strip meta data sort { $a->[1] <=> $b->[1] } # sort by meta data map { [ $_, /([^!]+)/ ] } # build meta data @array;

    You might also want to check out japhy's Resorting to Sorting tute.

    Update: Tweaked the example for readability.

        --k.

      Speaking of Schwartzian transform, is there a simple way to define a ST-method and then apply it to different circumstances? The problem is felt when trying to sort two different kinds of things, yet without having to write the ST again and again, each time with a minor change.

      Consider:
      my @blobs = map { $_->[0] } sort {$a->[1] cmp $b->[1] } map { [$_, $_->property_foo()] } @blob_list; my @flobs = map { $_->[0] } sort {$a->[1] cmp $b->[1] } map { [$_, $_->function_bar()] } @flob_list;
      You can do this with C++ templates. How, though, in Perl, though without using eval or sacrificing the value of warnings when passing bad function names?
        sub ST(&@){ my $metric=shift; map {$_->[0]} sort {$a->[1] cmp $b->[1]} map {[$_,&{$metric}]} @_ }
•Re: Sort based on a delimited field in array elements without affecting original array elements
by merlyn (Sage) on Mar 01, 2002 at 23:46 UTC
Re: Sort based on a delimited field in array elements without affecting original array elements
by patgas (Friar) on Mar 01, 2002 at 23:14 UTC

    My solution uses the Schwartizan Transform, a good technique to get familiar with when you're doing more complicated sorting...

    First, you want to use map to turn each element of the array into an arrayref that contains the split-up line. Then, you sort based on the 1st (not 0th) element of the arrayref, which is the first field of your line. Then, you use another map to return the array element back to normal. Voila:

    #!/usr/bin/perl -w use strict; use Data::Dumper; chomp( my @array = <DATA> ); @array = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [ $_, split /!/ ] } @array; print Dumper \@array; __DATA__ 100!100!key!date!2.. 100!100!key!store! 110!110!P\d\d\d_.*\.\d{6,}\.EMRemr 120!125! 1!yeada 130!132! 140!133! 150!134! 500!blah 160!135!arbor_ama 503!blah 170!136! 505!blah 180!137! 190!138!

    "We're experiencing some Godzilla-related turbulence..."

Re: Sort based on a delimited field in array elements without affecting original array elements
by VSarkiss (Monsignor) on Mar 01, 2002 at 23:09 UTC

    Umm, in this case, you really don't have to do anything special. This worked fine for me:

    my @array = <DATA>; @array = sort { $a cmp $b } @array; print @array; __DATA__ 100!100!key!date!2.. 100!100!key!store! 110!110!P\d\d\d_.*\.\d{6,}\.EMRemr 120!125! 1!yeada 130!132! 140!133! 150!134! 500!blah 160!135!arbor_ama 503!blah 170!136! 505!blah 180!137! 190!138!
    It produces:
    1!yeada 100!100!key!date!2.. 100!100!key!store! 110!110!P\d\d\d_.*\.\d{6,}\.EMRemr 120!125! 130!132! 140!133! 150!134! 160!135!arbor_ama 170!136! 180!137! 190!138! 500!blah 503!blah 505!blah
    I don't know if it's just the sample data you're using, but from your output, you really want to sort on the whole line, lexicographically.

    HTH