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