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

i have an array of hashrefs to sort by one of the values. sounds simple, but i need to do a 2-level sort on the contents of a string ...

it sounds like a job for the Schwartzian Transform, but i'm at a loss on how to build the temp array/hash then reconstruct the original.

the values i need to sort on look like this:

size: L, color: White size: M, color: Orange size: M, color: White size: M, color: White size: S, color: Orange size: S, color: White size: Xl, color: Orange size: Xl, color: White size: Xl, color: White
I need to sort the array of hashrefs by color, then by size, but since (obviously) S should come before M ... an alpha or ascii sort won't work. i *think* i need a mapping hash also.

i have the regex to get the values out of the string ... but now, how to apply that into a Schwartzian and reconstruct?

my $string = $element->{description}; $string =~ m!\w+:\s(\w+),\s\w+:\s(\w+)!;
where $2 would be the first sort criteria, $1 the second ... so that i end up with ordering like:
size: S, color: Orange size: S, color: White size: M, color: Orange size: M, color: White size: M, color: White size: L, color: White size: Xl, color: Orange size: Xl, color: White

Replies are listed 'Best First'.
Re: complicated sorting issue
by dragonchild (Archbishop) on Apr 09, 2004 at 19:42 UTC
    my @sorted_values = map { $_->[0] } sort { $a->[2] cmp $b->[2] || $a->[1] cmp $b->[1] } map { my ($x, $y) = /\w+:\s(\w+),\s\w+:\s(\w+)/; [ $_, $x, $y ] } @strings;

    Untested, but should work.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

      it's *close*, but the  || in the sort doesn't quite do it.

      thanks, seriously. i couldn't get my head around the right-most map ...

        Oh. You need to sort by value instead of asciibetically. I hate those sorts. You have to create a hash for lookup. Something like:
        my %lookup = do { my $i = 1; map { $_ => $i++ } qw( S M L XL ); }; my @sorted_values = map { $_->[0] } sort { $a->[2] cmp $b->[2] || $lookup{$a->[1]} <=> $lookup{$b->[1]} } map { my ($x, $y) = /\w+:\s(\w+),\s\w+:\s(\w+)/; [ $_, $x, $y ] } @strings;

        ------
        We are the carpenters and bricklayers of the Information Age.

        Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

Re: complicated sorting issue
by sgifford (Prior) on Apr 09, 2004 at 19:50 UTC
    No Schwartzian transorm, but this seems to do it:
    #!/usr/bin/perl -w use strict; # Build a utility array for sorting by size our @sizes=qw(s m l xl); our %sizeval; { my $i = 0; $sizeval{$_}=$i++ foreach @sizes; } # Build the array to test my @arr; while (<DATA>) { chomp; push(@arr,{ sortval => $_ }); } # Now sort the results and print them foreach (sort bysizecolor @arr) { print $_->{sortval},"\n"; } # Sort hashrefs by size and color sub bysizecolor { my ($a_size,$a_color) = getsizecolor($a->{sortval}) or warn("Couldn't parse '$a'"),return 0; my ($b_size,$b_color) = getsizecolor($b->{sortval}) or warn("Couldn't parse '$b'"), return 0; return bysize($a_size, $b_size) || bycolor($a_color, $b_color); } # Compare two sizes sub bysize { my($a,$b)=@_; $sizeval{lc $a} <=> $sizeval{lc $b}; } # Compare two colors sub bycolor { my($a,$b)=@_; lc $a cmp lc $b; } # Extract the size and color from a string sub getsizecolor { return ($_[0] =~ m!\w+:\s(\w+),\s\w+:\s(\w+)!); } __DATA__ size: L, color: White size: M, color: Orange size: M, color: White size: M, color: White size: S, color: Orange size: S, color: White size: Xl, color: Orange size: Xl, color: White size: Xl, color: White
Re: complicated sorting issue
by Anonymous Monk on Apr 09, 2004 at 19:56 UTC
    #!c:/perl/bib/perl -w $|++; use strict; my %map = ( S => 0, M => 1, L => 2, Xl => 3 ); my @sorted = map { $_->[0] } sort { ( $map{$a->[1]} <=> $map{$b->[1]} ) || ( $a->[2] cmp $b->[2] ) } map { chomp; /^\w+:\s+(\w+),\s+\w+:\s+(\w+)$/; [$_, $1, $2] } <DATA +>; print join("\n", @sorted); __DATA__ size: L, color: White size: M, color: Orange size: M, color: White size: M, color: White size: S, color: Orange size: S, color: White size: Xl, color: Orange size: Xl, color: White size: Xl, color: White
      I'm amused that you do the mapping in the sort block, instead of properly computing the value once. Perhaps you meant:
      use strict; my %map = ( S => 0, M => 1, L => 2, Xl => 3 ); my @sorted = map { $_->[0] } sort { $a->[1] <=> $b->[1] or $a->[2] cmp $b->[2] } map { /^(\w+:\s+(\w+),\s+\w+:\s+(\w+))$/ ? [$1, $map{$2}, $3] : () +} <DATA>; print join("\n", @sorted); __DATA__ size: L, color: White size: M, color: Orange size: M, color: White size: M, color: White size: S, color: Orange size: S, color: White size: Xl, color: Orange size: Xl, color: White size: Xl, color: White

      -- Randal L. Schwartz, Perl hacker
      Be sure to read my standard disclaimer if this is a reply.

        Oops, nice catch, though I would expdect you to spot such an error :). I can only say that mine does work, though with a rather less competitive performance. It's just like me to take something as dandy as the Schwartzian Transform and use it wrongly. Thanks for the correction, it will never happen again, I promise. ++ for the removal of chomp and the additional of the extra pair of capturing parentheses. Why didn't I think of that?

      this was perfect! thanks.

        I'm not sure if you saw merlyn's correction to my mistake, so if not, please use this code instead:

        use strict; my %map = ( S => 0, M => 1, L => 2, Xl => 3 ); my @sorted = map { $_->[0] } sort { $a->[1] <=> $b->[1] or $a->[2] cmp $b->[2] } map { /^(\w+:\s+(\w+),\s+\w+:\s+(\w+))$/ ? [$1, $map{$2}, $3] : () } <DATA>; print join("\n", @sorted); __DATA__ size: L, color: White size: M, color: Orange size: M, color: White size: M, color: White size: S, color: Orange size: S, color: White size: Xl, color: Orange size: Xl, color: White size: Xl, color: White