summaryrefslogtreecommitdiff
path: root/lib/Data/MultiValued/RangeContainer.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Data/MultiValued/RangeContainer.pm')
-rw-r--r--lib/Data/MultiValued/RangeContainer.pm83
1 files changed, 79 insertions, 4 deletions
diff --git a/lib/Data/MultiValued/RangeContainer.pm b/lib/Data/MultiValued/RangeContainer.pm
index e9b1b62..0bfe8cd 100644
--- a/lib/Data/MultiValued/RangeContainer.pm
+++ b/lib/Data/MultiValued/RangeContainer.pm
@@ -5,6 +5,27 @@ use MooseX::Types::Moose qw(Num Str Any Undef ArrayRef);
use MooseX::Types::Structured qw(Dict);
use Data::MultiValued::Exceptions;
+# ABSTRACT: container for ranged values
+
+=head1 DESCRIPTION
+
+Please don't use this module directly, use L<Data::MultiValued::Ranges>.
+
+This module implements the storage for ranged data. It's similar to
+L<Array::IntSpan>, but simpler (and slower).
+
+A range is defined by a pair of numbers, C<from> and C<to>, and it
+contains C<< Num $x : $min <= $x < $max >>. C<undef> is treated as
+"inf" (negative infinity if used as C<from> or C<at>, positive
+infinity if used as C<to>).
+
+The internal representation of a range is a hash with three keys,
+C<from> C<to> C<value>.
+
+=head1 METHODS
+
+=cut
+
has _storage => (
is => 'rw',
isa => ArrayRef[
@@ -18,6 +39,16 @@ has _storage => (
default => sub { [ ] },
);
+=head2 C<get>
+
+ my $value = $obj->get({ at => $point });
+
+Retrieves the range that includes the given point. Throws a
+L<Data::MultiValued::Exceptions::RangeNotFound> exception if no range
+includes the point.
+
+=cut
+
sub get {
my ($self,$args) = @_;
@@ -45,6 +76,8 @@ sub _cmp {
return $a <=> $b;
}
+# a binary search would be a good idea.
+
sub _get_slot_at {
my ($self,$at) = @_;
@@ -56,6 +89,10 @@ sub _get_slot_at {
return;
}
+# this is quite probably uselessly slow: we don't really need all of
+# @before and @after, we just need to know if they're not empty; also,
+# a binary search would be a good idea.
+
sub _partition_slots {
my ($self,$from,$to) = @_;
@@ -80,7 +117,18 @@ sub _partition_slots {
return \@before,\@overlap,\@after;
}
-sub set_or_create {
+=head2 C<get_or_create>
+
+ $obj->get_or_create({ from => $min, to => $max });
+
+Retrieves the range that has the given extremes. If no such range
+exists, creates a new range, splicing any existing overlapping range,
+and returns it. Throws L<Data::MultiValued::Exceptions::BadRange> if
+C<< $min > $max >>.
+
+=cut
+
+sub get_or_create {
my ($self,$args) = @_;
my $from = $args->{from};
@@ -103,6 +151,19 @@ sub set_or_create {
return $range;
}
+=head2 C<clear>
+
+ $obj->clear({ from => $min, to => $max });
+
+Removes the range that has the given extremes. If no such range
+exists, splices any existing overlapping range so that C<<
+$obj->get({at => $point }) >> for any C<< $min <= $point < $max >>
+will die.
+
+Throws L<Data::MultiValued::Exceptions::BadRange> if C<< $min > $max >>.
+
+=cut
+
sub clear {
my ($self,$args) = @_;
@@ -133,10 +194,16 @@ sub _clear_slot {
$self->_splice_slot($from,$to);
}
+# Most of the splicing mechanics is here. Given a range and something
+# to put in it, do "the right thing"
+
sub _splice_slot {
my ($self,$from,$to,$new) = @_;
- if (!@{$self->_storage}) { # empty
+ # if !$new, it's like C<splice> without a replacement list: we
+ # just delete the range
+
+ if (!@{$self->_storage}) { # empty, just store
push @{$self->_storage},$new if $new;
return $new;
}
@@ -144,27 +211,33 @@ sub _splice_slot {
my ($before,$overlap,$after) = $self->_partition_slots($from,$to);
if (!@$before && !@$overlap) {
+ # nothing before, nothing overlapping: put $new at the beginning
unshift @{$self->_storage},$new if $new;
return $new;
}
if (!@$after && !@$overlap) {
+ # nothing after, nothing overlapping: put $new at the end
push @{$self->_storage},$new if $new;
return $new;
}
- # by costruction, the first and the last may have to be split, all
- # others must be removed
+ # ok, we have to insert in the middle of things, and maybe we have
+ # to trim existing ranges
+
my $first_to_replace;
my $how_many = @$overlap;
my @replacement = $new ? ($new) : ();
if ($how_many > 0) { # we have to splice
+ # by costruction, the first and the last may have to be split, all
+ # others must be removed
$first_to_replace = $overlap->[0];
my $last_to_replace = $overlap->[-1];
my $first = $self->_storage->[$first_to_replace];
my $last = $self->_storage->[$last_to_replace];
+ # does the first overlapping range need trimming?
if (_cmp($first->{from},$from,0,0)<0
&& _cmp($first->{to},$from,1,0)>=0) {
unshift @replacement, {
@@ -173,6 +246,7 @@ sub _splice_slot {
value => $first->{value},
}
}
+ # does the last overlapping range need trimming?
if (_cmp($last->{from},$to,0,1)<=0
&& _cmp($last->{to},$to,1,1)>0) {
push @replacement, {
@@ -183,6 +257,7 @@ sub _splice_slot {
}
}
else {
+ # no overlaps, just insert between @before and @after
$first_to_replace = $before->[-1]+1;
}