File Coverage

blib/lib/App/Math/Tutor/Role/Unit.pm
Criterion Covered Total %
statement 88 91 96.7
branch 23 34 67.6
condition 16 28 57.1
subroutine 11 11 100.0
pod 1 1 100.0
total 139 165 84.2


line stmt bran cond sub pod time code
1             package App::Math::Tutor::Role::Unit;
2              
3 1     1   967 use warnings;
  1         4  
  1         39  
4 1     1   5 use strict;
  1         3  
  1         71  
5              
6             =head1 NAME
7              
8             App::Math::Tutor::Role::Unit - role for numererical parts for calculation with units
9              
10             =cut
11              
12 1     1   5 use Moo::Role;
  1         3  
  1         8  
13              
14             our $VERSION = '0.004';
15              
16 1     1   1426 use Hash::MoreUtils qw/slice_def/;
  1         1276  
  1         74  
17 1     1   8 use App::Math::Tutor::Numbers;
  1         4  
  1         1272  
18              
19             has unit_definitions => ( is => "lazy" );
20              
21             sub _build_unit_definitions
22             {
23             return {
24 3     3   691 time => {
25             base => { s => { max => 59 } },
26             multiplier => {
27             w => {
28             max => 52,
29             factor => 7 * 24 * 60 * 60,
30             },
31             d => {
32             max => 6,
33             factor => 24 * 60 * 60,
34             },
35             h => {
36             max => 23,
37             factor => 60 * 60,
38             },
39             min => {
40             max => 59,
41             factor => 60,
42             },
43             },
44             divider => {
45             ms => {
46             max => 999,
47             factor => 1000,
48             },
49             },
50             },
51             length => {
52             base => { m => { max => 999 } },
53             multiplier => {
54             km => {
55             factor => 1000,
56             },
57             },
58             divider => {
59             dm => {
60             max => 9,
61             factor => 10,
62             },
63             cm => {
64             max => 9,
65             factor => 100,
66             },
67             mm => {
68             max => 9,
69             factor => 1000,
70             },
71             }
72             },
73             weight => {
74             base => { g => { max => 999 } },
75             multiplier => {
76             kg => {
77             max => 999,
78             factor => 1000,
79             },
80             t => {
81             factor => 1000 * 1000,
82             },
83             },
84             divider => {
85             mg => {
86             max => 999,
87             factor => 1000,
88             },
89             },
90             },
91             euro => {
92             base => { '\euro{}' => {} },
93             divider => {
94             'cent' => {
95             factor => 100,
96             max => 99
97             }
98             },
99             },
100             pound => {
101             base => { '\textsterling{}' => {} },
102             divider => {
103             'p' => {
104             factor => 100,
105             max => 99
106             }
107             },
108             },
109             dollar => {
110             base => { '\textdollar{}' => {} },
111             divider => {
112             '\textcent{}' => {
113             factor => 100,
114             max => 99
115             }
116             },
117             },
118             };
119             }
120              
121             has ordered_units => ( is => "lazy" );
122              
123             requires "relevant_units";
124              
125             sub _build_ordered_units_flatten_helper
126             {
127 54     54   71 my $unit_part = $_[0];
128 54         52 my @flatten;
129              
130 54         53 foreach my $upnm ( keys %{$unit_part} )
  54         132  
131             {
132 63         70 my ( $min, $max, $factor ) = @{ $unit_part->{$upnm} }{qw(min max factor)};
  63         136  
133 63 50       139 defined $min or $min = 0;
134 63 100       101 defined $factor or $factor = 1;
135 63         264 my %upv = slice_def {
136             min => $min,
137             max => $max,
138             factor => $factor,
139             unit => $upnm
140             };
141 63         1279 push @flatten, \%upv;
142             }
143              
144 54         126 @flatten;
145             }
146              
147             sub _build_ordered_units
148             {
149 3     3   651 my $self = shift;
150 3         8 my %ou; # ordered units
151 3         17 my $ud = $self->unit_definitions;
152 3         20 my $ru = $self->relevant_units;
153              
154 3         37 foreach my $cat (@$ru)
155             {
156 18         46 my @base = _build_ordered_units_flatten_helper( $ud->{$cat}->{base} );
157 18         53 my @mult = _build_ordered_units_flatten_helper( $ud->{$cat}->{multiplier} );
158 18         49 my @div = _build_ordered_units_flatten_helper( $ud->{$cat}->{divider} );
159 18         25 my %ru; # reworked unit
160              
161 18 50       41 1 != scalar @base and die "Invalid unit description: $cat";
162              
163 18         36 @mult = sort { $b->{factor} <=> $a->{factor} } @mult;
  18         34  
164 18         25 @div = sort { $a->{factor} <=> $b->{factor} } @div;
  8         17  
165 18         31 $ru{base} = scalar @mult;
166 18         59 $ru{spectrum} = [ @mult, @base, @div ];
167 18         71 $ou{$cat} = \%ru;
168             }
169              
170 3         18 return \%ou;
171             }
172              
173             sub _guess_unit_number
174             {
175 168     168   232 my ( $unit_type, $lb, $ub ) = @_;
176 168         170 my @rc;
177              
178 168 50 66     672 $lb == $ub and $lb == scalar @{ $unit_type->{spectrum} } and --$lb;
  59         201  
179 168 100 100     491 $lb == $ub and $ub == 0 and scalar @{ $unit_type->{spectrum} } > 0 and ++$ub;
  27   66     108  
180 168 100 100     420 $lb == $ub and $ub < $unit_type->{base} and ++$ub;
181 168 100       297 $lb == $ub and --$lb;
182              
183 168         200 REDO:
184             my ( $_lb, $_ub ) = ( $lb, $ub );
185 168         154 my $i;
186 168         339 for ( $i = $_lb; $i <= $_ub; ++$i )
187             {
188 399         390 my ( $min, $max ) = @{ $unit_type->{spectrum}->[$i] }{qw(min max)};
  399         838  
189 399 100       827 defined $max
190             or $max = 100; # largest unit doesn't have an upper limit - XXX make it user definable
191 399         1359 push( @rc, int( rand( $max + $min ) ) - $min );
192             }
193 168   66     745 ++$_lb and shift @rc while ( @rc and !$rc[0] );
      50        
194 168   66     760 $_ub-- and pop @rc while ( @rc and !$rc[-1] );
      50        
195 168 50       323 @rc or goto REDO;
196              
197             return
198 168         4116 Unit->new(
199             type => $unit_type,
200             begin => $_lb,
201             end => $_ub,
202             parts => \@rc
203             );
204             }
205              
206             requires "unit_length";
207             requires "deviation";
208              
209             =head1 METHODS
210              
211             =head2 get_unit_numbers
212              
213             Returns as many numbers with units as requested. Does Factory :)
214              
215             =cut
216              
217             sub get_unit_numbers
218             {
219 108     108 1 165 my ( $self, $amount, $ut ) = @_;
220              
221 108         312 my $ou = $self->ordered_units;
222 108         3508 my @result;
223 108         383 my @unames = keys %$ou;
224 108 100       393 defined $ut or $ut = $ou->{ $unames[ int( rand( scalar @unames ) ) ] };
225 108 50       274 my $length = $self->has_unit_length ? $self->unit_length : scalar @{ $ut->{spectrum} };
  108         206  
226 108         187 my $deviation = $self->deviation;
227 108         115 my ( $lo, $uo );
228              
229             my $fits = sub {
230 168     168   228 my ( $lb, $ub ) = @_;
231 168 50       345 $ub - $lb >= $length and return 0;
232 168 50       518 defined $deviation or return 1;
233 0 0 0     0 defined $lo and abs( $lb - $lo ) > $deviation and return 0;
234 0 0 0     0 defined $uo and abs( $lb - $uo ) > $deviation and return 0;
235 0         0 return 1;
236 108         1422 };
237              
238 108         243 while ( $amount-- )
239             {
240 168         175 my ( @bounds, $unit );
241             do
242 168         181 {
243 168         342 @bounds = (
244 168         347 int( rand( scalar @{ $ut->{spectrum} } ) ),
245 168         152 int( rand( scalar @{ $ut->{spectrum} } ) )
246             );
247 168 100       591 $bounds[0] > $bounds[1] and @bounds = reverse @bounds;
248             } while ( !$fits->(@bounds) );
249              
250 168         290 $unit = _guess_unit_number( $ut, @bounds );
251 168 100       6467 @result or ( $lo, $uo ) = ( $unit->begin, $unit->end );
252 168         505 push( @result, $unit );
253             }
254              
255 108         756 return @result;
256             }
257              
258             =head1 LICENSE AND COPYRIGHT
259              
260             Copyright 2010-2014 Jens Rehsack.
261              
262             This program is free software; you can redistribute it and/or modify it
263             under the terms of either: the GNU General Public License as published
264             by the Free Software Foundation; or the Artistic License.
265              
266             See http://dev.perl.org/licenses/ for more information.
267              
268             =cut
269              
270             1;