File Coverage

blib/lib/Project/Euler/Lib/Utils.pm
Criterion Covered Total %
statement 19 56 33.9
branch 0 30 0.0
condition 0 22 0.0
subroutine 7 13 53.8
pod 3 3 100.0
total 29 124 23.3


line stmt bran cond sub pod time code
1 1     1   4928 use warnings;
  1         2  
  1         36  
2 1     1   6 use strict;
  1         2  
  1         57  
3             package Project::Euler::Lib::Utils;
4             BEGIN {
5 1     1   18 $Project::Euler::Lib::Utils::VERSION = '0.20';
6             }
7              
8 1     1   6 use Modern::Perl;
  1         2  
  1         8  
9 1     1   159 use Carp;
  1         3  
  1         81  
10              
11 1     1   5588 use List::MoreUtils qw/ any all /;
  1         1562  
  1         139  
12              
13             # Export our functions with tags
14             use Exporter::Easy (
15 1         9 TAGS => [
16             fibs => [qw/ fib_generator n_fibs /],
17             list => [qw/ multiple_check /],
18             all => [qw/ :fibs /],
19             ],
20             #OK => [qw( some other stuff )],
21 1     1   1021 );
  1         1746  
22              
23              
24             # Initial array of fib #s
25             my @fibs = (1, 1);
26              
27              
28             #ABSTRACT: Collection of helper utilities for project euler problems
29              
30              
31              
32              
33              
34             sub fib_generator {
35 0     0 1   my ($a, $b) = 0..1;
36             return sub {
37             # Swap the 2 numbers
38 0     0     ($a, $b) = ($b, $a+$b);
39              
40             # And return the newly generated first one
41 0           $a;
42             }
43 0           }
44              
45              
46              
47              
48             sub n_fibs {
49 0     0 1   my ($num) = @_;
50              
51             # Turn $num into a string signifying undef to prepare for the error message.
52 0   0       $num //= 'UNDEFINED';
53              
54             # If a number > 0 was not passed, then confess with an error
55 0 0 0       confess "You must provide an integer > 0 to n_fibs. You provided: '$num'"
56             unless $num =~ /\A\d+\z/ and $num > 0;
57              
58             # If we've already calculated the fib the user wants, then simply return
59             # that value now
60              
61 0 0         if (scalar @fibs >= $num) {
    0          
62             # User is using 1-base not 0-base
63 0           $num--;
64              
65             # If the user wants an array, then take a slice, otherwise just grab that element.
66 0 0         return wantarray ? @fibs[0..$num] : $fibs[$num];
67             }
68              
69             # If not, then we'll take a different course of action depending on whether
70             # the user wants an array or not. I don't just fill out the cache because
71             # if the user wanted a huge value, then that would be impractical. I could
72             # do some logic around the # requested but I'm going to postpone that for
73             # now until I have an all-around bettter caching solution.
74             elsif (wantarray) {
75             # Calculate how many values we already have
76 0           $num -= @fibs;
77              
78             # Increase the size of the array until it's the size we want.
79 0           push @fibs, $fibs[-2] + $fibs[-1] while $num--;
80              
81 0           return @fibs;
82             }
83              
84             # Otherwise we'll just start with the last 2 known fibs and go from there
85             # till we get to the # we want.
86             else {
87             # User is using 1-base not 0-base
88 0           $num--;
89              
90             # Calculate the fibs until we find the one we want.
91 0           my ($a, $b) = @fibs[-2, -1];
92 0           ($a, $b) = ($b, $a+$b) while $num--;
93              
94 0           return $a;
95             }
96             }
97              
98              
99              
100              
101             sub multiple_check {
102 0     0 1   my ($num, $ranges, $all) = @_;
103             # Turn $num into a string signifying undef to prepare for the error message.
104 0   0       $num //= 'UNDEFINED';
105              
106             # If a number > 0 was not passed as the num range, then confess with an error
107 0 0 0       confess "You must provide an integer > 0 to filter_ranges for the first arg. You provided: '$num'"
108             unless $num !~ /\D/ and $num > 0;
109              
110 0 0         confess "You must provide an array ref of integers as the second arg to filter_ranges!"
111             unless defined $ranges # Makes sure ranges is defined
112             and ref $ranges eq 'ARRAY' # Makes sure ranges is an array_ref
113 0 0 0       and ((grep { !$_ # Ensure none of the values are either undef or 0
      0        
114             or $_ =~ /\D/ # or ontains something that isn't a digit
115             }
116             @$ranges) == 0);
117              
118              
119             # We only want need to check the values that are > than the number to
120             # check, since a number can not be divisible by another number that is
121             # greater than itself.
122 0           my @ranges = grep {$_ <= $num} @$ranges;
  0            
123              
124             # If the user wanted to check all of the numbers, then return "false" if
125             # any number got filtered out
126 0 0 0       return 0 if ($all and scalar @ranges != scalar @$ranges);
127              
128             # If there are no (remaining) numbers to filter on, then we'll return
129             # failure
130 0 0         return 0 unless scalar @ranges;
131              
132              
133             # If the user wants the values that matched (and isn't filtering on all of
134             # them) then we need to keep track of which ones matched so we have to use
135             # a slower native-perl version
136 0 0 0       if (wantarray and !$all) {
137 0           my @return_range;
138 0           for my $mult (@ranges) {
139 0 0         push @return_range, $mult if $num % $mult == 0;
140             }
141 0           return @return_range;
142             }
143              
144              
145             # Otherwise we can use List::MoreUtils's fast XS utils to do the checking
146             # for us
147 0     0     my $status = $all ? all {($num % $_) == 0} @ranges
148 0     0     : any {($num % $_) == 0} @ranges
149 0 0         ;
150              
151              
152             # Take the appropriate action depending on the context we're in
153 0 0         if (wantarray) {
154 0 0         return $status ? @ranges : ();
155             }
156             else {
157 0 0         return $status ? 1 : 0;
158             }
159             }
160              
161              
162              
163             1; # End of String::Palindrome
164              
165             __END__
166             =pod
167              
168             =head1 NAME
169              
170             Project::Euler::Lib::Utils - Collection of helper utilities for project euler problems
171              
172             =head1 VERSION
173              
174             version 0.20
175              
176             =head1 SYNOPSIS
177              
178             use Project::Euler::Lib::Utils qw/ :all /;
179              
180             =head1 EXPORTS
181              
182             =head2 :fibs
183              
184             =over 4
185              
186             =item *
187              
188             fib_generator
189              
190             =item *
191              
192             n_fibs
193              
194             =back
195              
196             =head2 :list
197              
198             =over 4
199              
200             =item *
201              
202             filter_ranges
203              
204             =back
205              
206             =head2 :all
207              
208             =over 4
209              
210             =item *
211              
212             :fibs
213              
214             =item *
215              
216             :list
217              
218             =back
219              
220             =head1 FUNCTIONS
221              
222             =head2 fib_generator
223              
224             This returns a clojure that returns the next successive fib number with each call
225              
226             =head3 Example
227              
228             my $fib = fib_generator;
229              
230             # Manually create the first 4 fibs
231             my @fibs;
232             push @fibs, $fib->() for 1..4;
233              
234             =head2 n_fibs
235              
236             The returns either the first 'n' fibs or the nth fib if called in scalar
237             context. If only the nth fib is used, then no memory is used to store the
238             previous fibs and it should run very fast. For now this does some very
239             primitive caching but will have to be improved in the future.
240              
241             This also does not currently use Math::BigInt so if a large # is requested it
242             may not be 100% accurate. This will be fixed once I decide upon a caching
243             solution.
244              
245             =head3 Parameters
246              
247             =over 4
248              
249             =item 1
250              
251             Fib number (or list up to a number) that you would like returned.
252              
253             =back
254              
255             =head3 Example
256              
257             # Get the first 4 fib numbers
258             my @fibs = n_fibs( 4 );
259              
260             # Just get the last one
261             my $fourth_fib = n_fibs( 4 );
262              
263             $fibs[-1] == $fourth_fib;
264              
265             =head2 multiple_check
266              
267             Check to see if a number is evenly divisible by one or all of a range of numbers.
268              
269             =head3 Parameters
270              
271             =over 4
272              
273             =item 1
274              
275             Number to check divisibility on (I<must be greater than 0>)
276              
277             =item 2
278              
279             Range of numbers to check for divisibility (I<all must be grater than 0>)
280              
281             =item 3
282              
283             Boolean to check all range numbers (B<optional>)
284              
285             =back
286              
287             =head3 Example
288              
289             my $is_divisible = multiple_check(15, [2, 3, 5], 0);
290             my $is_divisible2 = multiple_check(15, [2, 3, 5]);
291             my $is_not_divisible = multiple_check(10, [3, 6, 7]);
292              
293             my $is_all_divisible = multiple_check(30, [2, 3, 5], 1);
294             my $is_not_all_divisible = multiple_check(15, [2, 3, 5], 1);
295              
296             my @div_by = multiple_check(15, [2, 3, 5]);
297             @div_by ~~ (3, 5) == 1;
298              
299              
300             my $num = 3;
301             my $is_prime = !multiple_check($num, [2..sqrt($num)]);
302              
303             =head1 AUTHOR
304              
305             Adam Lesperance <lespea@gmail.com>
306              
307             =head1 COPYRIGHT AND LICENSE
308              
309             This software is copyright (c) 2010 by Adam Lesperance.
310              
311             This is free software; you can redistribute it and/or modify it under
312             the same terms as the Perl 5 programming language system itself.
313              
314             =cut
315