File Coverage

blib/lib/Algorithm/LineSegments.pm
Criterion Covered Total %
statement 15 92 16.3
branch 0 20 0.0
condition 0 4 0.0
subroutine 5 10 50.0
pod 1 1 100.0
total 21 127 16.5


line stmt bran cond sub pod time code
1             package Algorithm::LineSegments;
2 3     3   121906 use 5.012000;
  3         13  
  3         128  
3 3     3   19 use strict;
  3         5  
  3         119  
4 3     3   23 use warnings;
  3         25  
  3         113  
5 3     3   16 use List::Util qw/min max/;
  3         4  
  3         465  
6 3     3   3118 use Heap::Priority;
  3         4861  
  3         3760  
7            
8             require Exporter;
9            
10             our @ISA = qw(Exporter);
11            
12             our %EXPORT_TAGS = ( 'all' => [ qw(
13             line_segment_points
14             ) ] );
15            
16             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
17            
18             our @EXPORT = qw(
19             line_segment_points
20             );
21            
22             our $VERSION = '0.04';
23            
24             sub _normalised_euclidean {
25 0     0     my ($left, $right, $p) = @_;
26 0           my $y0 = $left->[0];
27 0           my $y1 = $right->[-1];
28 0           my $cl = @{ $left };
  0            
29 0           my $cr = @{ $right };
  0            
30 0           my $xx = ($y1 - $y0) / ($cl + $cr - 1);
31            
32 0           my $sum = 0;
33 0           for (0 .. $cl - 1) {
34 0           my $pi = $y0 + $xx * $_;
35 0           my $pr = $left->[$_];
36 0           $sum += ($p->($pr) - $p->($pi))**2;
37             }
38            
39 0           for (0 .. $cr - 1) {
40 0           my $pi = $y0 + $xx * ($_ + $cl);
41 0           my $pr = $right->[$_];
42 0           $sum += ($p->($pr) - $p->($pi))**2;
43             }
44            
45 0           sqrt $sum;
46             }
47            
48             sub line_segment_points {
49            
50 0     0 1   my (%o) = @_;
51            
52 0 0         die unless $o{points};
53 0           my @d = @{ $o{points} };
  0            
54            
55 0           my @q;
56            
57 0           for (my $ix = 0; $ix < @d - 1; $ix += 2) {
58 0           push @q, [$d[$ix], $d[$ix+1]];
59             }
60            
61 0           my $min = min @d;
62 0           my $max = max @d;
63            
64             ###################################################################
65             # This function projects values from $min to $max to 0 to 1. This
66             # is useful in order to keep the cost values similar.
67             ###################################################################
68             my $scale_to_unit = sub {
69 0     0     my $v = shift;
70 0           return ($v - $min) / ($max - $min);
71 0           };
72            
73             $o{cost} //= sub {
74 0     0     my ($left, $right) = @_;
75 0           _normalised_euclidean($q[$left], $q[$right], $scale_to_unit);
76 0   0       };
77            
78             $o{continue} //= sub {
79 0     0     my ($count, $cost) = @_;
80 0 0         return 0 if $count <= 3;
81 0           return 1;
82 0   0       };
83            
84 0           my $heap = Heap::Priority->new;
85 0           $heap->lowest_first;
86 0           $heap->add($_, $o{cost}->($_, $_+1)) for 0 .. $#q - 1;
87            
88             ###################################################################
89             # I haven't found a good solution to maintain the heap and modify
90             # the list, so as a workaround the heap identifies a mergable pair
91             # with the key and when merging elements of a pair, the second
92             # element is replaced by `undef` to maintain the size of the list,
93             # so the heap keys, indices into the list, remain valid. This has
94             # the consequence of producing gaps in the list, and the variables
95             # below maintain how the gaps can be skipped.
96             ###################################################################
97 0           my %next = map { $_ => $_ + 1 } 0 .. $#q - 1;
  0            
98 0           my %prev = map { $_ => $_ - 1 } 1 .. $#q - 1;
  0            
99            
100 0           for (my $count = @q;;) {
101 0           my $ix = $heap->pop;
102 0 0         last unless defined $ix;
103            
104             #################################################################
105             # Ordinarily it should be possible to obtain the priority of the
106             # element on top of the heap, but the chosen module can report
107             # only the priorities of all elements, which is a bit costly, so
108             # instead the cost is re-computed here for now.
109             #################################################################
110 0           my $cost = $o{cost}->($ix, $next{$ix});
111            
112             #################################################################
113             # The callback can be by calling code to stop the merging process
114             #################################################################
115 0 0         last unless $o{continue}->($count, $cost);
116            
117 0           my $k = $ix;
118 0           my $j = $next{$k};
119            
120 0 0         next unless defined $j;
121            
122 0           my @merged = map { @{ $q[$_] } } $k, $j;
  0            
  0            
123 0           $q[$k] = undef;
124 0           $q[$j] = undef;
125 0           splice @q, $k, 2, [@merged], undef;
126 0           $count--;
127            
128             #################################################################
129             # Now that $k has changed, merging $k with the element before or
130             # after has a different cost, so those elements are removed from
131             # the heap and added again with the newly calculated cost factor.
132             #################################################################
133 0 0         $heap->delete_item($next{$k}) if defined $next{$k};
134 0 0         $heap->delete_item($prev{$k}) if defined $prev{$k};
135            
136 0           $next{$k} = $next{$j};
137            
138 0 0         $heap->add($prev{$k}, $o{cost}->($prev{$k}, $k)) if defined $prev{$k};
139 0 0         $heap->add($k, $o{cost}->($k, $next{$k})) if defined $next{$k};
140            
141 0 0         $prev{$next{$j}} = $k if defined $next{$j};
142            
143 0           delete $next{$j};
144 0           delete $prev{$j};
145             }
146            
147 0           my @temp = grep { defined } @q;
  0            
148 0           my @result;
149 0           my $pos = 0;
150 0           for (my $ix = 0; $ix < @temp; ++$ix) {
151 0           push @result, [
152             [ $pos, $temp[$ix][0] ],
153 0           [ $pos + scalar(@{$temp[$ix]}) - 1, $temp[$ix][-1] ]
154             ];
155 0           $pos += scalar(@{$temp[$ix]})
  0            
156             }
157            
158 0           return @result;
159             }
160            
161             1;
162            
163             __END__