File Coverage

blib/lib/Data/TableData/Rank.pm
Criterion Covered Total %
statement 57 57 100.0
branch 10 12 83.3
condition 3 6 50.0
subroutine 6 6 100.0
pod 1 1 100.0
total 77 82 93.9


line stmt bran cond sub pod time code
1             package Data::TableData::Rank;
2              
3 1     1   68466 use 5.010001;
  1         13  
4 1     1   5 use strict;
  1         2  
  1         21  
5 1     1   4 use warnings;
  1         2  
  1         95  
6              
7             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
8             our $DATE = '2021-11-17'; # DATE
9             our $DIST = 'Data-TableData-Rank'; # DIST
10             our $VERSION = '0.001'; # VERSION
11              
12 1     1   6 use Exporter qw(import);
  1         2  
  1         632  
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 557 require Data::TableData::Object;
85              
86 1         2623 my %args = @_;
87 1         4 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     4 my $rank_column_name = $args{rank_column_name} // 'rank';
91              
92 1         7 my $td = Data::TableData::Object->new($args{table});
93 1         7005 my @colidxs = map { $td->col_idx($_) } @$data_columns;
  3         27  
94             #use DD; print "D:colidxs "; dd \@colidxs;
95              
96 1         11 my $aoaos = $td->rows_as_aoaos;
97             my $cmp_row = sub {
98 32     32   47 my ($row1, $row2) = @_;
99             #use DD; print "D:comparing: "; dd {a=>$row1, b=>$row2};
100 32         44 my $res = 0;
101 32         46 for (@colidxs) {
102 54         91 my $cmp = $row1->[$_] <=> $row2->[$_];
103 54 50       99 $cmp = -$cmp unless $smaller_wins;
104 54 100       90 if ($cmp) { $res = $cmp; last }
  27         36  
  27         37  
105             }
106             #print "D:comparison result: $res\n";
107 32         53 $res;
108 1         180 };
109 1         3 my @sorted_indices = sort { $cmp_row->($aoaos->[$a], $aoaos->[$b]) } 0 .. $#{$aoaos};
  23         37  
  1         5  
110             #use DD; print "D:sorted_indices: "; dd \@sorted_indices;
111             #use DD; print "D:sorted table: "; dd [map {$aoaos->[$_]} @sorted_indices];
112 1         3 my @sorted_aoaos = map { $aoaos->[$_] } @sorted_indices;
  10         16  
113 1         3 my @ranks;
114             my %num_has_rank; # key=rank, val=num of rows
115 1         4 for my $rownum (0 .. $#sorted_aoaos) {
116 10 100       16 if ($rownum) {
117 9 100       20 if ($cmp_row->($sorted_aoaos[$rownum-1], $sorted_aoaos[$rownum])) {
118 7         11 my $rank = @ranks + 1;
119 7         13 push @ranks, $rank;
120 7         18 $num_has_rank{$rank}++;
121             } else {
122 2         4 push @ranks, $ranks[-1];
123 2         5 $num_has_rank{ $ranks[-1] }++;
124             }
125             } else {
126 1         2 push @ranks, 1;
127 1         3 $num_has_rank{1}++;
128             }
129             }
130              
131 1 50       3 if ($add_equal_prefix) {
132 1         2 for my $i (0..$#ranks) {
133 10 100       23 if ($num_has_rank{ $ranks[$i] } > 1) { $ranks[$i] = "=$ranks[$i]" }
  3         7  
134             }
135             }
136             #use DD; print "D:ranks: "; dd \@ranks;
137              
138             # assign the ranks to the original, unsorted rows
139 1         3 my @ranks_orig = map { undef } @ranks;
  10         15  
140 1         3 for my $i (0 .. $#sorted_indices) {
141 10         17 $ranks_orig[ $sorted_indices[$i] ] = $ranks[ $i ];
142             #use DD; dd \@ranks_orig;
143             }
144             #use DD; print "D:ranks_orig: "; dd \@ranks_orig;
145              
146 1         9 $td->add_col($rank_column_name, $args{rank_column_idx}, {}, \@ranks_orig);
147 1         96 $td;
148             }
149              
150             1;
151             # ABSTRACT: Add a rank column to a table
152              
153             __END__