Perl5 -> Perl 6: Schwartzian transform
The Schwartzian transform is a real powerful idiom that, exploiting a concatenation of
map
and sort
allows for a quick and short reordering of a complex data structure.
Before keep reading, please consider the Perl 6 operators are more efficient, lazy and smart than their Perl 5 counterparts. This means that
you could not need the Schwartzian transform at all!
Now imagine you have an hash containing the person name as key and the score of the game as value. How can you sort the hash
(keys) from the lowest to higher score? It is quite easy using the Schwartzian transform:
my %scores_of = ( luca => 10,
simon => 5,
fred => 90,
barnie => 45,
);
my @sorted_keys = map { $_->[ 1 ] }
sort { $b->[0] <=> $a->[0] }
map { [ $scores_of{ $_ }, $_ ] }
keys %scores_of;
say "Sorted keys @sorted_keys";
- extract the keys of the hash thru the
keys
operator; - pass each key as topic variable to
map
and build a single tuple as an array reference (thru[]
), having the value (i.e., the score) as elemnt 0 and the name as element 1; - pass each tuple returned as array of arrayref from
map
tosort
. Since we want to order from the greatest to the lower, i.e., inverse natural ordering, we compare$b
first against$a
. Thesort
operator will return a list of tuples (arrayref) ordered; - remap the tuples keeping only the element at index 1, that is, the hash key (i.e., the name).
- it have to be read from left to right, we are working on objects now!
- there is no need to use
[]
since comma now produces a list (andsort
know uses objects); sort
does not use anymore global variables$a
and$b
, so you have to declare them explicitly in the block signature.
my @sorted_keys = %scores_of{}:k
.map( { %scores_of{ $_ }, $_ } )
.sort( -> $a, $b { $b[ 0 ] <=> $a[ 0 ] } )
.map: { $_[ 1 ] };
@sorted_keys.say;
- extract the keys thru the
:k
adverb on the whole hash slice; map
the keys as a list of tuples as in the above (note the square brackets are not mandatory here);sort
the keys picking them a couple at a time and assigning to$a, $b
;map
the result keeping only the field at index 1 (note that here there’s no array ref but just a single array).