File Coverage

blib/lib/Number/Closest/NonOO.pm
Criterion Covered Total %
statement 54 61 88.5
branch 29 40 72.5
condition 21 33 63.6
subroutine 8 8 100.0
pod 1 1 100.0
total 113 143 79.0


line stmt bran cond sub pod time code
1             package Number::Closest::NonOO;
2              
3             our $DATE = '2015-09-03'; # DATE
4             our $VERSION = '0.05'; # VERSION
5              
6 1     1   675 use 5.010001;
  1         3  
7 1     1   5 use strict;
  1         1  
  1         18  
8 1     1   4 use warnings;
  1         2  
  1         22  
9              
10 1     1   945 use Perinci::Sub::Util qw(gen_modified_sub);
  1         2200  
  1         97  
11 1     1   7 use Scalar::Util 'looks_like_number';
  1         2  
  1         852  
12              
13             require Exporter;
14             our @ISA = qw(Exporter);
15             our @EXPORT_OK = qw(find_closest_number find_farthest_number);
16             our %SPEC;
17              
18             $SPEC{':package'} = {
19             v => 1.1,
20             summary => 'Find number(s) closest to a number in a list of numbers',
21             };
22              
23             sub _find {
24 14     14   39 my %args = @_;
25              
26 14         25 my $num = $args{number};
27 14   50     72 my $nan = $args{nan} // 'nothing';
28 14   100     55 my $inf = $args{inf} // 'nothing';
29 14         16 my @nums = @{ $args{numbers} };
  14         48  
30 14 50 33     152 if ($nan eq 'exclude' && $inf eq 'exclude') {
    50          
31             @nums = grep {
32 0 0 0     0 looks_like_number($_) && $_ != 'inf' && $_ != '-inf'
  0         0  
33             } @nums;
34             } elsif ($nan eq 'exclude') {
35             @nums = grep {
36 0         0 my $l = looks_like_number($_);
  0         0  
37 0 0 0     0 $l &&
38             $l != 36 && # nan
39             $l != 44; # -nan
40             } @nums;
41             }
42 14 50       31 if ($inf eq 'exclude') {
43             @nums = grep {
44 0 0 0     0 !looks_like_number($_) ? 1 : ($_ != 'inf' && $_ != '-inf')
  0         0  
45             } @nums;
46             }
47              
48 14         19 my @mapped;
49             my @res;
50 14 100       31 if ($inf eq 'number') {
51             @res =map {
52 6         14 my $m = [$_];
  30         57  
53 30 100 100     330 if ($num == 'inf' && $_ == 'inf') { push @$m, 0, 0 }
  1 100 100     4  
    100 100        
    100 100        
    100          
    100          
    100          
    100          
54 1         5 elsif ($num == 'inf' && $_ == '-inf') { push @$m, 'inf', 'inf' }
55 1         5 elsif ($num == '-inf' && $_ == 'inf') { push @$m, 'inf', 'inf' }
56 1         4 elsif ($num == '-inf' && $_ == '-inf') { push @$m, 0, 0 }
57 5         10 elsif ($num == 'inf') { push @$m, $num, -$_ }
58 5         13 elsif ($num == '-inf') { push @$m, -$num, $_ }
59 4         12 elsif ($_ == 'inf') { push @$m, $_, -$_ }
60 4         12 elsif ($_ == '-inf') { push @$m, -$_, $_ }
61 8         45 else { push @$m, abs($_-$num), 0 }
62 30         89 $m;
63             } @nums;
64             #use Data::Dump; dd \@res;
65 6 50 100     19 @res = sort {$a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] || $a->[0] <=> $b->[0] }
  40         179  
66             @res;
67             } else {
68 26 50       83 @res = sort {$a->[1] <=> $b->[1] || $a->[0] <=> $b->[0]}
69 8         12 map {[$_, abs($_-$num)]} @nums;
  24         94  
70             }
71 14         26 @res = map {$_->[0]} @res;
  54         127  
72              
73 14   100     54 my $items = $args{items} // 1;
74 14 100       50 @res = reverse @res if $args{-farthest};
75 14 100       41 splice @res, $items unless $items >= @res;
76              
77 14 100       34 if ($items == 1) {
78 6         51 return $res[0];
79             } else {
80 8         81 return \@res;
81             }
82             }
83              
84             $SPEC{find_closest_number} = {
85             v => 1.1,
86             summary => 'Find number(s) closest to a number in a list of numbers',
87             args => {
88             number => {
89             summary => 'The target number',
90             schema => 'num*',
91             req => 1,
92             },
93             numbers => {
94             summary => 'The list of numbers',
95             schema => 'array*',
96             req => 1,
97             },
98             items => {
99             summary => 'Return this number of closest numbers',
100             schema => ['int*', min=>1, default=>1],
101             },
102             nan => {
103             summary => 'Specify how to handle NaN and non-numbers',
104             schema => ['str', in=>['exclude', 'nothing'], default=>'exclude'],
105             description => <<'_',
106              
107             `exclude` means the items will first be excluded from the list. `nothing` will
108             do nothing about it, meaning there will be warnings when comparing non-numbers.
109              
110             _
111             },
112             inf => {
113             summary => 'Specify how to handle Inf',
114             schema => ['str', in=>['number', 'nothing', 'exclude'],
115             default=>'nothing'],
116             description => <<'_',
117              
118             `exclude` means the items will first be excluded from the list. `nothing` will
119             do nothing about it and will produce a warning if target number is an infinite,
120             `number` will treat Inf like a very large number, i.e. Inf is closest to Inf and
121             largest positive numbers, -Inf is closest to -Inf and after that largest
122             negative numbers.
123              
124             I'd reckon that `number` is the behavior that most people want when dealing with
125             infinites. But since it's slower, it's not the default and you have to specify
126             it specifically. You should choose `number` if target number is infinite.
127              
128             _
129             },
130             },
131             result_naked => 1,
132             };
133             sub find_closest_number {
134 9     9 1 57 my %args = @_;
135 9         38 _find(%args);
136             }
137              
138             gen_modified_sub(
139             output_name => 'find_farthest_number',
140             base_name => 'find_closest_number',
141             summary => 'Find number(s) farthest to a number in a list of numbers',
142             output_code => sub {
143 5     5   24 my %args = @_;
144 5         27 _find(%args, -farthest=>1);
145             },
146             );
147              
148             1;
149             # ABSTRACT: Find number(s) closest to a number in a list of numbers
150              
151             __END__