package Data::Mining::Apriori;

use 5.010001;
use strict;
use warnings;
use Algorithm::Combinatorics qw(subsets variations);
no if $] >= 5.017011, warnings => 'experimental::smartmatch';

our $VERSION = 0.13;

my$self;

$" = ', ';
$| = 1;
$SIG{'INT'} = \&stop;

sub new{
	my $type = shift;
	my $class = ref($type)||$type;
	$self = {
		totalTransactions => 0,
		metrics => {
			minSupport => 0.01,
			minConfidence => 0.10,
			minLift => undef,
			minLeverage => undef,
			minConviction => undef,
			minCoverage => undef,
			minCorrelation => undef,
			minCosine => undef,
			minLaplace => undef,
			minJaccard => undef
		},
		output => undef,
		messages => undef,
		itemsKeyDescription => undef,
		itemsKeyTransactions => undef,
		limitRules => undef,
		frequentItemset => [],
		associationRules => undef,
		implications => undef,
		largeItemsetSize => 2,
		rule => 0
	};
	bless($self,$class);
	return $self;
}

sub validate_data{
	(defined $self->{itemsKeyDescription})
		or die("Error: \$apriori->{itemsKeyDescription} is not defined!\n");
	(defined $self->{itemsKeyTransactions})
		or die("Error: \$apriori->{itemsKeyTransactions} is not defined!\n");
}

sub insert_key_items_transaction{
	(scalar(@_)==2)
		or die("Error: \$apriori->insert_key_items_transaction(\\\@items) missing parameter key items to array reference!\n");
	(ref($_[1]) eq "ARRAY")
		or die("Error: \$apriori->insert_key_items_transaction(\\\@items) parameter key items is not an array reference!\n");
	my@items=sort(@{$_[1]});
	my @itemsets=subsets(\@items);
	foreach my$itemset(@itemsets){
		$self->{itemsKeyTransactions}{"@{$itemset}"}++;
	}
	$self->{totalTransactions}++;
}

sub input_data_file{
	(scalar(@_)==3)
		or die("Error: \$apriori->input_data_file(\"datafile.txt\",\",\") missing parameters path to data file and/or item separator!\n");
	my$file=$_[1];
	my$separator=$_[2];
	open(FILE,"<$file")
		or die("Error: $!");
	while(my$line=<FILE>){
		$line=~s/\r\n|\n//;
		my@items=split($separator,$line);
		if(scalar(@items)>=2){
			$self->insert_key_items_transaction(\@items);
		}
	}
	close(FILE);
}

sub quantity_possible_rules{
	$self->validate_data;
	return ((3**scalar(keys(%{$self->{itemsKeyDescription}})))-(2**(scalar(keys(%{$self->{itemsKeyDescription}}))+1))+1);
}

sub generate_rules{
	$self->validate_data;
	my @largeItemsetSizeOne = keys(%{$self->{itemsKeyDescription}});
	if($self->{messages}){
		print "\n${\scalar(keys(%{$self->{itemsKeyDescription}}))} items, ${\$self->quantity_possible_rules} possible rules";
	}
	my @frequentItemset = grep{(($self->{itemsKeyTransactions}{$_}/$self->{totalTransactions})*100)>=$self->{metrics}{minSupport}}@largeItemsetSizeOne;
	$self->association_rules(\@frequentItemset);
}

sub association_rules{
	my @largeItemset = @{$_[1]};
	my @variations = variations(\@largeItemset,$self->{largeItemsetSize});
	my @frequentItemset;
	if($self->{messages}){
		print "\nLarge itemset size $self->{largeItemsetSize}, ${\scalar(@largeItemset)} items ";
		print "\nProcessing... ";
	}
	foreach my$variation(@variations){
		my@candidateItemset=@{$variation};
		my@antecedent;
		my@consequent;
		for(my$antecedentSize=0;$antecedentSize<$#candidateItemset;$antecedentSize++){
			push@antecedent,$candidateItemset[$antecedentSize];
			@consequent=();
			for(my$consequentSize=($antecedentSize+1);$consequentSize<=$#candidateItemset;$consequentSize++){
				push@consequent,$candidateItemset[$consequentSize];
			}
			@antecedent=sort@antecedent;
			@consequent=sort@consequent;
			next if("@consequent" ~~ @{$self->{implications}{"@antecedent"}});
			push @{$self->{implications}{"@antecedent"}},"@consequent";
			my@implication;
			push@implication,@antecedent,@consequent;
			@implication=sort(@implication);
			next unless $self->{itemsKeyTransactions}{"@antecedent"};
			my$supportAntecedent=($self->{itemsKeyTransactions}{"@antecedent"}/$self->{totalTransactions});
			next unless $self->{itemsKeyTransactions}{"@implication"};
			my$supportConsequent=($self->{itemsKeyTransactions}{"@implication"}/$self->{totalTransactions});
			my $support = $supportConsequent;
			next if($support < $self->{metrics}{minSupport});
			my $confidence = ($supportConsequent/$supportAntecedent);
			next if(defined $self->{metrics}{minConfidence} && $confidence < $self->{metrics}{minConfidence});
			my $lift = ($support/($supportAntecedent*$supportConsequent));
			next if(defined $self->{metrics}{minLift} && $lift < $self->{metrics}{minLift});
			my $leverage = ($support-($supportAntecedent*$supportConsequent));
			next if(defined $self->{metrics}{minLeverage} && $leverage < $self->{metrics}{minLeverage});
			my $conviction = ((1-$supportConsequent)==0)?"NaN":((1-$confidence)==0)?"NaN":((1-$supportConsequent)/(1-$confidence));
			next if(defined $self->{metrics}{minConviction} && $conviction < $self->{metrics}{minConviction});
			my $coverage = $supportAntecedent;
			next if(defined $self->{metrics}{minCoverage} && $coverage < $self->{metrics}{minCoverage});
			my $correlation = (($support-($supportAntecedent*$supportConsequent))/sqrt($supportAntecedent*(1-$supportAntecedent)*$supportConsequent*(1-$supportConsequent)));
			next if(defined $self->{metrics}{minCorrelation} && $correlation < $self->{metrics}{minCorrelation});
			my $cosine = ($support/sqrt($supportAntecedent*$supportConsequent));
			next if(defined $self->{metrics}{minCosine} && $cosine < $self->{metrics}{minCosine});
			my $laplace = (($support+1)/($supportAntecedent+2));
			next if(defined $self->{metrics}{minLaplace} && $laplace < $self->{metrics}{minLaplace});
			my $jaccard = ($support/($supportAntecedent+$supportConsequent-$support));
			next if(defined $self->{metrics}{minJaccard} && $jaccard < $self->{metrics}{minJaccard});
			$self->{rule}++;
			$self->{associationRules}{$self->{rule}} = {
				implication => "{ @antecedent } => { @consequent }",
				support => sprintf("%.4f", $support), 
				confidence => sprintf("%.4f", $confidence),
				lift => sprintf("%.4f", $lift),
				leverage => sprintf("%.4f", $leverage),
				conviction => sprintf("%.4f", $conviction),
				coverage => sprintf("%.4f", $coverage),
				correlation => sprintf("%.4f", $correlation),
				cosine => sprintf("%.4f", $cosine),
				laplace => sprintf("%.4f", $laplace),
				jaccard => sprintf("%.4f", $jaccard),
				items => [@antecedent, @consequent]
			};
			my@items=grep{!($_~~@frequentItemset)}@implication;
			push@frequentItemset,@items;
		}
		last if(defined $self->{limitRules} && $self->{rule} == $self->{limitRules});
	}
	if($self->{messages}){
		print "\nFrequent itemset: { @frequentItemset }, ${\scalar(@frequentItemset)} items ";
	}
	if(defined $self->{associationRules}){
		@{$self->{frequentItemset}}=@frequentItemset;
		$self->output;
	}
	return if(defined $self->{limitRules} && $self->{rule} == $self->{limitRules});
	if(scalar(@frequentItemset)>=($self->{largeItemsetSize}+1)){
		$self->{largeItemsetSize}++;
		$self->{associationRules} = undef;
		$self->association_rules(\@frequentItemset);
	}
}

sub stop{
	if($self->{messages}){
		print "\nStopping... ";
		$self->output if $self->{associationRules};
		print "\nExit? (Y/N): ";
		my $answer = <STDIN>;
		chomp($answer);
		if($answer =~ /^y$/i){
			exit;
		}
		else{
			print "Processing... ";
		}
	}
	else{
		$self->output if $self->{associationRules};
		exit;
	}
}

sub output{
	if($self->{output}){
		if($self->{output}==1){
			$self->file;
		}
		elsif($self->{output}==2){
			$self->excel;
		}
	}
}

sub file{
	if($self->{messages}){
		print "\nExporting to file \"output_large_itemset_size_$self->{largeItemsetSize}.txt\"... ";
	}
	open(FILE,">output_large_itemset_size_$self->{largeItemsetSize}.txt")
		or die("\nError: \"output_large_itemset_size_$self->{largeItemsetSize}.txt\" $!");
	print FILE "Rules\tSupport\tConfidence";
	my@headings=('Lift', 'Leverage', 'Conviction', 'Coverage', 'Correlation', 'Cosine', 'Laplace', 'Jaccard');
	my@metrics;
	foreach my$metric(@headings){
		push@metrics,$metric if defined $self->{metrics}{"min$metric"};
	}
	foreach my$metric(@metrics){
		print FILE "\t$metric";
	}
	print FILE "\n";
	foreach my$rule(sort{$a<=>$b}keys(%{$self->{associationRules}})){
		foreach my$metric(@metrics){
			$self->{associationRules}{$rule}{lc$metric}=~s/\./,/;
		}
		print FILE "R$rule\t$self->{associationRules}{$rule}{support}\t$self->{associationRules}{$rule}{confidence}";
		foreach my$metric(@metrics){
			print FILE "\t$self->{associationRules}{$rule}{${\lc$metric}}";
		}
		print FILE "\n";
	}
	print FILE "\n";
	foreach my$rule(sort{$a<=>$b}keys(%{$self->{associationRules}})){
		print FILE "Rule R$rule: $self->{associationRules}{$rule}{implication}\n";
		print FILE "Support: $self->{associationRules}{$rule}{support}\n";
		print FILE "Confidence: $self->{associationRules}{$rule}{confidence}\n";
		foreach my$metric(@metrics){
			print FILE "$metric: $self->{associationRules}{$rule}{${\lc$metric}}\n";
		}
		print FILE "Items:\n";
		foreach my$item(@{$self->{associationRules}{$rule}{items}}){
			print FILE "$item $self->{itemsKeyDescription}{$item}\n";
		}
		print FILE "\n";
	}
	print FILE "Frequent itemset: { @{$self->{frequentItemset}} }\n";
	print FILE "Items:\n";
	foreach my$item(@{$self->{frequentItemset}}){
		print FILE "$item $self->{itemsKeyDescription}{$item}\n";
	}
	close(FILE);
}

sub excel{
	require Excel::Writer::XLSX;
	if($self->{messages}){
		print "\nExporting to excel \"output_large_itemset_size_$self->{largeItemsetSize}.xlsx\"... ";
	}
	my $workbook  = Excel::Writer::XLSX->new("output_large_itemset_size_$self->{largeItemsetSize}.xlsx") 
		or die("\nError: \"output_large_itemset_size_$self->{largeItemsetSize}.xlsx\" $!");
	my $worksheet = $workbook->add_worksheet();
	my $bold = $workbook->add_format(bold => 1);
	my $headings = ['Rules', 'Support', 'Confidence'];
	my@metrics=('Lift', 'Leverage', 'Conviction', 'Coverage', 'Correlation', 'Cosine', 'Laplace', 'Jaccard');
	foreach my$metric(@metrics){
		push@{$headings},$metric if defined $self->{metrics}{"min$metric"};
	}
	@metrics=@{$headings}[3..$#{$headings}];
	my(@rules,@support,@confidence,@lift,@leverage,@conviction,@coverage,@correlation,@cosine,@laplace,@jaccard);	
	foreach my$rule(sort{$a<=>$b}keys(%{$self->{associationRules}})){
		push @rules,"R$rule";
		push @support,$self->{associationRules}{$rule}{support};
		push @confidence,$self->{associationRules}{$rule}{confidence};
		push @lift,$self->{associationRules}{$rule}{lift} if defined $self->{metrics}{minLift};
		push @leverage,$self->{associationRules}{$rule}{leverage} if defined $self->{metrics}{minLeverage};
		push @conviction,$self->{associationRules}{$rule}{conviction} if defined $self->{metrics}{minConviction};
		push @coverage,$self->{associationRules}{$rule}{coverage} if defined $self->{metrics}{minCoverage};
		push @correlation,$self->{associationRules}{$rule}{correlation} if defined $self->{metrics}{minCorrelation};
		push @cosine,$self->{associationRules}{$rule}{cosine} if defined $self->{metrics}{minCosine};
		push @laplace,$self->{associationRules}{$rule}{laplace} if defined $self->{metrics}{minLaplace};
		push @jaccard,$self->{associationRules}{$rule}{jaccard} if defined $self->{metrics}{minJaccard};
	}
	my$line=(scalar(@rules)+1);
	my@data=(\@rules,\@support,\@confidence);
	push @data,\@lift if defined $self->{metrics}{minLift};
	push @data,\@leverage if defined $self->{metrics}{minLeverage};
	push @data,\@conviction if defined $self->{metrics}{minConviction};
	push @data,\@coverage if defined $self->{metrics}{minCoverage};
	push @data,\@correlation if defined $self->{metrics}{minCorrelation};
	push @data,\@cosine if defined $self->{metrics}{minCosine};
	push @data,\@laplace if defined $self->{metrics}{minLaplace};
	push @data,\@jaccard if defined $self->{metrics}{minJaccard};
	$worksheet->write('A1', $headings, $bold);
	$worksheet->write('A2', \@data);
	my$chart=$workbook->add_chart(type => 'column', embedded => 1);
	my@columns=('B'..'M');
	my$i=0;
	$chart->add_series(
		name       => 'Support',
		categories => '=Sheet1!$A$2:$A$'.$line,
		values     => '=Sheet1!$'.$columns[$i].'$2:$'.$columns[$i].'$'.$line,
	);
	$i++;
	$chart->add_series(
		name       => 'Confidence',
		categories => '=Sheet1!$A$2:$A$'.$line,
		values     => '=Sheet1!$'.$columns[$i].'$2:$'.$columns[$i].'$'.$line,
	);
	foreach my$metric(@metrics){
		$i++;
		$chart->add_series(
			name       => $metric,
			categories => '=Sheet1!$A$2:$A$'.$line,
			values     => '=Sheet1!$'.$columns[$i].'$2:$'.$columns[$i].'$'.$line,
		);
	}
	$worksheet->insert_chart($columns[($i+2)].'2', $chart);
	$line+=2;
	foreach my$rule(sort{$a<=>$b}keys(%{$self->{associationRules}})){
		$worksheet->write("A$line","Rule R$rule: $self->{associationRules}{$rule}{implication}");
		$line++;
		$worksheet->write("A$line","Support: $self->{associationRules}{$rule}{support}");
		$line++;
		$worksheet->write("A$line","Confidence: $self->{associationRules}{$rule}{confidence}");
		$line++;
		foreach my$metric(@metrics){
			$worksheet->write("A$line","$metric: $self->{associationRules}{$rule}{${\lc$metric}}");
			$line++;
		}
		$worksheet->write("A$line","Items:");
		$line++;
		foreach my$item(@{$self->{associationRules}{$rule}{items}}){
			$worksheet->write("A$line","$item $self->{itemsKeyDescription}{$item}");
			$line++;
		}
		$line++;
	}
	$worksheet->write("A$line","Frequent itemset: { @{$self->{frequentItemset}} }");
	$line++;
	$worksheet->write("A$line","Items:");
	$line++;
	foreach my$item(@{$self->{frequentItemset}}){
		$worksheet->write("A$line","$item $self->{itemsKeyDescription}{$item}");
		$line++;
	}
	$workbook->close;
}

return 1;
__END__
=head1 NAME

Data::Mining::Apriori - Perl extension for implement the apriori algorithm of data mining.

=head1 SYNOPSIS

	use strict;
	use warnings;
	use Data::Mining::Apriori;

	# TRANSACTION 103:CEREAL 101:MILK 102:BREAD
	#        1101          1        1         0
	#        1102          1        0         1
	#        1103          1        1         1
	#        1104          1        1         1
	#        1105          0        1         1
	#        1106          1        1         1
	#        1107          1        1         1
	#        1108          1        0         1
	#        1109          1        1         1
	#        1110          1        1         1

	my $apriori = new Data::Mining::Apriori;

	$apriori->{metrics}{minSupport}=0.0155; # The minimum support, default value is 0.01(1%)

	$apriori->{metrics}{minConfidence}=0.0155; # The minimum confidence, default value is 0.10(10%)

	$apriori->{metrics}{minLift}=1; # The minimum lift(optional)

	$apriori->{metrics}{minLeverage}=0; # The minimum leverage(optional)

	$apriori->{metrics}{minConviction}=0; # The minimum conviction(optional)

	$apriori->{metrics}{minCoverage}=0; # The minimum coverage(optional)

	$apriori->{metrics}{minCorrelation}=0; # The minimum correlation(optional)

	$apriori->{metrics}{minCosine}=0; # The minimum cosine(optional)

	$apriori->{metrics}{minLaplace}=0; # The minimum laplace(optional)

	$apriori->{metrics}{minJaccard}=0; # The minimum jaccard(optional)

	$apriori->{output}=1;
	# The output type (1 - Export to text file delimited by tab; 2 - Export to excel file with chart)(optional)

	$apriori->{messages}=1; # A value boolean to display the messages(optional)

	$apriori->{itemsKeyDescription}{'101'}='MILK'; # Hash table to add items by key and description
	$apriori->{itemsKeyDescription}{102}='BREAD';
	$apriori->{itemsKeyDescription}{'103'}='CEREAL';

	my@items=(103,101);
	$apriori->insert_key_items_transaction(\@items); # Insert key items per transaction
	$apriori->insert_key_items_transaction([103,102]);
	$apriori->insert_key_items_transaction([103,101,102]);
	$apriori->insert_key_items_transaction([103,101,102]);
	$apriori->insert_key_items_transaction([101,102]);
	$apriori->insert_key_items_transaction([103,101,102]);
	$apriori->insert_key_items_transaction([103,101,102]);
	$apriori->insert_key_items_transaction([103,102]);
	$apriori->insert_key_items_transaction([103,101,102]);
	$apriori->insert_key_items_transaction([103,101,102]);

	# or from a data file

	$apriori->input_data_file("datafile.txt",",");
	# Insert items per line(transaction), accepts the arguments of path to data file and item separator

	# file contents (example)

	103,101
	103,102
	103,101,102
	103,101,102
	101,102
	103,101,102
	103,101,102
	103,102
	103,101,102
	103,101,102

	print "\n${\$apriori->quantity_possible_rules}"; # Show the quantity of possible rules

	$apriori->{limitRules}=10; # The limit of rules

	$apriori->generate_rules;
	# Generate association rules to no longer meet the minimum support, confidence, lift, leverage, conviction, coverage or limit of rules

	print "\n@{$apriori->{frequentItemset}}\n"; # Show frequent items

	#output messages

	12
	3 items, 12 possible rules
	Large itemset size 2, 3 items
	Processing...
	Frequent itemset: { 103, 102, 101 }, 3 items
	Exporting to excel "output_large_itemset_size_2.xlsx"...
	Large itemset size 3, 3 items
	Processing...
	Frequent itemset: { 103, 101, 102 }, 3 items
	Exporting to excel "output_large_itemset_size_3.xlsx"...
	103, 101, 102

	#output file "output_itemset_size_2.txt"

	Rules	Support	Confidence	Lift	Leverage	Conviction	Coverage	Correlation	Cosine	Laplace	Jaccard
	R1	0.7000	0.7778	1,1111	0,0700	1,3500	0,9000	0,5092	0,8819	0,5862	0,7778
	R2	0.8000	0.8889	1,1111	0,0800	1,8000	0,9000	0,6667	0,9428	0,6207	0,8889
	R3	0.7000	0.8750	1,2500	0,1400	2,4000	0,8000	0,7638	0,9354	0,6071	0,8750
	R4	0.7000	0.8750	1,2500	0,1400	2,4000	0,8000	0,7638	0,9354	0,6071	0,8750
	R5	0.8000	0.8889	1,1111	0,0800	1,8000	0,9000	0,6667	0,9428	0,6207	0,8889
	R6	0.7000	0.7778	1,1111	0,0700	1,3500	0,9000	0,5092	0,8819	0,5862	0,7778

	Rule R1: { 102 } => { 101 }
	Support: 0.7000
	Confidence: 0.7778
	Lift: 1,1111
	Leverage: 0,0700
	Conviction: 1,3500
	Coverage: 0,9000
	Correlation: 0,5092
	Cosine: 0,8819
	Laplace: 0,5862
	Jaccard: 0,7778
	Items:
	102 BREAD
	101 MILK

	to be continued...

	#output file "output_itemset_size_3.txt"

	Rules	Support	Confidence	Lift	Leverage	Conviction	Coverage	Correlation	Cosine	Laplace	Jaccard
	R7	0.6000	0.7500	1,2500	0,1200	1,6000	0,8000	0,6124	0,8660	0,5714	0,7500
	R8	0.6000	0.8571	1,4286	0,1800	2,8000	0,7000	0,8018	0,9258	0,5926	0,8571
	R9	0.6000	0.8571	1,4286	0,1800	2,8000	0,7000	0,8018	0,9258	0,5926	0,8571
	R10	0.6000	0.6667	1,1111	0,0600	1,2000	0,9000	0,4082	0,8165	0,5517	0,6667
	R11	0.6000	0.7500	1,2500	0,1200	1,6000	0,8000	0,6124	0,8660	0,5714	0,7500
	R12	0.6000	0.6667	1,1111	0,0600	1,2000	0,9000	0,4082	0,8165	0,5517	0,6667

	Rule R7: { 101 } => { 102, 103 }
	Support: 0.6000
	Confidence: 0.7500
	Lift: 1,2500
	Leverage: 0,1200
	Conviction: 1,6000
	Coverage: 0,8000
	Correlation: 0,6124
	Cosine: 0,8660
	Laplace: 0,5714
	Jaccard: 0,7500
	Items:
	101 MILK
	102 BREAD
	103 CEREAL

	Rule R8: { 101, 102 } => { 103 }
	Support: 0.6000
	Confidence: 0.8571
	Lift: 1,4286
	Leverage: 0,1800
	Conviction: 2,8000
	Coverage: 0,7000
	Correlation: 0,8018
	Cosine: 0,9258
	Laplace: 0,5926
	Jaccard: 0,8571
	Items:
	101 MILK
	102 BREAD
	103 CEREAL

	to be continued...

	# or from a database

	# CREATE TABLE dimension_product(
		# product_key INTEGER NOT NULL PRIMARY KEY,
		# product_alternate_key INTEGER NOT NULL,
		# product_name TEXT NOT NULL,
		# price REAL NOT NULL
		# -- ...
	# );

	# INSERT INTO dimension_product VALUES(1,101,'MILK',10.00);
	# INSERT INTO dimension_product VALUES(2,102,'BREAD',10.00);
	# INSERT INTO dimension_product VALUES(3,103,'CEREAL',10.00);
	# -- ...

	# CREATE TABLE fact_sales(
		# sales_order_number INTEGER NOT NULL,
		# sales_order_line_number INTEGER NOT NULL,
		# product_key INTEGER NOT NULL,
		# quantity INTEGER NOT NULL,
		# -- ...
		# PRIMARY KEY(sales_order_number, sales_order_line_number),
		# FOREIGN KEY(product_key) REFERENCES dimension_product(product_key)
	# );

	# INSERT INTO fact_sales VALUES(1101,1,3,1);
	# INSERT INTO fact_sales VALUES(1101,2,1,1);
	# INSERT INTO fact_sales VALUES(1102,1,3,1);
	# INSERT INTO fact_sales VALUES(1102,2,2,1);
	# INSERT INTO fact_sales VALUES(1103,1,1,1);
	# INSERT INTO fact_sales VALUES(1103,2,2,1);
	# INSERT INTO fact_sales VALUES(1103,3,3,1);
	# INSERT INTO fact_sales VALUES(1104,1,1,1);
	# INSERT INTO fact_sales VALUES(1104,2,2,1);
	# INSERT INTO fact_sales VALUES(1104,3,3,1);
	# INSERT INTO fact_sales VALUES(1105,1,1,1);
	# INSERT INTO fact_sales VALUES(1105,2,2,1);
	# INSERT INTO fact_sales VALUES(1106,1,1,1);
	# INSERT INTO fact_sales VALUES(1106,2,2,1);
	# INSERT INTO fact_sales VALUES(1106,3,3,1);
	# INSERT INTO fact_sales VALUES(1107,1,1,1);
	# INSERT INTO fact_sales VALUES(1107,2,2,1);
	# INSERT INTO fact_sales VALUES(1107,3,3,1);
	# INSERT INTO fact_sales VALUES(1108,1,3,1);
	# INSERT INTO fact_sales VALUES(1108,2,2,1);
	# INSERT INTO fact_sales VALUES(1109,1,1,1);
	# INSERT INTO fact_sales VALUES(1109,2,2,1);
	# INSERT INTO fact_sales VALUES(1109,3,3,1);
	# INSERT INTO fact_sales VALUES(1110,1,1,1);
	# INSERT INTO fact_sales VALUES(1110,2,2,1);
	# INSERT INTO fact_sales VALUES(1110,3,3,1);
	# -- ...

	use DBD::SQLite;
	use Data::Mining::Apriori;

	my $apriori = new Data::Mining::Apriori;

	$apriori->{metrics}{minSupport}=0.0155;

	$apriori->{metrics}{minConfidence}=0.0155;

	$apriori->{metrics}{minLift}=1;

	$apriori->{metrics}{minLeverage}=0;

	$apriori->{metrics}{minConviction}=0;

	$apriori->{metrics}{minCoverage}=0;

	$apriori->{metrics}{minCorrelation}=0;

	$apriori->{metrics}{minCosine}=0;

	$apriori->{metrics}{minLaplace}=0;

	$apriori->{metrics}{minJaccard}=0;

	$apriori->{output}=1;

	$apriori->{messages}=1;

	my $db = DBI->connect('dbi:SQLite:dbname=DW.db','','');

	my$sql = qq~
	SELECT DISTINCT(fs.sales_order_number)
	FROM dimension_product dp
	JOIN fact_sales fs ON
	dp.product_key = fs.product_key
	-- WHERE ...
	~;

	my$query = $db->prepare($sql);
	$query->execute;
	my$transactions=$query->fetchall_arrayref;

	foreach my$transaction(@$transactions){
		$sql = qq~
			SELECT dp.product_alternate_key, dp.product_name
			FROM dimension_product dp
			JOIN fact_sales fs ON
			dp.product_key = fs.product_key
			WHERE fs.sales_order_number = $$transaction[0];
			-- AND ...
		~;
		$query = $db->prepare($sql);
		$query->execute;
		my@items;
		while(my($key,$description)=$query->fetchrow){
			$apriori->{itemsKeyDescription}{$key}=$description;
			push@items,$key;
		}
		$apriori->insert_key_items_transaction(\@items);
	}

	print "\n${\$apriori->quantity_possible_rules}";

	$apriori->{limitRules}=10;

	$apriori->generate_rules;

	print "\n@{$apriori->{frequentItemset}}\n";

=head1 DESCRIPTION

This module implements the apriori algorithm of data mining.

=head1 ATTRIBUTES

=head2 totalTransactions

The total number of transactions.

=head2 metrics

The type of metrics

=over 4

=item * minSupport - the minimum support, default value is 0.01(1%);

=item * minConfidence - the minimum confidence, default value is 0.10(10%);

=item * minLift - the minimum lift(optional);

=item * minLeverage - the minimum leverage(optional);

=item * minConviction - the minimum conviction(optional);

=item * minCoverage - the minimum coverage(optional);

=item * minCorrelation - the minimum correlation(optional);

=item * minCosine - the minimum cosine(optional);

=item * minLaplace - the minimum laplace(optional);

=item * minJaccard - the minimum jaccard(optional).

=back

=head2 limitRules

The limit of rules(optional)

=head2 output

The output type:(optional)

=over 4

=item * 1 - Text file delimited by tab;

=item * 2 - Excel file with chart.

=back

=head2 messages

A value boolean to display the messages(optional)

=head2 itemsKeyDescription

Hash table to add items by key and description.

=head2 itemsKeyTransactions

Hash table, to add the key items per transaction.

=head2 frequentItemset

Frequent itemset.

=head2 associationRules

A data structure to store the name of the rule, key items, implication, support, confidence, lift, leverage, conviction, coverage, correlation, cosine, laplace and jaccard.

    $self->{associationRules} = {
                                '1' => {
                                       'implication' => '{ 102 } => { 101 }',
                                       'support' => '0.7000',
                                       'correlation' => '0,5092',
                                       'jaccard' => '0,7778',
                                       'confidence' => '0.7778',
                                       'conviction' => '1,3500',
                                       'leverage' => '0,0700',
                                       'laplace' => '0,5862',
                                       'cosine' => '0,8819',
                                       'coverage' => '0,9000',
                                       'lift' => '1,1111',
                                       'items' => [
                                                  '102',
                                                  '101'
                                                  ]
                                        }
                                },
                                # to be continued...

=head1 METHODS

=head2 new

Creates a new instance of Data::Mining::Apriori.

=head2 insert_key_items_transaction(\@items)

Insert key items per transaction. Accepts the following arguments:

=over 4

=item * An array reference to key items.

=back

=head2 input_data_file("datafile.txt",",")

Insert items per line(transaction). Accepts the following arguments:

=over 4

=item * Data file;

=item * Item separator.

=back

	# file contents (example)

	103,101
	103,102
	103,101,102
	103,101,102
	101,102
	103,101,102
	103,101,102
	103,102
	103,101,102
	103,101,102

=head2 quantity_possible_rules

Returns the quantity of possible rules.

=head2 generate_rules

Generate association rules until no set of items meets the minimum support, confidence, lift, leverage, conviction, coverage, correlation, cosine, laplace, jaccard or limit of rules.

=head2 association_rules

Generate association rules by size of large itemsets.

=head1 AUTHOR

Alex Graciano, E<lt>agraciano@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2015-2016 by Alex Graciano

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.12.4 or,
at your option, any later version of Perl 5 you may have available.

=cut
