package List::Compare::Base::_Auxiliary; our $VERSION = 0.55; use Carp; our @ISA = qw(Exporter); our @EXPORT_OK = qw| _validate_2_seenhashes _validate_seen_hash _validate_multiple_seenhashes _calculate_array_seen_only _calculate_seen_only _calculate_intermediate _calculate_union_only _calculate_union_seen_only _calculate_sharedref _subset_subengine _chart_engine_regular _chart_engine_multiple _equivalent_subengine _index_message1 _index_message2 _index_message3 _index_message4 _prepare_listrefs _subset_engine_multaccel _calc_seen _calc_seen1 _equiv_engine _argument_checker_0 _argument_checker _argument_checker_1 _argument_checker_2 _argument_checker_3 _argument_checker_3a _argument_checker_4 _alt_construct_tester _alt_construct_tester_1 _alt_construct_tester_2 _alt_construct_tester_3 _alt_construct_tester_4 _alt_construct_tester_5 |; our %EXPORT_TAGS = ( calculate => [ qw( _calculate_array_seen_only _calculate_seen_only _calculate_intermediate _calculate_union_only _calculate_union_seen_only _calculate_sharedref ) ], checker => [ qw( _argument_checker_0 _argument_checker _argument_checker_1 _argument_checker_2 _argument_checker_3 _argument_checker_3a _argument_checker_4 ) ], tester => [ qw( _alt_construct_tester _alt_construct_tester_1 _alt_construct_tester_2 _alt_construct_tester_3 _alt_construct_tester_4 _alt_construct_tester_5 ) ], ); use strict; local $^W =1; my $bad_lists_msg = q{If argument is single hash ref, you must have a 'lists' key whose value is an array ref}; sub _validate_2_seenhashes { my ($refL, $refR) = @_; my (%seenL, %seenR, %badentriesL, %badentriesR); foreach (keys %$refL) { if (${$refL}{$_} =~ /^\d+$/ and ${$refL}{$_} > 0) { $seenL{$_} = ${$refL}{$_}; } else { $badentriesL{$_} = ${$refL}{$_}; } } foreach (keys %$refR) { if (${$refR}{$_} =~ /^\d+$/ and ${$refR}{$_} > 0) { $seenR{$_} = ${$refR}{$_}; } else { $badentriesR{$_} = ${$refR}{$_}; } } my $msg = q{}; if ( (keys %badentriesL) or (keys %badentriesR) ) { $msg .= "\nValues in a 'seen-hash' may only be positive integers.\n"; $msg .= " These elements have invalid values:\n"; if (keys %badentriesL) { $msg .= " First hash in arguments:\n"; $msg .= " Key: $_\tValue: $badentriesL{$_}\n" foreach (sort keys %badentriesL); } if (keys %badentriesR) { $msg .= " Second hash in arguments:\n"; $msg .= " Key: $_\tValue: $badentriesR{$_}\n" foreach (sort keys %badentriesR); } $msg .= "Correct invalid values before proceeding"; croak "$msg: $!"; } return (\%seenL, \%seenR); } sub _validate_seen_hash { if (@_ > 2) { _validate_multiple_seenhashes( [@_] ); } else { my ($l, $r) = @_; my (%badentriesL, %badentriesR); foreach (keys %$l) { $badentriesL{$_} = ${$l}{$_} unless (${$l}{$_} =~ /^\d+$/ and ${$l}{$_} > 0); } foreach (keys %$r) { $badentriesR{$_} = ${$r}{$_} unless (${$r}{$_} =~ /^\d+$/ and ${$r}{$_} > 0); } my $msg = q{}; if ( (keys %badentriesL) or (keys %badentriesR) ) { $msg .= "\nValues in a 'seen-hash' must be numeric.\n"; $msg .= " These elements have invalid values:\n"; if (keys %badentriesL) { $msg .= " First hash in arguments:\n"; $msg .= " Key: $_\tValue: $badentriesL{$_}\n" foreach (sort keys %badentriesL); } if (keys %badentriesR) { $msg .= " Second hash in arguments:\n"; $msg .= " Key: $_\tValue: $badentriesR{$_}\n" foreach (sort keys %badentriesR); } $msg .= "Correct invalid values before proceeding"; croak "$msg: $!"; } } } sub _validate_multiple_seenhashes { my $hashrefsref = shift; my (%badentries); for (my $i = 0; $i <= $#{$hashrefsref}; $i++) { foreach my $k (keys %{$hashrefsref->[$i]}) { unless ($hashrefsref->[$i]->{$k} =~ /^\d+$/ and $hashrefsref->[$i]->{$k} > 0) { $badentries{$i}{$k} = $hashrefsref->[$i]->{$k}; } } } my $msg = q{}; if (scalar(keys %badentries)) { $msg .= "\nValues in a 'seen-hash' must be positive integers.\n"; $msg .= " These elements have invalid values:\n\n"; foreach my $b (sort keys %badentries) { $msg .= " Hash $b:\n"; foreach my $val (sort keys %{$badentries{$b}}) { $msg .= " Bad key-value pair: $val\t$badentries{$b}->{$val}\n"; } } $msg .= "Correct invalid values before proceeding"; croak "$msg: $!"; } } sub _list_builder { my ($aref, $x) = @_; if (ref(${$aref}[$x]) eq 'HASH') { return keys %{${$aref}[$x]}; } else { return @{${$aref}[$x]}; } } sub _calculate_array_seen_only { my $aref = shift; my (@seen); for (my $i = 0; $i <= $#{$aref}; $i++) { my %seenthis = (); foreach my $el ( _list_builder($aref, $i) ) { $seenthis{$el}++; } push @seen, \%seenthis; } return \@seen; } sub _calculate_seen_only { my $aref = shift; my (%seen); for (my $i = 0; $i <= $#{$aref}; $i++) { my %seenthis = (); foreach my $h ( _list_builder($aref, $i) ) { $seenthis{$h}++; } $seen{$i} = \%seenthis; } return \%seen; } sub _calculate_intermediate { my $aref = shift; my $aseenref = _calculate_array_seen_only($aref); my @vals = sort { scalar(keys(%{$a})) <=> scalar(keys(%{$b})) } @{$aseenref}; my %intermediate = map { $_ => 1 } keys %{$vals[0]}; for my $l ( 1..$#vals ) { %intermediate = map { $_ => 1 } grep { exists $intermediate{$_} } keys %{$vals[$l]}; } return \%intermediate; } sub _calculate_union_only { my $aref = shift; my (%union); for (my $i = 0; $i <= $#{$aref}; $i++) { foreach my $h ( _list_builder($aref, $i) ) { $union{$h}++; } } return \%union; } sub _calculate_union_seen_only { my $aref = shift; my (%union, %seen); for (my $i = 0; $i <= $#{$aref}; $i++) { my %seenthis = (); foreach my $h ( _list_builder($aref, $i) ) { $seenthis{$h}++; $union{$h}++; } $seen{$i} = \%seenthis; } return (\%union, \%seen); } sub _calculate_sharedref { my $seenrefsref = shift; my %intermediate = (); for my $href (@{$seenrefsref}) { my %this = map { $_ => 1 } keys(%{$href}); for my $k (keys %this) {; $intermediate{$k}++; }; } my $sharedref; for my $k (keys %intermediate) { $sharedref->{$k}++ if $intermediate{$k} > 1; } return $sharedref; } sub _is_list_subset { my ( $subset, $superset ) = @_; # return false if the superset value is false # for any subset value. # note that this does *not* validate overlap of # the keys; it validates the truth of supserset # values. $superset->{ $_ } or return 0 for keys %$subset; return 1; } sub _subset_subengine { my $aref = shift; my (@xsubset); my %seen = %{_calculate_seen_only($aref)}; foreach my $i (keys %seen) { foreach my $j (keys %seen) { if ( $i eq $j ) { $xsubset[$i][$j] = 1; } elsif ( $i gt $j ) { if ( scalar(keys %{ $seen{$i} }) == scalar(keys %{ $seen{$j} }) ){ $xsubset[$i][$j] = _is_list_subset($seen{$i}, $seen{$j}); $xsubset[$j][$i] = $xsubset[$i][$j]; } elsif ( scalar(keys %{ $seen{$i} }) < scalar(keys %{ $seen{$j} }) ){ $xsubset[$i][$j] = _is_list_subset($seen{$i}, $seen{$j}); $xsubset[$j][$i] = 0; } else { $xsubset[$j][$i] = _is_list_subset($seen{$j}, $seen{$i}); $xsubset[$i][$j] = 0; } } } } return \@xsubset; } sub _chart_engine_regular { my $aref = shift; my @sub_or_eqv = @$aref; my $title = shift; my ($v, $w, $t); print "\n"; print $title, ' Relationships', "\n\n"; print ' Right: 0 1', "\n\n"; print 'Left: 0: 1 ', $sub_or_eqv[0], "\n\n"; print ' 1: ', $sub_or_eqv[1], ' 1', "\n\n"; } sub _chart_engine_multiple { my $aref = shift; my @sub_or_eqv = @$aref; my $title = shift; my ($v, $w, $t); print "\n"; print $title, ' Relationships', "\n\n"; print ' Right:'; for ($v = 0; $v <= $#sub_or_eqv; $v++) { print ' ', $v; } print "\n\n"; print 'Left: 0:'; my @firstrow = @{$sub_or_eqv[0]}; for ($t = 0; $t <= $#firstrow; $t++) { print ' ', $firstrow[$t]; } print "\n\n"; for ($w = 1; $w <= $#sub_or_eqv; $w++) { my $length_left = length($w); my $x = ''; print ' ' x (8 - $length_left), $w, ':'; my @row = @{$sub_or_eqv[$w]}; for ($x = 0; $x <= $#row; $x++) { print ' ', $row[$x]; } print "\n\n"; } 1; # force return true value } sub _equivalent_subengine { my $aref = shift; my @xsubset = @{_subset_subengine($aref)}; my (@xequivalent); for (my $f = 0; $f <= $#xsubset; $f++) { for (my $g = 0; $g <= $#xsubset; $g++) { $xequivalent[$f][$g] = 0; $xequivalent[$f][$g] = 1 if ($xsubset[$f][$g] and $xsubset[$g][$f]); } } return \@xequivalent; } sub _index_message1 { my ($index, $dataref) = @_; my $method = (caller(1))[3]; croak "Argument to method $method must be the array index of the target list \n in list of arrays passed as arguments to the constructor: $!" unless ( $index =~ /^\d+$/ and $index <= ${$dataref}{'maxindex'} ); } sub _index_message2 { my $dataref = shift; my ($index_left, $index_right); my $method = (caller(1))[3]; croak "Method $method requires 2 arguments: $!" unless (@_ == 0 || @_ == 2); if (@_ == 0) { $index_left = 0; $index_right = 1; } else { ($index_left, $index_right) = @_; foreach ($index_left, $index_right) { croak "Each argument to method $method must be a valid array index for the target list \n in list of arrays passed as arguments to the constructor: $!" unless ( $_ =~ /^\d+$/ and $_ <= ${$dataref}{'maxindex'} ); } } return ($index_left, $index_right); } sub _index_message3 { my ($index, $maxindex) = @_; my $method = (caller(1))[3]; croak "Argument to method $method must be the array index of the target list \n in list of arrays passed as arguments to the constructor: $!" unless ( $index =~ /^\d+$/ and $index <= $maxindex ); } sub _index_message4 { my $maxindex = shift; my ($index_left, $index_right); my $method = (caller(1))[3]; croak "Method $method requires 2 arguments: $!" unless (@_ == 0 || @_ == 2); if (@_ == 0) { $index_left = 0; $index_right = 1; } else { ($index_left, $index_right) = @_; foreach ($index_left, $index_right) { croak "Each argument to method $method must be a valid array index for the target list \n in list of arrays passed as arguments to the constructor: $!" unless ( $_ =~ /^\d+$/ and $_ <= $maxindex ); } } return ($index_left, $index_right); } sub _prepare_listrefs { my $dataref = shift; delete ${$dataref}{'unsort'}; my (@listrefs); foreach my $lref (sort {$a <=> $b} keys %{$dataref}) { push(@listrefs, ${$dataref}{$lref}); }; return \@listrefs; } sub _subset_engine_multaccel { my $dataref = shift; my $aref = _prepare_listrefs($dataref); my ($index_left, $index_right) = _index_message4($#{$aref}, @_); my $xsubsetref = _subset_subengine($aref); return ${$xsubsetref}[$index_left][$index_right]; } sub _calc_seen { my ($refL, $refR) = @_; # We've already guaranteed that args are both array refs or both hash # refs. So checking the left-hand one is sufficient. if (ref($refL) eq 'ARRAY') { my (%seenL, %seenR); foreach (@$refL) { $seenL{$_}++ } foreach (@$refR) { $seenR{$_}++ } return (\%seenL, \%seenR); } else { return ($refL, $refR); } } sub _equiv_engine { my ($hrefL, $hrefR) = @_; my (%intersection, %Lonly, %Ronly, %LorRonly); my $LequivalentR_status = 0; foreach (keys %{$hrefL}) { exists ${$hrefR}{$_} ? $intersection{$_}++ : $Lonly{$_}++; } foreach (keys %{$hrefR}) { $Ronly{$_}++ unless (exists $intersection{$_}); } $LorRonly{$_}++ foreach ( (keys %Lonly), (keys %Ronly) ); $LequivalentR_status = 1 if ( (keys %LorRonly) == 0); return $LequivalentR_status; } sub _argument_checker_0 { my @args = @_; my $first_ref = ref($args[0]); my @temp = @args[1..$#args]; my ($testing); my $condition = 1; while (defined ($testing = shift(@temp)) ) { unless (ref($testing) eq $first_ref) { $condition = 0; last; } } croak "Arguments must be either all array references or all hash references: $!" unless $condition; _validate_seen_hash(@args) if $first_ref eq 'HASH'; return (@args); } sub _argument_checker { my $argref = shift; croak "'$argref' must be an array ref" unless ref($argref) eq 'ARRAY'; my @args = _argument_checker_0(@{$argref}); return (@args); } sub _argument_checker_1 { my $argref = shift; my @args = @{$argref}; croak "Subroutine call requires 2 references as arguments: $!" unless @args == 2; return (_argument_checker($args[0]), ${$args[1]}[0]); } sub _argument_checker_2 { my $argref = shift; my @args = @$argref; croak "Subroutine call requires 2 references as arguments: $!" unless @args == 2; return (_argument_checker($args[0]), $args[1]); } # _argument_checker_3 is currently set-up to handle either 1 or 2 arguments # in get_unique and get_complement # The first argument is an arrayref holding refs to lists ('unsorted' has been # stripped off). # The second argument is an arrayref holding a single item (index number of # item being tested) # Note: Currently we're only checking for the quantity of arguments -- not # their types. This should be fixed. sub _argument_checker_3 { my $argref = shift; my @args = @{$argref}; if (@args == 1) { return (_argument_checker($args[0]), 0); } elsif (@args == 2) { return (_argument_checker($args[0]), ${$args[1]}[0]); } else { croak "Subroutine call requires 1 or 2 references as arguments: $!"; } } sub _argument_checker_3a { my $argref = shift; my @args = @{$argref}; if (@args == 1) { return [ _argument_checker($args[0]) ]; } else { croak "Subroutine call requires exactly 1 reference as argument: $!"; } } sub _argument_checker_4 { my $argref = shift; my @args = @{$argref}; if (@args == 1) { return (_argument_checker($args[0]), [0,1]); } elsif (@args == 2) { if (@{$args[1]} == 2) { my $last_index = $#{$args[0]}; foreach my $i (@{$args[1]}) { croak "No element in index position $i in list of list references passed as first argument to function: $!" unless ($i =~ /^\d+$/ and $i <= $last_index); } return (_argument_checker($args[0]), $args[1]); } else { croak "Must provide index positions corresponding to two lists: $!"; } } else { croak "Subroutine call requires 1 or 2 references as arguments: $!"; } } sub _calc_seen1 { my @listrefs = @_; # _calc_seen1() is applied after _argument_checker(), which checks to make # sure that the references in its output are either all arrayrefs # or all seenhashrefs # hence, _calc_seen1 only needs to determine whether it's dealing with # arrayrefs or seenhashrefs, then, if arrayrefs, calculate seenhashes if (ref($listrefs[0]) eq 'ARRAY') { my (@seenrefs); foreach my $aref (@listrefs) { my (%seenthis); foreach my $j (@{$aref}) { $seenthis{$j}++; } push(@seenrefs, \%seenthis); } return \@seenrefs; } else { return \@listrefs; } } # _alt_construct_tester prepares for _argument_checker in # get_union get_intersection get_symmetric_difference get_shared get_nonintersection sub _alt_construct_tester { my @args = @_; my ($argref, $unsorted); if (@args == 1 and (ref($args[0]) eq 'HASH')) { my $hashref = shift; croak "$bad_lists_msg: $!" unless ( ${$hashref}{'lists'} and (ref(${$hashref}{'lists'}) eq 'ARRAY') ); $argref = ${$hashref}{'lists'}; $unsorted = ${$hashref}{'unsorted'} ? 1 : ''; } else { $unsorted = shift(@args) if ($args[0] eq '-u' or $args[0] eq '--unsorted'); $argref = shift(@args); } return ($argref, $unsorted); } # _alt_construct_tester_1 prepares for _argument_checker_1 in # is_member_which is_member_which_ref is_member_any sub _alt_construct_tester_1 { my @args = @_; my ($argref); if (@args == 1 and (ref($args[0]) eq 'HASH')) { my (@returns); my $hashref = $args[0]; croak "$bad_lists_msg: $!" unless ( ${$hashref}{'lists'} and (ref(${$hashref}{'lists'}) eq 'ARRAY') ); croak "If argument is single hash ref, you must have an 'item' key: $!" unless ${$hashref}{'item'}; @returns = ( ${$hashref}{'lists'}, [${$hashref}{'item'}] ); $argref = \@returns; } else { $argref = \@args; } return $argref; } # _alt_construct_tester_2 prepares for _argument_checker_2 in # are_members_which are_members_any sub _alt_construct_tester_2 { my @args = @_; if (@args == 1 and (ref($args[0]) eq 'HASH')) { my $hashref = $args[0]; croak "$bad_lists_msg: $!" unless ( ${$hashref}{'lists'} and (ref(${$hashref}{'lists'}) eq 'ARRAY') ); croak "If argument is single hash ref, you must have an 'items' key whose value is an array ref: $!" unless ( ${$hashref}{'items'} and (ref(${$hashref}{'items'}) eq 'ARRAY') ); return [ (${$hashref}{'lists'}, ${$hashref}{'items'}) ]; } else { return \@args; } } # _alt_construct_tester_3 prepares for _argument_checker_3 in # get_unique get_complement sub _alt_construct_tester_3 { my @args = @_; my ($argref, $unsorted); if (@args == 1 and (ref($args[0]) eq 'HASH')) { my (@returns); my $hashref = $args[0]; croak "$bad_lists_msg: $!" unless ( ${$hashref}{'lists'} and (ref(${$hashref}{'lists'}) eq 'ARRAY') ); @returns = defined ${$hashref}{'item'} ? (${$hashref}{'lists'}, [${$hashref}{'item'}]) : (${$hashref}{'lists'}); $argref = \@returns; $unsorted = ${$hashref}{'unsorted'} ? 1 : ''; } else { $unsorted = shift(@args) if ($args[0] eq '-u' or $args[0] eq '--unsorted'); $argref = \@args; } return ($argref, $unsorted); } # _alt_construct_tester_4 prepares for _argument_checker_4 in # is_LsubsetR is_RsubsetL is_LequivalentR is_LdisjointR sub _alt_construct_tester_4 { my @args = @_; my ($argref); if (@args == 1 and (ref($args[0]) eq 'HASH')) { my (@returns); my $hashref = $args[0]; croak "$bad_lists_msg: $!" unless ( ${$hashref}{'lists'} and (ref(${$hashref}{'lists'}) eq 'ARRAY') ); @returns = defined ${$hashref}{'pair'} ? (${$hashref}{'lists'}, ${$hashref}{'pair'}) : (${$hashref}{'lists'}); $argref = \@returns; } else { $argref = \@args; } return $argref; } # _alt_construct_tester_5 prepares for _argument_checker in # print_subset_chart print_equivalence_chart sub _alt_construct_tester_5 { my @args = @_; my ($argref); if (@args == 1) { if (ref($args[0]) eq 'HASH') { my $hashref = shift; croak "Need to define 'lists' key properly: $!" unless ( ${$hashref}{'lists'} and (ref(${$hashref}{'lists'}) eq 'ARRAY') ); $argref = ${$hashref}{'lists'}; } else { $argref = shift(@args); } } else { croak "Subroutine call requires exactly 1 reference as argument: $!"; } return $argref; } 1; __END__ =head1 NAME List::Compare::Base::_Auxiliary - Internal use only =head1 VERSION This document refers to version 0.55 of List::Compare::Base::_Auxiliary. This version was released August 16 2020. =head1 SYNOPSIS This module contains subroutines used within List::Compare and List::Compare::Functional. They are not intended to be publicly callable. =head1 AUTHOR James E. Keenan (jkeenan@cpan.org). When sending correspondence, please include 'List::Compare' or 'List-Compare' in your subject line. Creation date: May 20, 2002. Last modification date: February 25 2020. Copyright (c) 2002-20 James E. Keenan. United States. All rights reserved. This is free software and may be distributed under the same terms as Perl itself. =cut