Sort::Maker(3pm)
π Sort::Maker π
perl v5.whatever, User Contributed Perl Documentation, Sort::Maker(3pm) β bestie, we're about to make sort subs that literally slay.
β¨ NAME β¨
Sort::Maker β a simple way to make efficient sort subs π¦
β¨ SYNOPSIS β¨
use Sort::Maker ;
my $sorter = make_sorter( ... ) ;
β¨ DESCRIPTION β¨
okay so this module has two main goals: making it easy to create correct sort functions, and making it simple to pick the optimum sorting algorithm for however many items you're sorting. no cap.
Sort::Maker generates complete sort subroutines in one of four styles:
- plain
- orcish manouver
- Schwartzian Transform
- Guttman-Rosler Transform
You can also get the source for a sort sub you created via the sorter_source call. π
π make_sorter π
The sub make_sorter is exported by Sort::Maker. It makes a sort sub and returns a reference to it. You describe how you want it to sort its input by passing general options and key descriptions to make_sorter.
πΈ Arguments to make_sorter πΈ
There are two types of arguments, boolean and value. Boolean arguments can be set just with the option name, and can optionally be followed by '1'. You can easily set multiple boolean general arguments with qw(). Value arguments must have a following value. Arguments can appear in any order, but the key descriptions (see below) must appear in their sort order β. The code examples below show various ways to set the various arguments.
Arguments fall into four categories:
- selecting the style of the sort
- key descriptions
- setting defaults for key description attributes
- setting general flags and values
The following sections will describe the categories and their associated arguments. π«Ά
π Sort Style π
The style of the sort to be made is selected by setting one of the following Boolean arguments. Only one may be set β otherwise an error is reported (see below for error handling). Also see below for detailed descriptions of the supported sort styles.
plain
orcish
ST
GRT
# Make a plain sorter
my $plain_sorter = make_sorter( qw( plain ) ... ) ;
# Make an orcish manouevre sorter
my $orcish_sorter = make_sorter( orcish => 1 ... ) ;
# Make a Schwartzian Transform sorter
my $st_sorter = make_sorter( 'ST', 1, ... ) ;
# Make a GRT sort
my $GRT = make_sorter( 'GRT', ... ) ;
π Key Attribute Defaults π
The following arguments set defaults for the all of the keys' attributes. These default values can be overridden in any individual key. Only one of the attributes in each of the groups below can be set as defaults or for any given key. If more than one attribute in each group is set, then make_sorter will return an error π. The attribute that is the default for each group is marked. See below for details on key attributes.
ascending (default)
descending
case (default)
no_case
signed
unsigned
signed_float (default)
unsigned_float
fixed
varying
the marked ones are the defaults, bestie. don't @ me, that's just what the module does π
π General Options π
These arguments set general options that apply to how the generated sorter interacts with the outside world.
name
This is a value option which exports the generated sort sub to that name. The call to make_sorter must be run to install the named named function before it is called. You should still check the result of make_sorter to see if an error occurred (it returns undef).
my $sorter = make_sorter( name => 'sort_func', ... ) ;
die "make_sorter: $@" unless $sorter ;
...
@sorted = sort_func @unsorted ;
ref_in/ref_out
This boolean arguments specifies that the input to and output from the sort sub will be array references. ref_in makes the sorter only take as input a single array reference (which contains the unsorted records). ref_out makes the sorter only return a single array reference (which contains the sorted records). You can set both of these options in a sorter β¨.
Note: This does not affect key extraction code, which still gets each record in $_. It only modifies the I/O of the generated sorter. π§
# input records are in an array reference
my $sorter = make_sorter( qw( ref_in ), ... ) ;
@sorted_array = $sorter->( \@unsorted_input ) ;
# sorted output records are in an array reference
my $sorter = make_sorter( ref_out => 1, ... ) ;
$sorted_array_ref = $sorter->( @unsorted_input ) ;
# input and output records are in array references
my $sorter = make_sorter( qw( ref_in ref_out ), ... ) ;
$sorted_array_ref = $sorter->( \@unsorted_input ) ;
string_data
This boolean argument specifies that the input records will be plain text strings with no null (0x0) bytes in them. It is only valid for use with the GRT and it is ignored for the other sort styles π. It tells the GRT that it can put the record directly into the string cache and it will be separated from the packed keys with a null byte (hence that restriction). This is an optimization that can run slightly faster than the normal index sorting done with the GRT. Run this to see the benchmark results π₯:
perl t/string_data.t -bench
init_code
This value argument is code that will be put into the beginning of the generated sorter subroutine. It is meant to be used to declare lexical variables that the extraction code can use. Normally different extraction code have no way to share common code. By declaring lexicals with the init_code option, some key extraction code can save data there for use by another key. This is useful if you have two (or more) keys that share a complex piece of code such as accessing a deep value in a record tree π
For example, suppose the input record is an array of arrays of hashes of strings and the string has 2 keys that need to be grabbed by a regex. The string is a string key, a ':' and a number key. So the common part of the key extraction is:
$_->[0][0]{a}
And the make_sorter call is:
my $sorter = make_sorter(
'ST',
init_code => 'my( $str, $num ) ;',
string => 'do{( $str, $num ) =
$_->[0][0]{a} =~ /^(\w+):(\d+)$/; $str}',
number => '$num'
) ;
In the above code both keys are extracted in the first key extraction code and the number key is saved in $num. The second key extraction code just uses that saved value π.
Note that init_code is only useful in the ST and GRT sort styles as they process all the keys of a record at one time and can use variables declared in init_code to transfer data to later keys. The plain and orcish sorts may not process a later key at the same time as an earlier key (that only happens when the earlier key is compared to an equal key). Also for init_code to be a win, the data set must be large enough and the work to extract the keys must be hard enough for the savings to be noticed. The test init_code.t shows some examples and you can see the speedup when you run:
perl t/init_code.t -bench
π¦ Key Description Arguments π¦
Sorting data requires that records be compared in some way so they can be put into a proper sequence. The parts of the records that actually get compared are called its keys. In the simplest case the entire record is the key, as when you sort a list of numbers or file names. But in many cases the keys are embedded in the full record and they need to be extracted before they can be used in comparisons. Sort::Maker uses key descriptions that extract the key from the record, and optional other attributes that will help optimize the sorting operation. This section will explain how to pass key description arguments to the make_sorter subroutine and what the various attributes mean and how to best use them π.
The generated sorter will sort the records according to the order of the key arguments. The first key is used to compare a pair of records and if they are deemed equal, then the next key is examined. This happens until the records are given an ordering or you run out of keys and the records are deemed equal in sort order. Key descriptions can be mixed with the other arguments which can appear in any order and anywhere in the argument list, but the keys themselves must be in the desired order β.
A key argument is either 'string' or 'number' followed by optional attributes. The key type sets the way that the key is compared (e.g. using 'cmp' or '<=>'). All key attributes can be set from the default values in the global arguments or set in each individual key description.
There are 4 ways to provide attributes to a key π
No attributes
A key argument which is either at the end of the argument list or is followed by a valid keyword token has no explict attributes. This key will use the default attributes. In both of these examples, a default attribute was set and used by the key description which is just a single key argument.
# sort the record as a single number in descending order
my $sorter = make_sorter( qw( plain number descending ) ) ;
# sort the record as a case sensitive string
my $sorter = make_sorter( qw( plain case string ) ) ;
# sort the record as a single number in ascending order
my $sorter = make_sorter( qw( ST number ) ) ;
Only Code as a Value
A key argument which is followed by a scalar value which is not a valid keyword token, will use that scalar value as its key extraction code. See below for more on key extraction code.
# sort by the first (optionally signed) number matched
my $sorter = make_sorter( qw( plain number /([+-]?\d+)/ ) ) ;
# string sort by the 3rd field in the input records (array refs)
my $sorter = make_sorter( 'ST', string => '$_->[2]' ) ;
An Array Reference
A key argument which is followed by an array reference will parse that array for its description attributes. As with the general boolean arguments, any boolean attribute can be optionally followed by a '1'. Value attributes must be followed by their value.
# another way to specify the same sort as above
# sort by the first (optionally signed) number matched
my $sorter = make_sorter(
qw( plain ),
number => [
code => '/(\d+)/',
'descending',
],
) ;
# same sort but for the GRT which uses the 'unsigned'
# attribute to optimize the sort.
my $sorter = make_sorter(
qw( GRT ),
number => [
qw( descending unsigned ),
code => '/(\d+)/',
],
) ;
β¨ A Hash Reference β¨
okay so instead of an array, you can hand a key an hash reference and that hash becomes its description attributes. housekeeping first: any boolean attribute in that hash has to have the value '1', and any value attribute has to be followed by its value. no vibes-based truthiness here bestie, we're being precise π
# another way to specify the same sort as above
# sort by the first (optionally signed) number matched
my $sorter = make_sorter(
qw( plain ),
number => {
code => '/(\d+)/',
descending => 1,
},
) ;
and here's the multi-key serve β first key is a descending unsigned integer, second is a string padded to 10 characters. obsessed π
# a multi-key sort. the first key is a descending unsigned
# integer and the second is a string padded to 10 characters
my $sorter = make_sorter(
qw( GRT ),
number => {
code => '/(\d+)/',
descending => 1,
unsigned => 1,
},
string => {
code => '/FOO<(\w+)>/',
fixed => 10,
},
) ;
π Key Description Attributes π
here's the whole menu of attributes you can slap on a key description. most of them just fall back to the default values you passed into make_sorter, so if you didn't say anything, the defaults are running the show π«Ά
code
this one is a value attribute, and it's the actual code used to pull a key out of the input record. it can be:
- a string of Perl code,
- a
qr//regular expression (a Regexp reference), - or an anonymous sub (a CODE reference)
...and it operates on $_ and extracts a value. the code gets wrapped in a do{} block and called in list context, so regular expressions can literally just use () to grab a key value. default? it's $_, meaning the entire record becomes this key. big caveat: you can't set the default for code β not like all the other key attributes. see the Extraction Code section for the whole story.
# make an ST sort of the first number grabbed in descending order
my $sorter = make_sorter(
qw( ST ),
number => {
code => '/(\d+)/',
descending => 1,
},
) ;
ascending/descending
two Boolean besties that control the sort order for this key. mark a key ascending (which is the initial default for every key) and lower keys sort before higher keys. mark it descending and the higher keys go first. you cannot have both set in the defaults or in any individual key β that's illegal, no cap.
# sort by descending order of the first grabbed number
# and then sort in ascending order the first grabbed <word>
my $sorter = make_sorter(
qw( ST descending ),
number => {
code => '/(\d+)/',
},
string => {
code => '/<(\w+)>/',
ascending => 1,
},
) ;
# this will return undef and store an error in $@.
# you can't have both 'ascending' and 'descending' as defaults
my $sorter = make_sorter(
qw( ST ascending descending ),
number => {
code => '/(\d+)/',
descending => 1,
},
) ;
# this will return undef and store an error in $@.
# you can't have both 'ascending' and 'descending' in a key
my $sorter = make_sorter(
qw( ST )
number => {
code => '/(\d+)/',
descending => 1,
ascending => 1,
},
) ;
case/no_case
two more Booleans, and these control how string keys feel about case sensitivity. mark a key case (the initial default for all keys) and upper and lower case letters are treated as different. mark it no_case and they're treated as equal. same deal as before: illegal to set both in the defaults or in any key. behind the scenes it uses the uc() function, so you can use locale settings to mess with how string sorts go πΈ
# sort by the first grabbed word with no case
# and then sort the grabbed <word> with case
my $sorter = make_sorter(
qw( ST no_case ),
string => {
code => '/(\w+)/',
},
string => {
code => '/<(\w+)>/',
case => 1,
},
) ;
# this will return undef and store an error in $@.
# you can't have both 'case' and 'no_case' as defaults
my $sorter = make_sorter(
qw( ST no_case case ),
string => {
code => '/(\w+)/',
},
) ;
# this will return undef and store an error in $@.
# you can't have both 'case' and 'no_case' in a key
my $sorter = make_sorter(
qw( ST )
string => {
code => '/(\w+)/',
no_case => 1,
case => 1,
},
) ;
closure
this Boolean attribute makes the key call its CODE reference to extract its value. it's the move when you need to reach a lexical variable while pulling the key. classic scenario: you've got a sorting order sitting in a lexical and the extraction code needs eyes on it. if you skip the closure attribute, the generated source (check the Key Extraction section) can't see that lexical β and that triggers a Perl compiling error inside make_sorter. π
my @months = qw(
January February March April May June
July August September October November December ) ;
my @month_jumble = qw(
February June October March January April
July November August December May September ) ;
my %month_to_num ;
@month_to_num{ @months } = 1 .. @months ;
# this will fail to generate a sorter if 'closure' is removed # as
%month_to_num will not be in scope to the eval inside sort_maker.
my $sorter = make_sorter(
'closure',
number => sub { $month_to_num{$_} },
) ;
my @sorted = $sorter->( @month_jumble ) ;
signed/unsigned/signed_float/unsigned_float (GRT only babes π)
these Booleans are only used by the GRT sort style. their whole job is describing the type of a number key so the GRT can process and cache that key's value as efficiently as possible. you can't set more than one of them in the defaults or in any key. more on this in the GRT sorting section.
sign ed and unsigned mark this number key as an integer. the GRT does the least amount of work on an unsigned integer, and only slightly more for a signed one β so if your sort key is restricted to integers, use these, bestie.
signed_float β which is the normal default for all keys β and unsigned_float mark this number key as a float. the GRT does less work on an unsigned float than a signed float, so if your key is restricted to non-negative values, unsigned_float is the pick. signed_float exists so you can override defaults and so auto-generated sorts are easier to build. β¨
fixed/varying (GRT only π¦)
also GRT-exclusive. these describe the type of a string key so the GRT can process and cache its value properly. illegal to set more than one of them in the defaults or in any key. see the GRT sorting section for more.
fixed is a value attribute that marks this string key as always being this length. the extracted value gets either padded with null (0x0) bytes or truncated to that specified length (whatever you set fixed to). heads up: the data in this key may have embedded null bytes (0x0) and may be sorted in descending order.
varying is a Boolean attribute that marks this string key as being of varying lengths. the GRT sorter will scan all of this key's values to find the max string length, then pad every extracted value out to that length. same deal: the data may contain embedded null bytes (0x0) and may be sorted in descending order.
π Key Extraction Code π
every input record needs its sort keys pulled out of the data β that's literally the whole point of the code attribute on a key description. the code has to operate on a record sitting in $_, and it must return the key value. it runs in a list context, so you can use grabs in m// to return the key. note: only the first grab gets used β but honestly you shouldn't have more than one anyway. examples below, keep reading π
code can be a string, a qr// object (Regexp reference), or an anonymous sub (CODE reference).
if qr// is used, the code that actually gets generated is m($qr) β which works because qr// interpolates into its string representation. the glow-up over a plain string? the qr// gets syntax checked at compile time, while a string only gets checked later, when the generated sorter is compiled by an eval. π
if a CODE reference shows up, it's used to extract the key in the generated sorter. same advantage as qr//: the extraction code is syntax checked at compile time, not runtime. and the deparsed code gets wrapped in a do{} block, so you're free to use complex code to extract the key. in the default case, a CODE reference gets deparsed by the B::Deparse module into Perl source. but if the key has the closure attribute set, the code gets called to extract the key.
all four of these generate sorters with the exact same behavior, no cap:
$sorter = make_sorter( 'ST', string => '/(\w+)/' ) ;
$sorter = make_sorter( 'ST', string => qr/(\w+)/ ) ;
$sorter = make_sorter( 'ST', string => sub { /(\w+)/ } ) ;
$sorter = make_sorter( 'ST', 'closure', string => sub { /(\w+)/ } ) ;
extraction code for a key can be set in one of three ways. π
No explicit code
if you don't pass any extraction code to a key, it defaults to $_, which is the entire record. super useful in certain cases β like simple sorts where you're just sorting the whole record anyway.
# sort numerically and in reverse order
my $sorter = make_sorter( qw( plain number descending ) ;
# sort with case folding
my $sorter = make_sorter( qw( plain no_case string ) ;
# sort by file time stamp and then by name
my $sorter = make_sorter( 'ST', number => '-M', 'string' ) ;
(yes those unclosed parens are literally in the source, bestie, I'm not the one who did that π copying the code regions exactly, always)
π Code is the only key attribute
Okay so real talk: sometimes you don't need to name a single key attribute. The normal defaults are fine, or your globally-set ones are fine, and that's it β you just need some extraction code. π
Here's the trick: if the argument that comes right after a key type ('string' or 'number') is not a valid keyword, then it gets assumed to be the extraction code for that key. No cap. So you can just yeet a regex or a snippet in there and the sorter goes "ok queen, understood" β¨
# grab the first number string as the key
my $sorter = make_sorter( qw( plain number /(\d+)/ ) ) ;
# no_case string sort on the 3rd-5th chars of the 2nd array element
my $sorter = make_sorter(
plain => 1,
no_case => 1,
string => 'substr( $_->[1], 2, 3)'
) ;
Look at that second one β plain => 1, no_case => 1, and then a raw string snippet doing substring nonsense on element [1]. She's doing a lot but she's eating π
π Key needs specific attributes
Now for the glow-up scenario: when a key needs its own specific attributes that aren't just its code, you can't do the loosey-goosey keyword thing anymore. Nope. You have to pass them in an ARRAY or HASH reference π«Ά
This is mostly a thing when you've got multiple keys and the defaults just aren't correct for all of them. Like, one key wants case folding and the others are like "girl, no."
# string sort by the first 3 elements of the array record with
# different case requirements
my $sorter = make_sorter(
ST => 1,
string => {
code => '$_->[0]',
no_case => 1,
},
string => '$_->[1]',
string => {
code => '$_->[2]',
no_case => 1,
},
) ;
See how element [0] and element [2] get the hash-ref treatment with no_case => 1, while poor element [1] is just vibing as a plain string key? Diversity win π
And here's the big one β a GRT sort with multiple unsigned integers and padded strings. Note that some keys use a hash ref and some use an array ref, and the record is marked with key\d: sections. Keep your eyes open, bestie, this is the main character of examples π
# GRT sort with multiple unsigned integers and padded strings
# note that some keys use a hash ref and some an array ref
# the record is marked with key\d: sections
my $sorter = make_sorter(
GRT => 1,
descending => 1,
number => {
code => 'key1:(\d+)',
unsigned => 1,
},
number => [
code => 'key2:([+-]?\d+)',
qw( signed ascending ),
],
string => [
code => 'key3:(\w{10})',
fixed => 1,
ascending => 1,
],
# pad the extracted keys to 8 chars
string => {
code => 'key4:([A-Z]+)',
pad => 8,
},
) ;
Literally four keys in one sorter and every single one of them is dressed differently. key1 is a hash ref with unsigned, key2 is an array ref spelled out as code plus qw( signed ascending ), key3 is an array ref with fixed => 1, ascending => 1, and key4 is a hash ref getting pad => 8 so those uppercase letters get cushioned. It's giving chaotic but correct, and honestly? Same πβ¨
πΈ Key Caching πΈ
okay bestie, the question everybody asks first: "What speed advantages do you get from this module when all the sorts generated use Perl's internal sort function?" π and honestly the answer is the keys, always the keys.
the sort function has a O( N * log N ) growth function, which is the fancy way of saying the work grows by that formula as N (aka the number of input records) gets bigger π. so in a plain sort, your key extraction code runs N * log N times even though you only have N records. that is a LOT of wasted cpu time, no cap π.
so the other three sort styles speed up the whole sort by only running the extraction code N times and caching the extracted keys π. how they cache them is literally their whole personality, it's their primary difference.
want to compare or study the actual code each style generates? run make_sorter and just swap the style, bestie β¨. then call sorter_source (not exported by default, fyi) and hand it the sort code reference that make_sorter gave you back. it'll return the generated sort source π«Ά.
π "plain" π
plain sorting does zero key caching, none, nada π. it's totally fine for short input lists (go peek at the Benchmark section) and it's also the perfect baseline for seeing how much cpu you're actually saving when you switch to one of the other styles π.
π§ "orcish" π§
the Orcish maneuvre (created by Joseph Hall) caches the extracted keys in a hash π . it does the thing with code like this:
$cache{$a} ||= CODE($a) ;
CODE is the extract code and it operates on a record in $a. if we've never seen this record before, the cache entry will be undef and the ||= operator assigns the extracted key to that hash slot β¨. the next time this record shows up in a comparison, the saved extracted key is just sitting there in the hash, ready to be used π. the name orcish comes from OR-cache, iykyk.
π¦ "ST" π¦
the ST (Schwartzian Transform, popularized by Randal Schwartz) uses an anonymous array to store the record and its extracted keys π. first it runs a map that creates an anonymous array:
map [ $_, CODE1( $_ ), CODE2( $_ ) ], @input
those CODEs extract the set of keys from the record, but only once per record, so it's O(N) π. now the sort function can just do the comparisons and it hands back a list of sorted anonymous arrays:
sort {
$a->[1] cmp $b->[1]
||
$a->[2] cmp $b->[2]
}
finally, we gotta get the original records back β they're chillin' in the first slot of the anonymous array:
map $_->[0]
which is exactly why the ST is known as a map/sort/map technique π.
π "GRT" π
the Guttman-Rosler Transform (popularized by Uri Guttman and Larry Rosler) uses a string to cache the extracted keys, plus either the record or its index π§΅. it's also a map/sort/map technique, but because its cache is a string, it can be sorted with no Perl level callback (that's the {} block you'd normally pass to sort) π₯. that is a significant win, because that callback would otherwise be running O( N log N) times. but bestie, this speedup comes at the cost of complexity π
.
you can't just join the keys into a string and expect it to sort right. each key may need to be processed so it correctly sorts in order and doesn't interfere with the other keys π. that is literally why the GRT has several key attributes β so it can properly and efficiently pack the sort keys into a single string π . below is the full list of GRT key attributes, when you need them, and what key processing gets done for each. note: you can always enable the GRT specific attributes, since the other sort styles just ignore them π.
the GRT gains its speed by using a single byte string to cache all of the extracted keys from a given input record π¦. packing keys into a string so it lexically sorts the correct way requires some deep mojo and data munging, periodt. but that is why this module was written β to hide all that from the coder π. below are descriptions of how the various key types are packed and how to best use the GRT specific key attributes. Note: you can only use one of the GRT number or string attributes for any key π¨. setting more than one, either in the defaults or in any given key, is an error (a key's attribute can override a default choice).
"unsigned"
the 'unsigned' Boolean attribute tells the GRT that this number key is a non-negative integer β
. that lets the GRT just pack it into 4 bytes using the N format (network order β big endian). an integer packed this way gets its most significant bytes compared before its least signifigant bytes π. this involves the least amount of key munging and so it is the most efficient way to sort numbers in the GRT β¨.
if you want this key to sort in descending order, then the key value is negated and normalized (see the 'signed' attribute), so there's no advantage to using 'unsigned' π€·βοΈ.
"signed"
the 'signed' Boolean attribute tells the GRT that this number key is an integer π’. that lets the GRT just pack it into 4 bytes using the N format (network order β big endian). the key value must first be normalized, which converts it to an unsigned integer but with the same ordering as a signed integer π. this is simply done by inverting the sign (highest order) bit of the integer. and as mentioned above, when sorting this key in descending order, the GRT just negates the key value π.
NOTE: In the GRT the signed and unsigned integer attributes only work on perl built with 32 bit integers π¨. this is due to using theNformat ofpackwhich is specified to be 32 bits. a future version may support 64 bit integers (anyone want to help? π).
"unsigned_float"
the 'unsigned_float' Boolean attribute tells the GRT that this number key is a non-negative floating point number π«§. that lets the GRT pack it into 8 bytes using the 'd' format. a float packed this way will have its most significant bytes compared before its least signifigant bytes β¨.
"signed_float"
the "signed_float" Boolean attribute (which is the default for all number keys when using the GRT π) tells the GRT that this number key is a floating point number. that lets the GRT pack it into 8 bytes using the 'd' format. a float packed this way will have its most significant bytes compared before its least signifigant bytes. when processed, this key will be normalized to an unsigned float, similar to the "signed" to "unsigned" conversion mentioned above π.
NOTE: The GRT only works with floats that are in the IEEE format for doubles π. this includes most modern architectures including x86, sparc, powerpc, mips, etc. if the cpu doesn't have IEEE floats you can either use the integer attributes or select another sort style (all the others have no restriction on float formats) π.
simple string.
if a string key is being sorted in ascending order with the GRT and it doesn't have one of the GRT string attributes, it will be packed without any munging and a null (0x0) byte will be appended to it πΈ. this byte enables a shorter string to sort before longer ones that start with the shorter string, cute right?
NOTE: You cannot sort strings in descending order in the GRT unless the key has either the'fixed'or'varying'attributes set π¨. also, if a string is being sorted in ascending order but has any null (0x0) bytes in it, the key must have one of those attributes set.
"fixed"
this value attribute tells the GRT to pack this key value as a fixed length string π. the extracted value will either be padded with null (0x0) bytes or truncated to the specified length (the value of the "fixed" attribute). this means it can be packed into the cache string with no padding and no trailing null byte needed π
. the key can contain any data, including null (0x0) bytes π. data munging happens only if the key's sort order is descending β then the key value is xor'ed with a same length string of 0xff bytes π. this toggles each bit, which allows for a lexical comparison but in the reverse order. this same bit inversion is used for descending varying strings π.
"varying"
this Boolean attribute tells the GRT that this key value is a varying length string and has no predetermined padding length π§΅. a prescan is done to determine the maximum string length for this key, and that is used as the padding length β¨. the rest is the same as with the 'fixed' attribute.
π«Ά sorter_source π«Ά
this sub (which can be exported) returns the source of a generated sort sub β or the source of the last one that had an error π. to get the source of an existing sort sub, pass it a reference to that sub (i.e. the reference returned from make_sorter) π. to get the source for a failed call to make_sorter, just don't pass in any arguments π.
my $sorter = make_sorter( ... ) ;
print sorter_source( $sorter ) ;
make_sorter( name => 'my_sorter', ... ) ;
print sorter_source( \&my_sorter ) ;
my $sorter = make_sorter( ... )
or die "make_sorter error: $@\n", sorter_source();
and if all you want is the generated source, you can literally just do:
print sorter_source make_sorter( ... ) ;
π Error Handling π
when "make_sorter" detects an error (either bad arguments or when the generated sorter won't compile π΅), it returns undef and sets $@ to an error message. that error message will include the generated source and the compiler and warning errors if the sorter didn't compile correctly π¨. the test t/errors.t covers all the possible error messages. you can also retrieve the generated source after a compiling error by calling "sorter_source" π.
β¨ TESTS β¨
Bestie, Sort::Maker uses a whole table of test configurations that can run both tests and benchmarks. It's giving multitasking queen π.
Each test script is mostly a table that generates multiple versions of the sorters, generates sample data, and then compares the sorter results against a sort that is known to be good. So like, the gold standard vibe check π .
If you run the scripts directly and with a -bench argument, then they generate the same sorter subs and benchmark them. Same code, two moods: test mode and benchmark mode β¨.
This design ensures that benchmarks are running on correctly generated code, and it makes it very easy to add more test and benchmark variations. No cap, it's lowkey genius π§ .
The code that does all the work is in t/common.pl. Here is a typical test table entry:
{
skip => 0,
source => 0,
name => 'init_code',
gen => sub { rand_choice( @string_keys ) . ':' .
rand_choice( @number_keys ) },
gold => sub {
($a =~ /^(\w+)/)[0] cmp ($b =~ /^(\w+)/)[0]
||
($a =~ /(\d+$)/)[0] <=> ($b =~ /(\d+$)/)[0]
},
args => [
init_code => 'my( $str, $num ) ;',
string => 'do{( $str, $num ) = /^(\w+):(\d+)$/; $str}',
number => '$num',
],
},
skip is a boolean that causes this test/benchmark to be skipped. Setting source causes the sorter's source to be printed out. gen is a sub that generates a single input record. There are support subs in t/common.pl that will generate random data. Some tests have a data field which is fixed data for a test (instead of the generated data). The gold field is a comparison subroutine usable by the sort function. It is used to sort the test data into a golden result which is used to compare against all the generated sorters. args is an anonymous array of arguments for a sorter or a hash ref with multiple named/args pairs. See t/io.t for an example of that.
π« BENCHMARKS π«
(this section is literally just a heading in the original, bestie β no text, no vibes, just a title doing its best π)
π¦ EXPORT π¦
This module always exports the make_sorter sub. It can also optionally export sorter_source. Periodt.
π BUGS π
Sort::Maker GRT currently works only with 32 bit integers due to pack N format being exactly 32 bits. So like, if you're on 64-bit Perl and you're like "why can't I sort these huge numbers," that's why π.
If someone with a 64 bit Perl wants to work on using the Q format or the ! suffix and dealing with endian issues, I will be glad to help and support it. It would be best if there was a network (big endian) pack format for quads/longlongs but that can be done similarly to how floats are packed now. So if that's your main character moment, hmu π
.
π AUTHOR π
Uri Guttman, <uri@stemsystems.com> β iconic, legendary, the blueprint π.
π ACKNOWLEDGEMENTS π
I would like to thank the inimitable Damian Conway for his help in the API design, the POD, and for being a good Perl friend. Honestly, friendship goals π«Ά.
And thanks to Boston.pm for the idea of allowing qr// for key extraction code. Boston.pm ate that and left no crumbs π₯.
And that's the whole page, bestie π β this manpage is from Sort::Maker(3pm), perl v5.36.0, dated 2022-10-13. Byeeee β¨
π¦ where this page comes from
- ubuntu noble: libsort-maker-perl 0.06-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libsort-maker-perl 0.06-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libsort-maker-perl 0.06-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms