File Coverage

blib/lib/Tree/VP.pm
Criterion Covered Total %
statement 6 8 75.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 9 11 81.8


line stmt bran cond sub pod time code
1             package Tree::VP;
2 1     1   14263 use v5.8;
  1         2  
3             our $VERSION = "0.05";
4              
5 1     1   496 use Moo;
  1         9610  
  1         6  
6 1     1   1890 use List::Priority;
  0            
  0            
7             use Tree::DAG_Node;
8              
9             has distance => (
10             is => "ro",
11             required => 1,
12             );
13              
14             has tree => (
15             is => "rw",
16             );
17              
18             sub build {
19             my ($self, $values) = @_;
20             $self->tree( $self->_build_tree_maybe($values) ) if @$values;
21             return $self;
22             }
23              
24             sub _build_tree_maybe {
25             my ($self, $values) = @_;
26             my $node;
27             if (@$values) {
28             my $vp = shift @$values;
29             my @v = ($vp);
30              
31             $node = Tree::DAG_Node->new({
32             name => "$vp",
33             attributes => { vp => $vp }
34             });
35              
36             return $node unless @$values;
37              
38             my @dist = sort { $a->[1] <=> $b->[1] } map {[$_, $self->distance->($_, $vp)]} @$values;
39              
40             my $center = int( $#dist/2 );
41             my (@left, @right, $min, $max);
42              
43             my $median = (@dist == 1)
44             ? $dist[0][1] : (@dist % 2 == 1)
45             ? $dist[$center][1] : ($dist[$center][1] + $dist[$center+1][1])/2;
46              
47             for (@dist) {
48             if ($_->[1] == 0) {
49             push @v, $_->[0];
50             } elsif ($_->[1] < $median) {
51             $min = $_->[1] if !defined($min);
52             push @left, $_->[0];
53             } else {
54             push @right, $_->[0];
55             $max = $_->[1];
56             }
57             }
58              
59             $node->attributes->{mu} = $median;
60             $node->attributes->{distance_min} = $min;
61             $node->attributes->{distance_max} = $max || $min || 0;
62              
63             if (@left) {
64             if (my $node_left = $self->_build_tree_maybe(\@left)) {
65             $node_left->attributes->{is_left_daughter} = 1;
66             $node->add_daughter( $node_left );
67             }
68             }
69             if (@right) {
70             if (my $node_right = $self->_build_tree_maybe(\@right)) {
71             $node_right->attributes->{is_right_daughter} = 1;
72             $node->add_daughter( $node_right );
73             }
74             }
75             }
76             return $node;
77             }
78              
79             sub search {
80             my ($self, %args) = @_;
81             $args{size} ||= 2;
82             return $self->_search_tree( $self->tree, %args );
83             }
84              
85             sub _search_tree {
86             my ($self, $tree, %args)= @_;
87             my $result = { values => [] };
88              
89             my ($left, $right) = $tree->daughters;
90             if (!$right && $left && $left->{attributes}{is_right_daughter}) {
91             $right = $left;
92             $left = undef;
93             }
94              
95             my $is_top_level = !defined($args{__pq});
96             my $pq = $args{__pq} ||= List::Priority->new;
97              
98             my $v = $tree->attributes->{vp};
99             my $d = $self->distance->($v, $args{query});
100              
101             $args{tau} = $tree->attributes->{distance_max} unless defined $args{tau};
102             if ($d < $args{tau}) {
103             $pq->insert($d, $v);
104             if ($pq->size() > $args{size}) {
105             $pq->pop();
106             $args{tau} = $pq->highest_priority;
107             }
108             }
109              
110             if (defined($tree->attributes->{mu})) {
111             my $mu = $tree->attributes->{mu};
112             if ($d < $args{tau}) {
113             if ($left && $tree->attributes->{distance_min} - $args{tau} < $d) {
114             $self->_search_tree($left, %args);
115             $args{tau} = $pq->highest_priority;
116             }
117             if ($right && $mu - $args{tau} < $d && $d < $tree->attributes->{distance_max} + $args{tau}) {
118             $self->_search_tree($right, %args);
119             }
120             } else {
121             if ($right && $d < $tree->attributes->{distance_max} + $args{tau}) {
122             $self->_search_tree($right, %args);
123             $args{tau} = $pq->highest_priority;
124             }
125             if ($left && $tree->attributes->{distance_min} - $args{tau} < $d && $d < $mu + $args{tau}) {
126             $self->_search_tree($left, %args);
127             }
128             }
129             }
130              
131             if ($is_top_level) {
132             my @results;
133             while ($pq->size() > 0) {
134             my $d = $pq->lowest_priority;
135             my $x = $pq->shift();
136             push @results, {
137             distance => $d,
138             value => $x,
139             }
140             }
141             $result->{results} = \@results;
142             }
143             return $result;
144             }
145              
146              
147             1;
148              
149             __END__