File Coverage

blib/lib/Set/SegmentTree/Builder.pm
Criterion Covered Total %
statement 106 113 93.8
branch 17 26 65.3
condition n/a
subroutine 22 22 100.0
pod 4 12 33.3
total 149 173 86.1


line stmt bran cond sub pod time code
1             package Set::SegmentTree::Builder;
2              
3 1     1   10 use strict;
  1         4  
  1         42  
4 1     1   9 use warnings;
  1         3  
  1         67  
5              
6             our $VERSION = '0.06';
7              
8 1     1   8 use Carp qw/croak confess carp/;
  1         4  
  1         76  
9 1     1   9 use IO::File;
  1         4  
  1         192  
10 1     1   822 use Time::HiRes qw/gettimeofday/;
  1         1834  
  1         7  
11 1     1   239 use File::Map qw/map_file/;
  1         3  
  1         9  
12 1     1   108 use List::Util qw/reduce/;
  1         4  
  1         77  
13 1     1   10 use Set::SegmentTree::ValueLookup;
  1         3  
  1         37  
14 1     1   711 use Readonly;
  1         5075  
  1         1977  
15              
16             Readonly our $INTERVAL_ID => 0;
17             Readonly our $INTERVAL_MIN => 1;
18             Readonly our $INTERVAL_MAX => 2;
19             Readonly our $ELEMENTARY_MIN => 0;
20             Readonly our $ELEMENTARY_MAX => 1;
21             Readonly our $TRUE => 1;
22             Readonly our $MS_IN_NS => 1000;
23             Readonly our $INTERVALS_PER_NODE => 2;
24              
25             #########################
26             my $cc = 0;
27             my $icc = 0;
28             #########################
29              
30             sub new_instance {
31 1     1 0 3 my ( $class, $options ) = @_;
32 1 50       3 return bless { locked => 0, segment_list => [], %{ $options || {} } },
  1         8  
33             $class;
34             }
35              
36             sub build {
37 1     1 1 6 my ($self) = @_;
38 1         2 $self->build_tree( @{ $self->{segment_list} } );
  1         5  
39 1         4 return Set::SegmentTree->deserialize( $self->serialize );
40             }
41              
42             sub new {
43 1     1 1 125 my ( $class, @list ) = @_;
44 1         3 my $options = {};
45 1 50       4 if ( 'HASH' eq ref @list ) { $options = pop @list; }
  0         0  
46 1         5 return $class->new_instance($options)->insert(@list);
47             }
48              
49             sub insert {
50 1     1 1 4 my ( $self, @list ) = @_;
51 1 50       6 confess 'This tree already built. Make a new one' if $self->{locked};
52 1         2 push @{ $self->{segment_list} }, @list;
  1         4  
53 1         5 return $self;
54             }
55              
56             sub serialize {
57 2     2 0 8 my ($self) = @_;
58 2 50       9 confess 'Cannot serialized unlocked tree' if !$self->{locked};
59              
60             my $t = Set::SegmentTree::ValueLookup->new(
61             root => $self->{tree},
62             nodes => $self->{nodelist},
63 2         27 created => time
64             );
65 2         11 return $t->serialize;
66             }
67              
68             sub to_file {
69 1     1 1 4 my ( $self, $outfile ) = @_;
70 1 50       5 if ( !$self->{locked} ) {
71 0         0 carp 'you asked for to_file without building first. '
72             . 'Building now. This is expensive.'
73             . $self->build;
74             }
75 1         9 my $out = IO::File->new( $outfile, '>:raw' );
76 1         288 $out->print( $self->serialize );
77 1         1528 undef $out;
78 1         76 return -s $outfile;
79             }
80              
81             sub endpoint {
82 58     58 0 151 my ( $self, $offset, $which ) = @_;
83 58         333 return $self->{elist}->[$offset]->[$which];
84             }
85              
86             sub endpoints {
87 1     1 0 4 my ( $self, @endpoints ) = @_;
88 14         50 my @list = sort { $a <=> $b }
89 1         3 map { ( $_->[$INTERVAL_MIN], $_->[$INTERVAL_MAX] ) } @endpoints;
  4         49  
90 1         13 return @list;
91             }
92              
93             sub place_intervals {
94 1     1 0 4 my ( $self, @intervals ) = @_;
95 1         2 foreach my $node ( @{ $self->{nodelist} } ) {
  1         3  
96 29 100       148 next if exists $node->{low};
97             $node->{segments} = [
98 24         164 map { $_->[$INTERVAL_ID] } grep {
99 15         32 $node->{min} >= $_->[$INTERVAL_MIN]
100 60 100       609 && $node->{max} <= $_->[$INTERVAL_MAX];
101             } @intervals
102             ];
103             }
104 1         3 return;
105             }
106              
107             sub build_elementary_list {
108 1     1 0 3 my ( $self, @segment_list ) = @_;
109             my ($elementary) = reduce {
110 7     7   19 my ( $d, $c ) = ( $a, $a );
111 7 100       29 if ( 'ARRAY' ne ref $a ) {
112 1         5 $d = [ [ $c, $c ], $c ];
113             }
114 7         12 $c = pop @{$d};
  7         18  
115 7         11 [ @{$d}, [ $c, $b ], [ $b, $b ], $b ];
  7         25  
116             }
117 1         9 $self->endpoints(@segment_list);
118 1         6 pop @{$elementary}; # extra bit
  1         2  
119 1         3 $self->{elist} = $elementary;
120 1         3 return $elementary;
121             }
122              
123             sub build_tree {
124 1     1 0 3 my ( $self, @segment_list ) = @_;
125 1 50       4 if ( $self->{locked} ) {
126 0         0 croak 'This tree is immutable. Build a new one.';
127             }
128 1         2 $self->{locked} = 1;
129 1         5 my $elementary = $self->build_elementary_list(@segment_list);
130              
131 1 50       4 if ( $self->{verbose} ) {
132 0         0 warn "Building binary tree\n";
133             }
134 1         10 my $st = gettimeofday;
135 1         2 $self->{tree} = $self->build_binary( 0, $#{$elementary} );
  1         6  
136 1 50       11 if ( $self->{verbose} ) {
137 0         0 my $et = gettimeofday;
138 0         0 warn "took $cc calls "
139             . sprintf( '%0.3f', ( ( $et - $st ) * $MS_IN_NS ) / $cc )
140             . ' ms per ('
141             . ( $et - $st )
142             . " elap)\n";
143 0         0 warn "placing intervals...\n";
144             }
145 1         5 my $ist = gettimeofday;
146 1         4 $self->place_intervals(@segment_list);
147 1         5 my $iet = gettimeofday;
148             warn "took $icc segment placements "
149             . sprintf( '%0.3f', ( ( $iet - $ist ) * $MS_IN_NS ) / $icc )
150             . ' ms per ('
151             . ( $iet - $ist )
152             . " elapsed)\n"
153 1 50       4 if $self->{verbose};
154 1         3 return $self;
155             }
156              
157             # from being offset into elementary list
158             # to being offset into elementary list
159             sub build_binary {
160 29     29 0 58 my ( $self, $from, $to ) = @_;
161 29         49 $cc++;
162 29         73 my $mid = int( ( $to - $from ) / $INTERVALS_PER_NODE ) + $from;
163 29         177 my $node = {
164             min => $self->endpoint( $from, $ELEMENTARY_MIN ),
165             max => $self->endpoint( $to, $ELEMENTARY_MAX ),
166             };
167              
168 29 100       74 if ( $from != $to ) {
169 14         40 $node->{low} = $self->build_binary( $from, $mid );
170 14         37 $node->{high} = $self->build_binary( $mid + 1, $to );
171             }
172 29         48 push @{ $self->{nodelist} }, $node;
  29         60  
173 29         46 return $#{ $self->{nodelist} };
  29         65  
174             }
175              
176             1;
177             __END__