NAME ==== Ranker - a module to rank a list of elements. DESCRIPTION =========== This is a module for ranking a list/array of scorable elements using various ranking strategies written in Raku. It's mostly based on [Ilya Scharrenbroich's Ruby library](https://github.com/quidproquo/ranker) by the same name. INSTALLATION ============ Either: * from CPAN: * `zef install Ranker` * from local directory: * `git clone git@gitlab.com:uzluisf/ranker.git` or [download it](https://gitlab.com/uzluisf/ranker) * `zef install path/to/ranker/` SYNOPSIS ======== Default Ranking --------------- Default ranking will assume values are numeric and rank them in their descending order. For example, a score of 100 is higher than a score of 50 and thus appears before as in `[100, 50]`. use Ranker; my @scores = 1, 1, 2, 3, 3, 1, 4, 4, 5, 6, 8, 1, 0, 8; my $rankings = Ranker::rank(@scores); put $rankings.elems; #=> 8 my $ranking = $rankings[0]; given $ranking { .rank .put; #=> 1 .score .put; #=> 8 .rankables .put; #=> [8, 8] .percentile .put; #=> 100 .z-score .put; #=> 1.83921346366645 } Custom Ranking by Block ----------------------- Custom ranking allows for ranking of objects by using an anonymous subroutine which can created with `sub`, with a pointy block or with a block. class Player { has Int $.score; } my @players = (0, 100, 1000, 25).map: Player.new(score => *); my (&score, $rankings); # Explicit: &score = -> $player { $player.score }; $rankings = Ranker::rank(@players, by => &score); # Still more explicit: &score = sub( Player:D $player ) { $player.score }; $rankings = Ranker::rank(@players, by => &score); # Same thing but more succint: $rankings = Ranker::rank(@players, by => { $^player.score }); In some cases, objects need to be ranked by score in ascending order. For instance, if you were ranking golf players. In this case, 75 is higher than a score of 100 and thus appears before as in `[75, 100]`. class GolfPlayer is Player { } my @golfplayers = (72, 100, 138, 54).map: GolfPlayer.new(score => *); my $rankings = Ranker::rank( @golfplayers, # Rankable values by => -> gp { $gp.score }, # Block to rank by :asc # Use ascending order ); Ranking Strategies ------------------ Ranker provides several ranking strategies, which are mostly based on the Wikipedia entry for [ranking](http://en.wikipedia.org/wiki/Ranking). Strategies can be passed in as an option to the `Ranker::rank` subroutine. my $rankings = Ranker::rank( @players, by => { $^p.score }, strategy => 'ordinal', ); The possible string values for the default strategies are: `'standard'`, `'modified'`, `'ordinal'`, `'dense'`, and `'fractional'`. ### **Standard Competition Ranking ("1224" ranking)** This is the default ranking strategy used by `Ranker`. For more info, see the Wikipedia entry on [Standard Competition Ranking](https://en.wikipedia.org/wiki/Ranking#Standard_competition_ranking_(%221224%22_ranking)). my $rankings = Ranker::rank( @players, by => { $^p.score }, strategy => 'standard', ); ### **Modified Competition Ranking ("1334" ranking)** For more info, see the Wikipedia entry on [Modified Competition Ranking](https://en.wikipedia.org/wiki/Ranking#Modified_competition_ranking_(%221334%22_ranking)). my $rankings = Ranker::rank( @players, by => { $^p.score }, strategy => 'modified', ); ### **Dense Ranking ("1223" ranking)** For more info, see the Wikipedia entry on [Dense Ranking](https://en.wikipedia.org/wiki/Ranking#Dense_ranking_(%221223%22_ranking) ) my $rankings = Ranker::rank( @players, by => { $^p.score }, strategy => 'dense', ); ### **Ordinal Ranking ("1234" ranking)** For more info, see the Wikipedia entry on [Ordinal Ranking](https://en.wikipedia.org/wiki/Ranking#Ordinal_ranking_(%221234%22_ranking) ). my $rankings = Ranker::rank( @players, by => { $^p.score }, strategy => 'ordinal', ); ### **Fractional Ranking ("1 2.5 2.5 4" ranking)** For more info, see the Wikipedia entry on [Fractional Ranking](https://en.wikipedia.org/wiki/Ranking#Fractional_ranking_(%221_2.5_2.5_4%22_ranking) ). my $rankings = Ranker::rank( @players, by => { $^p.score }, strategy => 'fractional', ); Custom Strategies ----------------- `Ranker` allows you to write your own strategies and supply them to the `Ranker::rank` subroutine. To do this, you must compose the `Strategy` role into a class and use [`::()`](https://docs.perl6.org/language/packages#Interpolating_into_names) to interpolate the class name into a package or variable name. `Strategy` is the role from which the default «Strategy» classes are composed. use Ranker; use Ranker::Strategies; # Composing Strategy into a class class MyCustomStrategy does Strategy { method execute { # You must implement this method ;-) } } my $rankings = Ranker::rank( @players, by => -> $p { $p.score }, strategy => ::('MyCustomStrategy') # Passing the package-interpolated class name ); By default, only the `Strategy` role is exported with `use Ranker::Strategies`, which must be composed before it's used. To export a default strategy, you must specify it with a `use` statement. For instance, `use Ranker::Strategies :dense` exports `Strategies::Dense` which can then be instantiated: use Ranker::Strategies :dense; my @scores = 44, 42, 42, 43; my $ds = Dense.new: rankables => @scores, options => { :asc }; ROUTINES ======== ### `Ranker` * `Ranker::rank(@rankables, *%options)` * `@rankables` - list of elements to be ranked * `%options` - key-value pairs to specify a custom ranking with `by` (e.g., `by => { $^value }`), a sorting order with `asc` (e.g., `asc => True`) and strategy to be used with `strategy` (e.g., `strategy => 'dense'`). By default, values are assumed to be numeric and ranked in descending order using the standard-competition ranking strategy. Thus, the following two calls achieve the same thing: * `Ranker::rank(@values);` * `Ranker::rank(@values, :by({ $^value }), :asc(False), :strategy('standard'));` ### Others Run `p6doc` on `Ranker::Ranking`, `Ranker::Rankings` and `Ranker::Strategies` to get the methods made available by `Ranking`, `Rankings` and `Strategy` respectively. AUTHOR ====== Luis F. Uceta LICENSE ======= Artistic License 2.0