PDK-Utils/lib/PDK/Utils/Set.pm
package PDK::Utils::Set;
use utf8;
use v5.30;
use Moose;
use POSIX qw(floor ceil);
use experimental 'smartmatch';
use namespace::autoclean;
has mins => (is => 'rw', isa => 'ArrayRef[Int]', default => sub { [] },);
has maxs => (is => 'rw', isa => 'ArrayRef[Int]', default => sub { [] },);
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
if (@_ == 0) {
return $class->$orig();
}
elsif (@_ == 1 and ref($_[0]) eq __PACKAGE__) {
my $setObj = $_[0];
return $class->$orig(mins => [@{$setObj->mins}], maxs => [@{$setObj->maxs}]);
}
elsif (@_ == 2 and defined $_[0] and defined $_[1] and $_[0] =~ /^\d+$/o and $_[1] =~ /^\d+$/o) {
my ($MIN, $MAX) = $_[0] < $_[1] ? ($_[0], $_[1]) : ($_[1], $_[0]);
return $class->$orig(mins => [$MIN], maxs => [$MAX]);
}
else {
return $class->$orig(@_);
}
};
sub BUILD {
my $self = shift;
my @ERROR;
my $lengthOfMin = @{$self->mins};
my $lengthOfMax = @{$self->maxs};
if ($lengthOfMin != $lengthOfMax) {
push(@ERROR, 'Attribute (mins) and (maxs) must has same length at constructor ' . __PACKAGE__);
}
for (my $i = 0; $i < $lengthOfMin; $i++) {
if ($self->mins->[$i] > $self->maxs->[$i]) {
push(@ERROR, 'Attribute (mins) must not bigger than (maxs) in the same index at constructor ' . __PACKAGE__);
last;
}
}
if (@ERROR > 0) {
confess(join(', ', @ERROR));
}
}
sub length {
my $self = shift;
my $lengthOfMin = @{$self->mins};
my $lengthOfMax = @{$self->maxs};
confess("ERROR: Attribute (mins) 's length($lengthOfMin) not equal (maxs) 's length($lengthOfMax)")
if $lengthOfMin != $lengthOfMax;
return $lengthOfMin;
}
sub min {
my $self = shift;
return ($self->length > 0 ? $self->mins->[0] : undef);
}
sub max {
my $self = shift;
return ($self->length > 0 ? $self->maxs->[-1] : undef);
}
sub dump {
my $self = shift;
my $length = $self->length;
for (my $i = 0; $i < $length; $i++) {
say $self->mins->[$i] . " " . $self->maxs->[$i];
}
}
sub addToSet {
my ($self, $MIN, $MAX) = @_;
if ($MIN > $MAX) {
($MAX, $MIN) = ($MIN, $MAX);
}
my $length = $self->length;
if ($length == 0) {
$self->mins([$MIN]);
$self->maxs([$MAX]);
return;
}
my $minArray = $self->mins;
my $maxArray = $self->maxs;
my $index;
for (my $i = 0; $i < $length; $i++) {
if ($MIN < $minArray->[$i]) {
$index = $i;
last;
}
}
$index = $length if not defined $index;
my (@min, @max);
push(@min, @{$minArray}[0 .. $index - 1]);
push(@max, @{$maxArray}[0 .. $index - 1]);
push(@min, $MIN);
push(@max, $MAX);
push(@min, @{$minArray}[$index .. $length - 1]);
push(@max, @{$maxArray}[$index .. $length - 1]);
$self->mins(\@min);
$self->maxs(\@max);
}
sub mergeToSet {
my $self = shift;
if (@_ == 1 and ref($_[0]) eq __PACKAGE__) {
my $setObj = $_[0];
my $length = $setObj->length;
for (my $i = 0; $i < $length; $i++) {
$self->_mergeToSet($setObj->mins->[$i], $setObj->maxs->[$i]);
}
}
else {
$self->_mergeToSet(@_);
}
}
sub _mergeToSet {
my ($self, $MIN, $MAX) = @_;
if ($MIN > $MAX) {
($MAX, $MIN) = ($MIN, $MAX);
}
my $length = $self->length;
if ($length == 0) {
$self->mins([$MIN]);
$self->maxs([$MAX]);
return;
}
my $minArray = $self->mins;
my $maxArray = $self->maxs;
my ($minIndex, $maxIndex) = (-1, $length);
MIN: {
for (my $i = 0; $i < $length; $i++) {
if ($MIN >= $minArray->[$i] and $MIN <= $maxArray->[$i] + 1) {
$minIndex = $i;
last MIN;
}
elsif ($MIN < $minArray->[$i]) {
$minIndex += 0.5;
last MIN;
}
else {
$minIndex++;
}
}
$minIndex += 0.5;
}
MAX: {
for (my $j = $length - 1; $j >= $minIndex; $j--) {
if ($MAX >= $minArray->[$j] - 1 and $MAX <= $maxArray->[$j]) {
$maxIndex = $j;
last MAX;
}
elsif ($MAX > $maxArray->[$j]) {
$maxIndex -= 0.5;
last MAX;
}
else {
$maxIndex--;
}
}
$maxIndex -= 0.5;
}
my $minIndexInt = POSIX::ceil($minIndex);
my $maxIndexInt = POSIX::floor($maxIndex);
my $isMinIndexInSet = $minIndex == $minIndexInt ? 1 : 0;
my $isMaxIndexInSet = $maxIndex == $maxIndexInt ? 1 : 0;
my (@min, @max);
push(@min, @{$minArray}[0 .. $minIndexInt - 1]);
push(@max, @{$maxArray}[0 .. $minIndexInt - 1]);
push(@min, $isMinIndexInSet ? $minArray->[$minIndexInt] : $MIN);
push(@max, $isMaxIndexInSet ? $maxArray->[$maxIndexInt] : $MAX);
push(@min, @{$minArray}[$maxIndexInt + 1 .. $length - 1]);
push(@max, @{$maxArray}[$maxIndexInt + 1 .. $length - 1]);
$self->mins(\@min);
$self->maxs(\@max);
}
sub compare {
my ($self, $setObj) = @_;
if ($self->isEqual($setObj)) {
return 'equal';
}
elsif ($self->_isContain($setObj)) {
return 'containButNotEqual';
}
elsif ($self->_isBelong($setObj)) {
return 'belongButNotEqual';
}
else {
return 'other';
}
}
sub isEqual {
my ($self, $setObj) = @_;
return (@{$self->mins} ~~ @{$setObj->mins} and @{$self->maxs} ~~ @{$setObj->maxs});
}
sub notEqual {
my ($self, $setObj) = @_;
return !(@{$self->mins} ~~ @{$setObj->mins} and @{$self->maxs} ~~ @{$setObj->maxs});
}
sub isContain {
my ($self, $setObj) = @_;
if ($self->isEqual($setObj)) {
return 1;
}
else {
return $self->_isContain($setObj);
}
}
sub _isContain {
my ($self, $setObj) = @_;
my $copyOfSelf = PDK::Utils::Set->new($self);
$copyOfSelf->mergeToSet($setObj);
return $self->isEqual($copyOfSelf);
}
sub isContainButNotEqual {
my ($self, $setObj) = @_;
if ($self->isEqual($setObj)) {
return 0;
}
else {
return $self->_isContain($setObj);
}
}
sub isBelong {
my ($self, $setObj) = @_;
if ($self->isEqual($setObj)) {
return 1;
}
else {
return $self->_isBelong($setObj);
}
}
sub _isBelong {
my ($self, $setObj) = @_;
my $copyOfSetObj = PDK::Utils::Set->new($setObj);
$copyOfSetObj->mergeToSet($self);
return $setObj->isEqual($copyOfSetObj);
}
sub isBelongButNotEqual {
my ($self, $setObj) = @_;
if ($self->isEqual($setObj)) {
return 0;
}
else {
return $self->_isBelong($setObj);
}
}
sub interSet {
my $result = PDK::Utils::Set->new;
my ($self, $setObj) = @_;
if ($self->length == 0) {
return $self;
}
if ($setObj->length == 0) {
return $setObj;
}
my $i = 0;
my $j = 0;
while ($i < $self->length and $j < $setObj->length) {
my @rangeSet1 = ($self->mins->[$i], $self->maxs->[$i]);
my @rangeSet2 = ($setObj->mins->[$j], $setObj->maxs->[$j]);
my ($min, $max) = $self->interRange(\@rangeSet1, \@rangeSet2);
$result->_mergeToSet($min, $max) if defined $min;
if ($setObj->maxs->[$j] > $self->maxs->[$i]) {
$i++;
}
elsif ($setObj->maxs->[$j] == $self->maxs->[$i]) {
$i++;
$j++;
}
else {
$j++;
}
}
return $result;
}
sub interRange {
my ($self, $rangeSet1, $rangeSet2) = @_;
my ($min, $max);
$min = $rangeSet1->[0] > $rangeSet2->[0] ? $rangeSet1->[0] : $rangeSet2->[0];
$max = $rangeSet1->[1] < $rangeSet2->[1] ? $rangeSet1->[1] : $rangeSet2->[1];
if ($min > $max) {
return;
}
else {
return ($min, $max);
}
}
for my $func (qw(addToSet _mergeToSet)) {
before $func => sub {
shift;
unless (@_ == 2 and $_[0] =~ /^\d+$/o and $_[1] =~ /^\d+$/o) {
confess("ERROR: function $func can only has two numeric argument");
}
}
}
for my $func (qw(compare isEqual isContain _isContain isContainButNotEqual isBelong _isBelong isBelongButNotEqual)) {
before $func => sub {
shift;
confess("ERROR: the first param of function($func) is not a PDK::Utils::Set") if ref($_[0]) ne 'PDK::Utils::Set';
}
}
__PACKAGE__->meta->make_immutable;
1;