use
5.016;
use
failures
qw/levels::mismatch levels::number/
;
my
$overload_info
;
my
$super_dotassign
;
BEGIN {
my
$overload_info
= overload_info(
'PDL'
);
$super_dotassign
=
$overload_info
->{
'.='
}{code};
}
'=='
=> \
&equal
,
'!='
=> \
¬_equal
,
fallback
=> 1;
eval
q{
use overload (
'""' => \&PDL::Factor::string,
);
}
;
sub
_check_levels {
my
(
$class
,
$levels
) =
@_
;
my
%levels
;
for
my
$i
( 0 ..
$#$levels
) {
if
( (
$levels
{
$levels
->[
$i
] }++ ) > 0 ) {
die
"levels element [$i] is duplicated"
;
}
}
}
sub
_extract_levels {
my
(
$class
,
$x
) =
@_
;
state
$levels_from_arrayref
=
sub
{
my
(
$aref
) =
@_
;
my
@uniq
=
sort
{
$a
cmp
$b
} List::AllUtils::uniq(
@$aref
);
return
\
@uniq
;
};
if
(
$x
->
$_DOES
(
'PDL'
) ) {
$x
=
$x
->slice( which(
$x
->isgood ) )
if
$x
->badflag;
if
(
$x
->
$_DOES
(
'PDL::SV'
) ) {
return
$levels_from_arrayref
->( [
$x
->list ] );
}
else
{
return
$levels_from_arrayref
->( [
$x
->uniq->qsort->list ] );
}
}
else
{
return
$levels_from_arrayref
->(
$x
);
}
}
sub
new {
my
(
$class
,
@args
) =
@_
;
my
$data
;
if
(
@args
% 2 != 0 ) {
$data
=
shift
@args
;
}
my
%opt
=
@args
;
if
(
$data
->
$_DOES
(
'PDL::Factor'
)) {
unless
(
exists
$opt
{levels}) {
return
$data
->copy;
}
my
@levels
= @{
delete
$opt
{levels} };
my
@integer_old
=
$data
->{PDL}->list;
my
$i
= 0;
my
%levels_old
=
map
{
$i
++ =>
$_
} @{
$data
->levels };
$i
= 0;
my
%levels_new
=
map
{
$_
=>
$i
++ }
@levels
;
my
@integer_new
=
map
{
my
$enum
=
$levels_old
{
$_
};
defined
$enum
?
$levels_new
{
$enum
} :
'nan'
;
}
@integer_old
;
return
$class
->new(
integer
=> \
@integer_new
,
levels
=> \
@levels
,
%opt
);
}
my
$enum
=
$opt
{integer} //
$data
;
if
( !
ref
(
$enum
) ) {
$enum
= [
$enum
];
}
my
$levels
;
if
(
my
$levels_opt
=
$opt
{levels} ) {
$class
->_check_levels(
$levels_opt
);
$levels
=
$levels_opt
;
}
else
{
$levels
=
$class
->_extract_levels(
$enum
);
}
unless
(
exists
$opt
{integer}) {
$enum
=
$enum
->
$_DOES
(
'PDL'
) ?
$enum
->unpdl : dclone(
$enum
);
my
$i
= 0;
my
%levels
=
map
{
$_
=>
$i
++; }
@$levels
;
rmap {
$_
= (
$levels
{
$_
} // -1);
}
$enum
;
}
my
$self
=
$class
->initialize();
my
$integer
= PDL::Core::indx(
$enum
)->setvaltobad(-1);
$integer
=
$integer
->setbadif(
$integer
>=
@$levels
);
$self
->{PDL} .=
$integer
;
$self
->levels(
$levels
);
my
$class_ordered
=
'PDL::Factor::Ordered'
;
if
(
$opt
{ordered} and not
$class
->DOES(
$class_ordered
)) {
load
$class_ordered
;
bless
$self
,
$class_ordered
;
}
return
$self
;
}
sub
levels {
my
$self
=
shift
;
if
(
@_
) {
my
$val
=
(
@_
== 1 and is_plain_arrayref(
$_
[0] ) ) ?
$_
[0] : \
@_
;
if
(
defined
$self
->{_levels} and
@$val
!=
$self
->number_of_levels ) {
failure::levels::number->throw(
{
msg
=>
"incorrect number of levels"
,
trace
=> failure->croak_trace,
}
);
}
$self
->{_levels} =
$val
;
}
return
$self
->{_levels};
}
sub
initialize {
my
(
$class
) =
@_
;
return
bless
( {
PDL
=> PDL::Core::null },
$class
);
}
sub
glue {
my
(
$self
,
$dim
,
@piddles
) =
@_
;
my
$class
=
ref
(
$self
);
if
(
$dim
!= 0) {
die
(
'PDL::Factor::glue does not yet support $dim != 0'
);
}
my
$data
= [
map
{ @{
$_
->unpdl} } (
$self
,
@piddles
) ];
my
$new
=
$class
->new(
integer
=>
$data
,
levels
=>
$self
->levels );
if
(List::AllUtils::any {
$_
->badflag } (
$self
,
@piddles
)) {
my
$isbad
= pdl([
map
{ @{
$_
->isbad->unpdl} } (
$self
,
@piddles
) ]);
$new
->{PDL} =
$new
->{PDL}->setbadif(
$isbad
);
}
return
$new
;
}
sub
copy {
my
(
$self
) =
@_
;
my
(
$class
) =
ref
(
$self
);
my
$new
=
$class
->new(
integer
=>
$self
->{PDL}->unpdl,
levels
=>
$self
->levels );
if
(
$self
->badflag ) {
$new
->{PDL} =
$new
->{PDL}->setbadif(
$self
->isbad );
}
return
$new
;
}
sub
inplace {
my
$self
=
shift
;
$self
->{PDL}->inplace(
@_
);
return
$self
;
}
sub
_call_on_pdl {
my
(
$method
) =
@_
;
return
sub
{
my
$self
=
shift
;
return
$self
->{PDL}->
$method
(
@_
);
};
}
for
my
$method
(
qw(isbad isgood)
) {
no
strict
'refs'
;
*{
$method
} = _call_on_pdl(
$method
);
}
sub
setbadif {
my
$self
=
shift
;
my
$new
=
$self
->copy;
$new
->{PDL} =
$new
->{PDL}->setbadif(
@_
);
return
$new
;
}
around
string
=>
sub
{
my
$orig
=
shift
;
my
(
$self
,
%opt
) =
@_
;
my
$ret
=
$orig
->(
@_
);
if
(
exists
$opt
{with_levels} ) {
my
@level_string
=
grep
{
defined
}
$self
->levels->flatten;
$ret
.=
"\n"
;
$ret
.=
"Levels: @level_string"
;
}
$ret
;
};
sub
_compare_levels {
my
(
$a
,
$b
) =
@_
;
return
unless
@$a
==
@$b
;
my
$ea
= List::AllUtils::each_arrayref(
$a
,
$b
);
while
(
my
(
$x
,
$y
) =
$ea
->() ) {
return
0
unless
$x
eq
$y
;
}
return
1;
}
sub
equal {
my
(
$self
,
$other
,
$d
) =
@_
;
if
( blessed(
$other
) &&
$other
->isa(
'PDL::Factor'
) ) {
if
( _compare_levels(
$self
->levels,
$other
->levels) ) {
return
$self
->{PDL} ==
$other
->{PDL};
}
else
{
failure::levels::mismatch->throw({
msg
=>
"level sets of factors are different"
,
trace
=> failure->croak_trace,
payload
=> {
self_levels
=>
$self
->levels,
other_levels
=>
$other
->levels,
}
}
);
}
}
else
{
my
$key_idx
= List::AllUtils::first_index {
$_
eq
$other
}
@{
$self
->levels};
return
$self
->{PDL} ==
$key_idx
;
}
}
sub
not_equal {
return
!equal(
@_
);
}
1;