File Coverage

blib/lib/Data/TableData/Rank.pm
Criterion Covered Total %
statement 60 60 100.0
branch 10 12 83.3
condition 3 6 50.0
subroutine 7 7 100.0
pod 1 1 100.0
total 81 86 94.1


line stmt bran cond sub pod time code
1             package Data::TableData::Rank;
2              
3 1     1   64355 use 5.010001;
  1         13  
4 1     1   5 use strict;
  1         2  
  1         21  
5 1     1   4 use warnings;
  1         2  
  1         101  
6              
7             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
8             our $DATE = '2022-03-27'; # DATE
9             our $DIST = 'Data-TableData-Rank'; # DIST
10             our $VERSION = '0.002'; # VERSION
11              
12 1     1   12 use Exporter qw(import);
  1         3  
  1         241  
13             our @EXPORT_OK = qw(add_rank_column_to_table);
14              
15             our %SPEC;
16              
17             $SPEC{add_rank_column_to_table} = {
18             v => 1.1,
19             summary => 'Add a rank column to a table',
20             description => <<'_',
21              
22             Will modify the table by adding a rank column. An example, with this table:
23              
24             | name | gold | silver | bronze |
25             |------------+------+--------+--------|
26             | E | 2 | 5 | 7 |
27             | A | 10 | 20 | 15 |
28             | H | 0 | 0 | 1 |
29             | B | 8 | 23 | 17 |
30             | G | 0 | 0 | 1 |
31             | J | 0 | 0 | 0 |
32             | C | 4 | 10 | 8 |
33             | D | 4 | 9 | 13 |
34             | I | 0 | 0 | 1 |
35             | F | 2 | 5 | 1 |
36              
37             the result of ranking the table with data columns of C<<
38             ["gold","silver","bronze"] >> will be:
39              
40             | name | gold | silver | bronze | rank |
41             |------------+------+--------+--------+------|
42             | A | 10 | 20 | 15 | 1 |
43             | B | 8 | 23 | 17 | 2 |
44             | C | 4 | 10 | 8 | 3 |
45             | D | 4 | 9 | 13 | 4 |
46             | E | 2 | 5 | 7 | 5 |
47             | F | 2 | 5 | 1 | 6 |
48             | G | 0 | 0 | 1 | =7 |
49             | H | 0 | 0 | 1 | =7 |
50             | I | 0 | 0 | 1 | =7 |
51             | J | 0 | 0 | 0 | 10 |
52              
53             _
54             args => {
55             table => {
56             summary => 'A table data (either aoaos, aohos, or its Data::TableData::Object wrapper)',
57             schema => 'any*',
58             req => 1,
59             },
60             data_columns => {
61             summary => 'Array of names (or indices) of columns which contain the data to be compared, which must all be numeric',
62             schema => [array => {of => 'str*', min_len=>1}],
63             req => 1,
64             },
65             smaller_wins => {
66             summary => 'Whether a smaller number in the data wins; normally a bigger name means a higher rank',
67             schema => 'bool*',
68             default => 0,
69             },
70             rank_column_name => {
71             schema => 'str*',
72             default => 'rank',
73             },
74             add_equal_prefix => {
75             schema => 'bool*',
76             default => 1,
77             },
78             rank_column_idx => {
79             schema => 'int*',
80             },
81             },
82             };
83             sub add_rank_column_to_table {
84 1     1 1 630 require Data::TableData::Object;
85              
86 1         2361 my %args = @_;
87 1         5 my $data_columns = $args{data_columns};
88 1   50     8 my $smaller_wins = $args{smaller_wins} // 0;
89 1   50     6 my $add_equal_prefix = $args{add_equal_prefix} // 1;
90 1   50     6 my $rank_column_name = $args{rank_column_name} // 'rank';
91              
92 1         9 my $td = Data::TableData::Object->new($args{table});
93 1         7075 my @colidxs = map { $td->col_idx($_) } @$data_columns;
  3         47  
94             #use DD; print "D:colidxs "; dd \@colidxs;
95              
96 1         13 my $aoaos = $td->rows_as_aoaos;
97             my $cmp_row = sub {
98 32     32   70 my ($row1, $row2) = @_;
99             #use DD; print "D:comparing: "; dd {a=>$row1, b=>$row2};
100 32         35 my $res = 0;
101 32         38 for (@colidxs) {
102 1     1   7 no warnings 'uninitialized';
  1         1  
  1         368  
103 54         66 my $cmp = $row1->[$_] <=> $row2->[$_];
104 54 50       77 $cmp = -$cmp unless $smaller_wins;
105 54 100       75 if ($cmp) { $res = $cmp; last }
  27         28  
  27         31  
106             }
107             #print "D:comparison result: $res\n";
108 32         45 $res;
109 1         119 };
110 1         5 my @sorted_indices = sort { $cmp_row->($aoaos->[$a], $aoaos->[$b]) } 0 .. $#{$aoaos};
  23         34  
  1         6  
111             #use DD; print "D:sorted_indices: "; dd \@sorted_indices;
112             #use DD; print "D:sorted table: "; dd [map {$aoaos->[$_]} @sorted_indices];
113 1         3 my @sorted_aoaos = map { $aoaos->[$_] } @sorted_indices;
  10         13  
114 1         2 my @ranks;
115             my %num_has_rank; # key=rank, val=num of rows
116 1         4 for my $rownum (0 .. $#sorted_aoaos) {
117 10 100       18 if ($rownum) {
118 9 100       16 if ($cmp_row->($sorted_aoaos[$rownum-1], $sorted_aoaos[$rownum])) {
119 7         9 my $rank = @ranks + 1;
120 7         10 push @ranks, $rank;
121 7         15 $num_has_rank{$rank}++;
122             } else {
123 2         4 push @ranks, $ranks[-1];
124 2         4 $num_has_rank{ $ranks[-1] }++;
125             }
126             } else {
127 1         2 push @ranks, 1;
128 1         4 $num_has_rank{1}++;
129             }
130             }
131              
132 1 50       4 if ($add_equal_prefix) {
133 1         3 for my $i (0..$#ranks) {
134 10 100       21 if ($num_has_rank{ $ranks[$i] } > 1) { $ranks[$i] = "=$ranks[$i]" }
  3         7  
135             }
136             }
137             #use DD; print "D:ranks: "; dd \@ranks;
138              
139             # assign the ranks to the original, unsorted rows
140 1         3 my @ranks_orig = map { undef } @ranks;
  10         12  
141 1         3 for my $i (0 .. $#sorted_indices) {
142 10         16 $ranks_orig[ $sorted_indices[$i] ] = $ranks[ $i ];
143             #use DD; dd \@ranks_orig;
144             }
145             #use DD; print "D:ranks_orig: "; dd \@ranks_orig;
146              
147 1         8 $td->add_col($rank_column_name, $args{rank_column_idx}, {}, \@ranks_orig);
148 1         96 $td;
149             }
150              
151             1;
152             # ABSTRACT: Add a rank column to a table
153              
154             __END__