File Coverage

blib/lib/AI/Pathfinding/SMAstar/PriorityQueue.pm
Criterion Covered Total %
statement 114 276 41.3
branch 25 88 28.4
condition 1 15 6.6
subroutine 15 27 55.5
pod 0 18 0.0
total 155 424 36.5


line stmt bran cond sub pod time code
1             #
2             # PriorityQueue.pm
3             #
4              
5             # Author: matthias beebe
6             # Date : June 2008
7             #
8             #
9             package AI::Pathfinding::SMAstar::PriorityQueue;
10              
11              
12 1     1   1140 use Tree::AVL;
  1         7507  
  1         33  
13 1     1   802 use AI::Pathfinding::SMAstar::Path;
  1         3  
  1         29  
14 1     1   732 use AI::Pathfinding::SMAstar::TreeOfQueues;
  1         3  
  1         30  
15 1     1   8 use Carp;
  1         2  
  1         64  
16 1     1   5 use strict;
  1         1  
  1         3099  
17              
18              
19              
20             ##################################################
21             # PriorityQueue constructor
22             ##################################################
23             sub new {
24 1     1 0 14 my $invocant = shift;
25 1   33     5 my $class = ref($invocant) || $invocant;
26             my $self = {
27             _hash_of_trees_ref => {},
28            
29             _cost_min_max_tree => Tree::AVL->new( fcompare => \&fp_compare, # floating-point compare
30 16     16   156 fget_key => sub { $_[0] },
31 1     0   9 fget_data => sub { $_[0] },),
  0         0  
32              
33             f_depth => \&AI::Pathfinding::SMAstar::Path::depth,
34             f_fcost => \&AI::Pathfinding::SMAstar::Path::fcost,
35             f_avl_compare => \&AI::Pathfinding::SMAstar::Path::compare_by_depth,
36             f_avl_get_key => \&AI::Pathfinding::SMAstar::Path::depth,
37             f_avl_get_data => \&AI::Pathfinding::SMAstar::Path::get_data,
38              
39             _size => 0,
40              
41             @_, # attribute override
42             };
43 1         39 return bless $self, $class;
44             }
45              
46             ################################################
47             # accessors
48             ################################################
49              
50             sub hash_of_trees {
51 0     0 0 0 my $self = shift;
52 0 0       0 if (@_) { $self->{_hash_of_trees_ref} = shift }
  0         0  
53 0         0 return $self->{_hash_of_trees_ref};
54             }
55              
56             sub size {
57 1     1 0 1 my $self = shift;
58 1 50       3 if (@_) { $self->{_size} = shift }
  0         0  
59 1         3 return $self->{_size};
60             }
61              
62              
63              
64             ################################################
65             ##
66             ## other methods
67             ##
68             ################################################
69              
70             sub insert {
71 16     16 0 29 my ($self, $pobj) = @_;
72              
73 16         24 my $cost_hash_ref = $self->{_hash_of_trees_ref};
74 16         16 my $cost_hash_key_func = $self->{f_fcost};
75              
76 16         18 my $cost_min_max_tree = $self->{_cost_min_max_tree};
77              
78 16         17 my $depth_func = $self->{f_depth};
79            
80 16         16 my $avl_compare_func = $self->{f_avl_compare};
81 16         15 my $avl_get_key_func = $self->{f_avl_get_key};
82 16         17 my $avl_get_data_func = $self->{f_avl_get_data};
83              
84 16         33 my $cost_key = $pobj->$cost_hash_key_func();
85 16         37 my $data = $pobj->$avl_get_data_func();
86              
87            
88             # inserting pobj with key: $cost_key, data: $data
89 16 100       75 if(!$cost_hash_ref->{$cost_key}){
90             # no tree for this depth yet, so create one.
91 6         27 my $avltree = AI::Pathfinding::SMAstar::TreeOfQueues->new(
92             f_avl_compare => $avl_compare_func,
93             f_obj_get_key => $avl_get_key_func,
94             f_obj_get_data => $avl_get_data_func,
95             );
96            
97 6         19 $avltree->insert($pobj);
98 6         26 $cost_hash_ref->{$cost_key} = \$avltree;
99             # insert the cost_key in the cost tree
100 6         15 $cost_min_max_tree->insert($cost_key);
101             }
102             else{
103             # there is already a tree at $cost_key, so inserting there
104 10         23 my $avltree = $cost_hash_ref->{$cost_key};
105 10         26 $$avltree->insert($pobj);
106             }
107 16         142 $self->{_size} = $self->{_size} + 1;
108 16         21 my $antecedent = $pobj->{_antecedent};
109 16 100       29 if($antecedent){
110 9         15 $antecedent->{_descendants_on_queue} = $antecedent->{_descendants_on_queue} + 1;
111             }
112 16         45 $pobj->is_on_queue(1);
113             }
114              
115              
116             sub print_trees_in_order
117             {
118 0     0 0 0 my ($self) = @_;
119              
120 0         0 my $cost_hash_ref = $self->{_hash_of_trees_ref};
121            
122 0         0 for my $cost_key (keys %$cost_hash_ref){
123 0 0       0 if(!$cost_hash_ref->{$cost_key}){
124             # no tree for this depth.
125             #print "no tree at key $depth_key\n";
126             }
127             else{
128             #print "contents of tree with depth $depth_key\n";
129 0         0 my $avltree = $cost_hash_ref->{$cost_key};
130 0         0 $$avltree->print();
131             }
132             }
133             }
134              
135              
136             #-----------------------------------
137             # get_list
138             #
139             # return a list of all objects in queue
140             #
141             #-----------------------------------
142             sub get_list
143             {
144 0     0 0 0 my ($self) = @_;
145              
146 0         0 my $cost_hash_ref = $self->{_hash_of_trees_ref};
147            
148 0         0 my @list;
149            
150 0         0 for my $cost_key (keys %$cost_hash_ref){
151 0 0       0 if($cost_hash_ref->{$cost_key}){
152 0         0 my $avltree = $cost_hash_ref->{$cost_key};
153 0         0 push(@list, $$avltree->get_list());
154             }
155             }
156 0         0 return @list;
157             }
158              
159              
160             sub is_empty
161             {
162 7     7 0 9 my ($self) = @_;
163            
164 7         8 my $cost_hash_ref = $self->{_hash_of_trees_ref};
165 7         16 my @cost_keys = (keys %$cost_hash_ref);
166            
167 7 50       18 if(!@cost_keys){
168 0         0 return 1;
169             }
170             else{
171 7         33 return 0;
172             }
173             }
174              
175              
176             sub remove
177             {
178 4     4 0 6 my ($self, $obj, $cmp_func) = @_;
179              
180 4         4 my $cost_hash_ref = $self->{_hash_of_trees_ref};
181 4         9 my @cost_keys = (keys %$cost_hash_ref);
182            
183              
184 4         7 my $cost_min_max_tree = $self->{_cost_min_max_tree};
185            
186            
187 4         5 my $avl_get_data_func = $self->{f_avl_get_data};
188 4         4 my $cost_hash_key_func = $self->{f_fcost};
189 4         5 my $depth_func = $self->{f_depth};
190            
191              
192 4         9 my $cost_key = $obj->$cost_hash_key_func();
193 4         10 my $data = $obj->$avl_get_data_func();
194            
195 4 50       16 if(!$cost_hash_ref->{$cost_key}){
196             # no tree for this cost_key
197 0         0 return;
198             }
199             else{
200             # found the tree at $cost_key, trying to remove obj from there
201            
202 4         8 my $avltree = $cost_hash_ref->{$cost_key};
203 4         14 $$avltree->remove($obj, $cmp_func);
204              
205             # if tree is empty, remove it from hash
206 4 100       84 if($$avltree->is_empty()){
207 3         10 delete $cost_hash_ref->{$cost_key};
208 3         7 $cost_min_max_tree->remove($cost_key);
209             }
210 4         50 $self->{_size} = $self->{_size} - 1;
211             }
212 4         5 my $antecedent = $obj->{_antecedent};
213 4 100       9 if($antecedent){
214 1         2 $antecedent->{_descendants_on_queue} = $antecedent->{_descendants_on_queue} - 1;
215             }
216              
217 4         12 $obj->is_on_queue(0);
218 4         12 return;
219             }
220              
221             sub deepest_lowest_cost_leaf
222             {
223 5     5 0 7 my ($self) = @_;
224            
225 5         8 my $cost_hash_ref = $self->{_hash_of_trees_ref};
226 5         13 my @cost_keys = (keys %$cost_hash_ref);
227              
228            
229 5         7 my $cost_min_max_tree = $self->{_cost_min_max_tree};
230              
231 5 50       12 if(!@cost_keys){
232             # queue is empty
233 0         0 return;
234             }
235              
236             # get the lowest cost from cost_keys
237 5         13 my $lowest_cost_key = $cost_min_max_tree->smallest();
238 5 50       72 if(!$lowest_cost_key){
239 0         0 croak "deepest_lowest_cost_leaf: object not found in min-max heap\n";
240             }
241            
242            
243 5 50       19 if(!$cost_hash_ref->{$lowest_cost_key}){
244             # no tree for this cost.
245 0         0 return;
246             }
247             else{
248 5         14 my $avltree = $cost_hash_ref->{$lowest_cost_key};
249 5         17 my $obj = $$avltree->pop_largest_oldest(); # get the deepest one
250 5         7 my $antecedent = $obj->{_antecedent};
251            
252             # if tree is empty, remove it from hash and heap.
253 5 100       22 if($$avltree->is_empty()){
254             #tree is empty, removing key $lowest_cost_key
255 1         3 delete $cost_hash_ref->{$lowest_cost_key};
256 1         4 $cost_min_max_tree->pop_smallest();
257             }
258              
259 5 100       22 if($antecedent){
260 3         4 $antecedent->{_descendants_on_queue} = $antecedent->{_descendants_on_queue} - 1;
261             }
262            
263 5         12 $obj->is_on_queue(0);
264 5         8 $self->{_size} = $self->{_size} - 1;
265 5         30 return $obj;
266             }
267             }
268              
269             sub deepest_lowest_cost_leaf_dont_remove
270             {
271 6     6 0 10 my ($self) = @_;
272            
273 6         7 my $avl_compare_func = $self->{f_avl_compare};
274 6         9 my $avl_get_key_func = $self->{f_avl_get_key};
275 6         14 my $avl_get_data_func = $self->{f_avl_get_data};
276            
277 6         7 my $cost_hash_ref = $self->{_hash_of_trees_ref};
278 6         14 my @cost_keys = (keys %$cost_hash_ref);
279              
280              
281              
282 6         9 my $cost_min_max_tree = $self->{_cost_min_max_tree};
283              
284 6 50       15 if(!@cost_keys){
285             # queue is empty
286 0         0 return;
287             }
288              
289             # get the lowest cost from @cost_keys
290 6         16 my $lowest_cost_key = $cost_min_max_tree->smallest();
291 6 50       170 if(!$lowest_cost_key){
292 0         0 croak "deepest_lowest_cost_leaf_dont_remove: object not found in min-max heap\n";
293             }
294            
295             # obtaining object from lowest-cost tree at cost: $lowest_cost_key\n";
296 6 50       22 if(!$cost_hash_ref->{$lowest_cost_key}){
297             # no tree for this cost.
298 0         0 return;
299             }
300             else{
301 6         11 my $avltree = $cost_hash_ref->{$lowest_cost_key};
302             # found tree at key $lowest_cost_key.
303              
304 6         18 my $obj = $$avltree->largest_oldest(); # get the deepest one
305 6         15 my $cost_key = $obj->$avl_get_key_func();
306 6         15 my $data = $obj->$avl_get_data_func();
307 6         22 return $obj;
308             }
309             }
310              
311              
312             # Return the shallowest, highest-cost leaf
313             sub shallowest_highest_cost_leaf
314             {
315 0     0 0 0 my ($self, $best, $succ, $str_function) = @_;
316            
317 0         0 my $cost_hash_ref = $self->{_hash_of_trees_ref};
318 0         0 my @cost_keys = (keys %$cost_hash_ref);
319            
320 0         0 my $cost_min_max_tree = $self->{_cost_min_max_tree};
321            
322 0         0 my $obj;
323              
324 0 0       0 if(!@cost_keys){
325 0         0 return;
326             }
327              
328             my $compare_func = sub{
329 0     0   0 my ($obj1, $obj2) = @_;
330 0         0 my $obj1_str = $str_function->($obj1);
331 0         0 my $obj2_str = $str_function->($obj2);
332 0 0       0 if($obj1_str eq $obj2_str){
333 0         0 return 1;
334             }
335 0         0 return 0;
336 0         0 };
337            
338             my $cmp_func = sub {
339 0     0   0 my ($phrase) = @_;
340             return sub{
341 0         0 my ($obj) = @_;
342 0         0 my $obj_phrase = $str_function->($obj);
343 0 0       0 if($obj_phrase eq $phrase){
344 0         0 return 1;
345             }
346             else{
347 0         0 return 0;
348             }
349             }
350 0         0 };
  0         0  
351              
352             # get the highest cost from @cost_keys
353            
354 0         0 my $highest_cost_key = $cost_min_max_tree->largest();
355 0 0       0 if(!$highest_cost_key){
356 0         0 croak "shallowest_highest_cost_leaf_dont_remove: object not found in min-max heap\n";
357             }
358              
359 0 0       0 if(!$cost_hash_ref->{$highest_cost_key}){
360             # no tree for this cost.
361 0         0 croak "shallowest_highest_cost_leaf: no tree at key $highest_cost_key\n";
362 0         0 return;
363             }
364             else{
365 0         0 my $least_depth = 0;
366 0         0 my $avltree;
367             my $depth_keys_iterator;
368              
369 0         0 while(1){
370              
371 0         0 while($least_depth == 0){
372 0         0 $avltree = $cost_hash_ref->{$highest_cost_key}; #tree with highest cost
373            
374             # get the deepest queue in the tree
375             # so we can use it to step backward to the smallest non-zero
376             # depth in the following loop
377 0         0 my $queue_at_largest_depth = $$avltree->largest();
378 0         0 $least_depth = $queue_at_largest_depth->key();
379 0         0 $depth_keys_iterator = $$avltree->get_keys_iterator();
380            
381              
382             # get lowest non-zero key of tree (smallest non-zero depth)
383 0         0 while (defined(my $key = $depth_keys_iterator->())){
384             #########################################################################
385             #
386             # Does this need to be a non-zero depth element? yes. (example: test68.lst)
387             #
388             #########################################################################
389 0 0       0 if($key != 0){
390 0         0 $least_depth = $key;
391 0         0 last;
392             }
393             }
394              
395             # if no non-zero depths, find the next highest key and loop back
396 0         0 my $next_highest_cost_key;
397 0 0       0 if($least_depth == 0){
398 0         0 $next_highest_cost_key = next_largest_element(\@cost_keys, $highest_cost_key);
399 0         0 $highest_cost_key = $next_highest_cost_key;
400 0 0       0 if(!$highest_cost_key){
401 0         0 print "no highest_cost_key found\n";
402 0         0 exit;
403             }
404             }
405             else{ # least depth is non-zero, so it's good
406 0         0 last;
407             }
408            
409             } # Now have a good highest_cost_key, with a tree that has a good non-zero key queue somewhere in it.
410            
411              
412 0         0 my $queue = $$avltree->get_queue($least_depth); # get the queue at least_depth
413              
414 0         0 my $queue_keys_iterator = $queue->get_keys_iterator();
415 0         0 my $queue_key = $queue_keys_iterator->(); # burn the first value from the iterator since we're getting first object on next line.
416 0         0 $obj = $$avltree->oldest_at($least_depth); # get the shallowest one that is not at zero depth
417            
418 0         0 my $i = 1;
419              
420 0   0     0 while($compare_func->($obj, $best) || $compare_func->($obj, $succ) || $obj->has_descendants_in_memory()){
      0        
421            
422 0 0       0 if($queue_key = $queue_keys_iterator->()){
423 0         0 $obj = $queue->lookup_by_key($queue_key);
424            
425             }
426             else{
427             # need a new least_depth. check if there are any more queues with non-zero depth in this tree.
428             # if not, need a new highest_cost_key.
429 0         0 $obj = undef;
430              
431 0         0 my $next_smallest = $depth_keys_iterator->();
432 0 0       0 if(!defined($next_smallest)){
433 0         0 last;
434             }
435             else{
436 0         0 $least_depth = $next_smallest;
437 0         0 $queue = $$avltree->get_queue($least_depth); # get the queue at least_depth
438 0         0 $queue_keys_iterator = $queue->get_keys_iterator();
439 0         0 $queue_key = $queue_keys_iterator->(); # burn the first value from the iterator
440 0         0 $obj = $$avltree->oldest_at($least_depth); # get the shallowest one that is not at zero depth
441 0         0 $i = 1;
442 0         0 next;
443             }
444             }
445            
446 0         0 $i++;
447             } # end while($compare_func->($obj, $best) || $compare_func->($obj, $succ) || $obj->has_descendants_in_memory())
448            
449             # done loop on last highest_cost_key. if obj is not found, get another highest_cost_key, and loop back again.
450 0 0       0 if(!$obj){
451 0         0 $least_depth = 0;
452 0         0 $highest_cost_key = next_largest_element(\@cost_keys, $highest_cost_key);
453             }
454             else{
455 0         0 last;
456             }
457            
458             } # end while(1)
459              
460 0         0 my $obj_str = $str_function->($obj);
461 0         0 $$avltree->remove($obj, $cmp_func->($obj_str));
462              
463 0 0       0 if($obj){
464 0         0 $self->{_size} = $self->{_size} - 1;
465            
466 0         0 my $antecedent = $obj->{_antecedent};
467 0 0       0 if($antecedent){
468 0         0 $antecedent->{_descendants_on_queue} = $antecedent->{_descendants_on_queue} - 1;
469             }
470 0         0 $obj->is_on_queue(0);
471 0 0       0 if($$avltree->is_empty()){
472 0         0 delete $cost_hash_ref->{$highest_cost_key};
473            
474              
475 0         0 $cost_min_max_tree->remove($highest_cost_key);
476             }
477 0         0 return $obj;
478             }
479             else{
480 0         0 return;
481             }
482             }
483             }
484              
485              
486             sub largest_element
487             {
488 0     0 0 0 my ($array) = @_;
489            
490 0 0       0 if(!@$array){
491 0         0 return;
492             }
493             else{
494 0         0 my $i = 0;
495 0         0 my $largest = $$array[$i];
496 0         0 for($i = 1; $i < @$array; $i++)
497             {
498 0 0       0 if($largest < $$array[$i]){
499 0         0 $largest = $$array[$i];
500             }
501             }
502 0         0 return $largest;
503             }
504             }
505              
506              
507             sub next_largest_element
508             {
509 0     0 0 0 my ($array, $val) = @_;
510            
511 0 0       0 if(!@$array){
512 0         0 return;
513             }
514             else{
515 0         0 my $i = 0;
516 0         0 my $largest = -1;
517 0         0 for($i = 0; $i < @$array; $i++)
518             {
519 0 0 0     0 if($$array[$i] < $val && $largest < $$array[$i]){
520 0         0 $largest = $$array[$i];
521             }
522             }
523              
524 0 0       0 if($largest != -1){
525 0         0 return $largest;
526             }
527             else{
528 0         0 return;
529             }
530             }
531             }
532              
533              
534              
535             sub next_smallest_non_zero_element
536             {
537 0     0 0 0 my ($array, $val) = @_;
538            
539 0         0 my $max = 2^32-1;
540              
541 0 0       0 if(!@$array){
542 0         0 return;
543             }
544             else{
545 0         0 my $i = 0;
546 0         0 my $smallest = $max;
547 0         0 for($i = 0; $i < @$array; $i++)
548             {
549 0 0 0     0 if($$array[$i] > $val && $$array[$i] < $smallest){
550 0         0 $smallest = $$array[$i];
551             }
552             }
553              
554 0 0       0 if($smallest != $max){
555 0         0 return $smallest;
556             }
557             else{
558 0         0 return;
559             }
560             }
561             }
562              
563              
564             sub smallest_element
565             {
566 0     0 0 0 my ($array) = @_;
567 0 0       0 if(!@$array){
568 0         0 return;
569             }
570             else{
571 0         0 my $i = 0;
572 0         0 my $smallest = $$array[$i];
573 0         0 for($i = 1; $i < @$array; $i++){
574 0 0       0 if($smallest > $$array[$i]){
575 0         0 $smallest = $$array[$i];
576             }
577             }
578 0         0 return $smallest;
579             }
580             }
581              
582              
583              
584             sub get_size{
585 0     0 0 0 my ($self) = @_;
586 0         0 my $cost_hash_ref = $self->{_hash_of_trees_ref};
587 0         0 my $size = 0;
588            
589 0         0 foreach my $key (keys %$cost_hash_ref){
590 0         0 my $tree = $cost_hash_ref->{$key};
591 0         0 my $tree_size = $$tree->get_size();
592 0         0 $size += $tree_size;
593             }
594 0         0 return $size;
595             }
596              
597              
598              
599             sub fp_compare
600             {
601 9     9 0 43 my ($obj1, $obj2) = @_;
602            
603 9 100       15 if(fp_equal($obj1, $obj2, 10)){
604 3         7 return 0;
605             }
606 6 100       27 if($obj1 < $obj2){
607 3         7 return -1;
608             }
609 3         7 return 1;
610             }
611              
612             sub fp_equal {
613 9     9 0 13 my ($A, $B, $dp) = @_;
614              
615 9         61 return sprintf("%.${dp}g", $A) eq sprintf("%.${dp}g", $B);
616             }
617              
618              
619              
620              
621              
622              
623              
624              
625              
626              
627              
628              
629              
630              
631              
632              
633              
634              
635              
636              
637              
638              
639              
640              
641              
642              
643              
644              
645              
646              
647              
648              
649              
650              
651              
652              
653              
654              
655              
656              
657              
658              
659              
660              
661              
662              
663              
664              
665              
666              
667              
668              
669              
670              
671              
672              
673              
674              
675              
676              
677              
678              
679              
680              
681              
682              
683              
684              
685              
686              
687              
688              
689              
690              
691              
692              
693              
694              
695              
696             1; # so the require or use succeeds
697