File Coverage

blib/lib/Algorithm/BestChoice.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Algorithm::BestChoice;
2              
3 3     3   213026 use warnings;
  3         9  
  3         98  
4 3     3   17 use strict;
  3         7  
  3         229  
5              
6             =head1 NAME
7              
8             Algorithm::BestChoice - Choose the best
9              
10             =head1 VERSION
11              
12             Version 0.01
13              
14             =cut
15              
16             our $VERSION = '0.01';
17              
18             =head1 SYNOPSIS
19              
20             # Find my favorite food based on color
21             my $chooser = Algorithm::BestChoice->new;
22             $chooser->add( match => red, value => cherry, rank => 1 )
23             $chooser->add( match => red, value => apple, rank => 10 ) # Like apples
24             $chooser->add( match => red, value => strawberry, rank => -5 ) # Don't like strawberries
25             $chooser->add( match => purple, value => grape, rank => 20 ) # Delicious
26             $chooser->add( match => yellow, value => banana )
27             $chooser->add( match => yellow, value => lemon rank => -5 ) # Too sour
28              
29             my $favorite;
30             $favorite = $chooser->best( red ) # apple is the favorite red
31             $favorite = $chooser->best( [ red, yellow, purple ] ) # grape is the favorite among red, yellow, and purple
32              
33             =head1 DESCRIPTION
34              
35             An Algorithm::BestChoice object is similar to a hash, except it returns a result based on a given key AND relative ranking. That is, you can associate multiple values
36             with a single key, and differentiate them by using a rank (or weight).
37              
38             =head1 METHODS
39              
40             =head2 Algorithm::BestChoice->new
41              
42             Create and return a new Algorithm::BestChoice object
43              
44             =head2 $chooser->add( ... )
45              
46             Add a possible choice to the chooser
47              
48             The arguments are:
49              
50             match The key for the choice, can be a string or a regular expression
51             value The value to associate with the key (what is returned by ->best)
52             rank An optional numeric weight, the default is 0 (>0 is better, <0 is worse)
53              
54             =head2 $value = $chooser->best( <criterion> )
55              
56             Given criterion, ->best will return the value that 1. has a matching matcher and 2. has the highest rank
57              
58             =cut
59              
60             # TODO: Document ->best() ->best( [ ... ] )
61              
62 3     3   4397 use Moose;
  0            
  0            
63              
64             use Algorithm::BestChoice::Matcher;
65             use Algorithm::BestChoice::Ranker;
66             use Algorithm::BestChoice::Result;
67             use Algorithm::BestChoice::Option;
68              
69             use Scalar::Util qw/looks_like_number/;
70              
71             has options => qw/is ro required 1 isa ArrayRef/, default => sub { [] };
72              
73             sub add {
74             my $self = shift;
75             my %given = @_;
76              
77             $given{matcher} = $given{match} unless exists $given{matcher};
78             $given{ranker} = $given{rank} unless exists $given{ranker};
79             my ($matcher, $ranker) = @given{ qw/matcher ranker/ };
80              
81             if ($ranker && ! ref $ranker && $ranker eq 'length') {
82             if (! ref $matcher) {
83             $ranker = defined $matcher ? length $matcher : 0;
84             }
85             else {
86             die "Trying to rank by length, but given not-scalar matcher $matcher";
87             }
88             }
89              
90             $matcher = Algorithm::BestChoice::Matcher->parse( $matcher );
91             $ranker = Algorithm::BestChoice::Ranker->parse( $ranker );
92              
93             my $option = Algorithm::BestChoice::Option->new( matcher => $matcher, ranker => $ranker, value => $given{value} );
94              
95             push @{ $self->options }, $option;
96             }
97              
98             sub _best {
99             my $self = shift;
100             my $key = shift;
101              
102             my @tally;
103             for my $option (@{ $self->options }) {
104             if (my $match = $option->match( $key )) {
105             my $rank;
106             if (ref $match eq 'HASH') {
107             $rank = $match->{rank};
108             die "Got an undefined rank from a match" unless defined $rank;
109             die "Got a non-numeric rank ($rank) from a match" unless looks_like_number $rank;
110             }
111             else {
112             $rank = $option->rank( $key );
113             die "Got an undefined rank from a ranker" unless defined $rank;
114             die "Got a non-numeric rank ($rank) from a ranker" unless looks_like_number $rank;
115             }
116             push @tally, Algorithm::BestChoice::Result->new( rank => $rank, value => $option->value );
117             }
118             }
119              
120             return @tally;
121             }
122              
123             # TODO: Test for this multi-key ranker
124             # TODO: Probably want to give different weights to different keys!
125             sub best {
126             my $self = shift;
127              
128             my @tally = map { $self->_best( $_ ) } @_ ? map { ref $_ eq 'ARRAY' ? @$_ : $_ } @_ : (undef);
129             @tally = sort { $b->rank <=> $a->rank } @tally;
130             @tally = map { $_->value } @tally;
131             return wantarray ? @tally : $tally[0];
132             }
133              
134             =head1 AUTHOR
135              
136             Robert Krimen, C<< <rkrimen at cpan.org> >>
137              
138             =head1 BUGS
139              
140             Please report any bugs or feature requests to C<bug-algorithm-bestchoice at rt.cpan.org>, or through
141             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Algorithm-BestChoice>. I will be notified, and then you'll
142             automatically be notified of progress on your bug as I make changes.
143              
144              
145              
146              
147             =head1 SUPPORT
148              
149             You can find documentation for this module with the perldoc command.
150              
151             perldoc Algorithm::BestChoice
152              
153              
154             You can also look for information at:
155              
156             =over 4
157              
158             =item * RT: CPAN's request tracker
159              
160             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Algorithm-BestChoice>
161              
162             =item * AnnoCPAN: Annotated CPAN documentation
163              
164             L<http://annocpan.org/dist/Algorithm-BestChoice>
165              
166             =item * CPAN Ratings
167              
168             L<http://cpanratings.perl.org/d/Algorithm-BestChoice>
169              
170             =item * Search CPAN
171              
172             L<http://search.cpan.org/dist/Algorithm-BestChoice/>
173              
174             =back
175              
176              
177             =head1 ACKNOWLEDGEMENTS
178              
179              
180             =head1 COPYRIGHT & LICENSE
181              
182             Copyright 2009 Robert Krimen, all rights reserved.
183              
184             This program is free software; you can redistribute it and/or modify it
185             under the same terms as Perl itself.
186              
187              
188             =cut
189              
190             '"Purple is a fruit"'; # End of Algorithm::BestChoice