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   8 use strict;
  1         3  
  1         39  
4 1     1   9 use warnings;
  1         3  
  1         64  
5              
6             our $VERSION = '0.05';
7              
8 1     1   9 use Carp qw/croak confess carp/;
  1         3  
  1         79  
9 1     1   10 use IO::File;
  1         3  
  1         192  
10 1     1   775 use Time::HiRes qw/gettimeofday/;
  1         1349  
  1         4  
11 1     1   178 use File::Map qw/map_file/;
  1         2  
  1         7  
12 1     1   68 use List::Util qw/reduce/;
  1         2  
  1         61  
13 1     1   6 use Set::SegmentTree::ValueLookup;
  1         2  
  1         20  
14 1     1   515 use Readonly;
  1         3079  
  1         1044  
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 5 my ( $class, $options ) = @_;
32 1 50       5 return bless { locked => 0, segment_list => [], %{ $options || {} } },
  1         18  
33             $class;
34             }
35              
36             sub build {
37 1     1 1 10 my ($self) = @_;
38 1         3 $self->build_tree( @{ $self->{segment_list} } );
  1         9  
39 1         12 return Set::SegmentTree->deserialize( $self->serialize );
40             }
41              
42             sub new {
43 1     1 1 146 my ( $class, @list ) = @_;
44 1         5 my $options = {};
45 1 50       8 if ( 'HASH' eq ref @list ) { $options = pop @list; }
  0         0  
46 1         8 return $class->new_instance($options)->insert(@list);
47             }
48              
49             sub insert {
50 1     1 1 5 my ( $self, @list ) = @_;
51 1 50       12 confess 'This tree already built. Make a new one' if $self->{locked};
52 1         4 push @{ $self->{segment_list} }, @list;
  1         7  
53 1         8 return $self;
54             }
55              
56             sub serialize {
57 2     2 0 9 my ($self) = @_;
58 2 50       12 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         46 created => time
64             );
65 2         14 return $t->serialize;
66             }
67              
68             sub to_file {
69 1     1 1 7 my ( $self, $outfile ) = @_;
70 1 50       8 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         17 my $out = IO::File->new( $outfile, '>:raw' );
76 1         346 $out->print( $self->serialize );
77 1         136 undef $out;
78 1         39 return -s $outfile;
79             }
80              
81             sub endpoint {
82 58     58 0 180 my ( $self, $offset, $which ) = @_;
83 58         417 return $self->{elist}->[$offset]->[$which];
84             }
85              
86             sub endpoints {
87 1     1 0 6 my ( $self, @endpoints ) = @_;
88 14         46 my @list = sort { $a <=> $b }
89 1         4 map { ( $_->[$INTERVAL_MIN], $_->[$INTERVAL_MAX] ) } @endpoints;
  4         56  
90 1         14 return @list;
91             }
92              
93             sub place_intervals {
94 1     1 0 4 my ( $self, @intervals ) = @_;
95 1         3 foreach my $node ( @{ $self->{nodelist} } ) {
  1         3  
96 29 100       132 next if exists $node->{low};
97             $node->{segments} = [
98 24         150 map { $_->[$INTERVAL_ID] } grep {
99 15         24 $node->{min} >= $_->[$INTERVAL_MIN]
100 60 100       516 && $node->{max} <= $_->[$INTERVAL_MAX];
101             } @intervals
102             ];
103             }
104 1         4 return;
105             }
106              
107             sub build_elementary_list {
108 1     1 0 6 my ( $self, @segment_list ) = @_;
109             my ($elementary) = reduce {
110 7     7   24 my ( $d, $c ) = ( $a, $a );
111 7 100       31 if ( 'ARRAY' ne ref $a ) {
112 1         5 $d = [ [ $c, $c ], $c ];
113             }
114 7         16 $c = pop @{$d};
  7         20  
115 7         16 [ @{$d}, [ $c, $b ], [ $b, $b ], $b ];
  7         33  
116             }
117 1         13 $self->endpoints(@segment_list);
118 1         9 pop @{$elementary}; # extra bit
  1         5  
119 1         5 $self->{elist} = $elementary;
120 1         4 return $elementary;
121             }
122              
123             sub build_tree {
124 1     1 0 5 my ( $self, @segment_list ) = @_;
125 1 50       7 if ( $self->{locked} ) {
126 0         0 croak 'This tree is immutable. Build a new one.';
127             }
128 1         4 $self->{locked} = 1;
129 1         7 my $elementary = $self->build_elementary_list(@segment_list);
130              
131 1 50       6 if ( $self->{verbose} ) {
132 0         0 warn "Building binary tree\n";
133             }
134 1         16 my $st = gettimeofday;
135 1         4 $self->{tree} = $self->build_binary( 0, $#{$elementary} );
  1         8  
136 1 50       4 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         6 $self->place_intervals(@segment_list);
147 1         6 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       6 if $self->{verbose};
154 1         4 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 66 my ( $self, $from, $to ) = @_;
161 29         55 $cc++;
162 29         103 my $mid = int( ( $to - $from ) / $INTERVALS_PER_NODE ) + $from;
163 29         224 my $node = {
164             min => $self->endpoint( $from, $ELEMENTARY_MIN ),
165             max => $self->endpoint( $to, $ELEMENTARY_MAX ),
166             };
167              
168 29 100       92 if ( $from != $to ) {
169 14         52 $node->{low} = $self->build_binary( $from, $mid );
170 14         44 $node->{high} = $self->build_binary( $mid + 1, $to );
171             }
172 29         51 push @{ $self->{nodelist} }, $node;
  29         71  
173 29         53 return $#{ $self->{nodelist} };
  29         73  
174             }
175              
176             1;
177             __END__