package Data::Mining::Apriori;

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

use Exporter;
our @ISA = qw(Exporter);
our %EXPORT_TAGS = ('all'=>[qw(
	validate_data
	quantity_possible_rules
	generate_rules
	association_rules
	stop
	output
	data_persistence
	file
	excel
)]);
our @EXPORT_OK = (@{$EXPORT_TAGS{'all'}});
our @EXPORT = qw();

our $VERSION = 0.11;

my$self;

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

sub new{
	my $type = shift;
	my $class = ref($type)||$type;
	$self = {
		totalTransactions => 0,
		minSupport => 1,
		minConfidence => undef,
		minLift => undef,
		minLeverage => undef,
		minConviction => undef,
		minCoverage => 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 = $self->data_persistence;
	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->{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"};
			next unless $self->{itemsKeyTransactions}{"@implication"};
			my$supportConsequent=$self->{itemsKeyTransactions}{"@implication"};
			my $support = sprintf("%.2f",(($supportConsequent/$self->{totalTransactions})*100));
			next if($support<$self->{minSupport});
			my $confidence = sprintf("%.2f",(($supportConsequent/$supportAntecedent)*100));
			next if(defined $self->{minConfidence} && $confidence < $self->{minConfidence});
			my $lift = sprintf("%.2f",($support/($supportAntecedent*$supportConsequent)));
			next if(defined $self->{minLift} && $lift < $self->{minLift});
			my $leverage = sprintf("%.2f",($support-($supportAntecedent*$supportConsequent)));
			next if(defined $self->{minLeverage} && $leverage < $self->{minLeverage});	
			my $conviction = sprintf("%.2f",((1-$supportAntecedent)/(1-$confidence)));
			next if(defined $self->{minConviction} && $conviction < $self->{minConviction});
			my $coverage = sprintf("%.2f",(($supportAntecedent/$self->{totalTransactions})*100));
			next if(defined $self->{minCoverage} && $coverage < $self->{minCoverage});
			$self->{rule}++;
			$self->{associationRules}{$self->{rule}}{"R$self->{rule}"}{rule} = ["{ @antecedent } => { @consequent }", $support, $confidence, $lift, $leverage, $conviction, $coverage];
			$self->{associationRules}{$self->{rule}}{"R$self->{rule}"}{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){
			print "Save data for further processing in the next run? (Y/N): ";
			$answer = <STDIN>;
			chomp($answer);
			if($answer =~ /^y$/i){
				$self->data_persistence;
			}
			exit;
		}
		else{
			print "\nProcessing... ";
		}
	}
	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 data_persistence{
	use Storable;
	if(wantarray){
		if(-e 'Apriori.storable'){
			print "\nContinue processing using the data saved in the last run? (Y/N): ";
			my$answer = <STDIN>;
			chomp($answer);
			if($answer =~ /^y$/i){
				$self = retrieve('Apriori.storable');
				unlink('Apriori.storable');
				return @{$self->{frequentItemset}};
			}
			else{
				unlink('Apriori.storable');
				return keys(%{$self->{itemsKeyDescription}});
			}
		}
		else{
			return keys(%{$self->{itemsKeyDescription}});
		}
	}
	else{
		store($self,'Apriori.storable');
	}
}

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 %";
	print FILE "\tConfidence %" if defined $self->{minConfidence};
	print FILE "\tLift" if defined $self->{minLift};
	print FILE "\tLeverage" if defined $self->{minLeverage};
	print FILE "\tConviction" if defined $self->{minConviction};
	print FILE "\tCoverage %" if defined $self->{minCoverage};
	print FILE "\n";
	foreach my$rule(sort{$a<=>$b}keys(%{$self->{associationRules}})){
		foreach(@{$self->{associationRules}{$rule}{"R$rule"}{rule}}){s/\./,/;}
		print FILE "R$rule\t${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[1]";
		print FILE "\t${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[2]" if defined $self->{minConfidence};
		print FILE "\t${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[3]" if defined $self->{minLift};
		print FILE "\t${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[4]" if defined $self->{minLeverage};
		print FILE "\t${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[5]" if defined $self->{minConviction};
		print FILE "\t${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[6]" if defined $self->{minCoverage};
		print FILE "\n";
	}
	print FILE "\n";
	foreach my$rule(sort{$a<=>$b}keys(%{$self->{associationRules}})){
		print FILE "Rule R$rule: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[0]\n";
		print FILE "Support: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[1] %\n";
		print FILE "Confidence: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[2] %\n" if defined $self->{minConfidence};
		print FILE "Lift: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[3]\n" if defined $self->{minLift};
		print FILE "Leverage: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[4]\n" if defined $self->{minLeverage};
		print FILE "Conviction: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[5]\n" if defined $self->{minConviction};
		print FILE "Coverage: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[6] %\n" if defined $self->{minCoverage};
		print FILE "Items:\n";
		foreach my$item(@{$self->{associationRules}{$rule}{"R$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 %'];
	push @{$headings},'Confidence %' if defined $self->{minConfidence};
	push @{$headings},'Lift' if defined $self->{minLift};
	push @{$headings},'Leverage' if defined $self->{minLeverage};
	push @{$headings},'Conviction' if defined $self->{minConviction};
	push @{$headings},'Coverage %' if defined $self->{minCoverage};
	my(@rules,@support,@confidence,@lift,@leverage,@conviction,@coverage);	
	foreach my$rule(sort{$a<=>$b}keys(%{$self->{associationRules}})){
		push @rules,"R$rule";
		push @support,${$self->{associationRules}{$rule}{"R$rule"}{rule}}[1];
		push @confidence,${$self->{associationRules}{$rule}{"R$rule"}{rule}}[2] if defined $self->{minConfidence};
		push @lift,${$self->{associationRules}{$rule}{"R$rule"}{rule}}[3] if defined $self->{minLift};
		push @leverage,${$self->{associationRules}{$rule}{"R$rule"}{rule}}[4] if defined $self->{minLeverage};
		push @conviction,${$self->{associationRules}{$rule}{"R$rule"}{rule}}[5] if defined $self->{minConviction};
		push @coverage,${$self->{associationRules}{$rule}{"R$rule"}{rule}}[6] if defined $self->{minCoverage};
	}
	my$line=(scalar(@rules)+1);
	my@data=(\@rules,\@support,);
	push @data,\@confidence if defined $self->{minConfidence};
	push @data,\@lift if defined $self->{minLift};
	push @data,\@leverage if defined $self->{minLeverage};
	push @data,\@conviction if defined $self->{minConviction};
	push @data,\@coverage if defined $self->{minCoverage};
	$worksheet->write('A1', $headings, $bold);
	$worksheet->write('A2', \@data);
	my$chart=$workbook->add_chart(type => 'column', embedded => 1);
	my@columns=('B'..'G');
	my$i=0;
	$chart->add_series(
		name       => 'Support %',
		categories => '=Sheet1!$A$2:$A$'.$line,
		values     => '=Sheet1!$'.$columns[$i].'$2:$'.$columns[$i].'$'.$line,
	);
	if(defined $self->{minConfidence}){
		$i++;
		$chart->add_series(
			name       => 'Confidence %',
			categories => '=Sheet1!$A$2:$A$'.$line,
			values     => '=Sheet1!$'.$columns[$i].'$2:$'.$columns[$i].'$'.$line,
		);
	}
	if(defined $self->{minLift}){
		$i++;
		$chart->add_series(
			name       => 'Lift',
			categories => '=Sheet1!$A$2:$A$'.$line,
			values     => '=Sheet1!$'.$columns[$i].'$2:$'.$columns[$i].'$'.$line,
		);
	}
	if(defined $self->{minLeverage}){
		$i++;
		$chart->add_series(
			name       => 'Leverage',
			categories => '=Sheet1!$A$2:$A$'.$line,
			values     => '=Sheet1!$'.$columns[$i].'$2:$'.$columns[$i].'$'.$line,
		);
	}
	if(defined $self->{minConviction}){
		$i++;
		$chart->add_series(
			name       => 'Conviction',
			categories => '=Sheet1!$A$2:$A$'.$line,
			values     => '=Sheet1!$'.$columns[$i].'$2:$'.$columns[$i].'$'.$line,
		);
	}
	if(defined $self->{minCoverage}){
		$i++;
		$chart->add_series(
			name       => 'Coverage %',
			categories => '=Sheet1!$A$2:$A$'.$line,
			values     => '=Sheet1!$'.$columns[$i].'$2:$'.$columns[$i].'$'.$line,
		);
	}
	$worksheet->insert_chart('I2', $chart);
	$line+=2;
	foreach my$rule(sort{$a<=>$b}keys(%{$self->{associationRules}})){
		$worksheet->write("A$line","Rule R$rule: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[0]");
		$line++;
		$worksheet->write("A$line","Support: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[1] %");
		$line++;
		if(defined $self->{minConfidence}){
			$worksheet->write("A$line","Confidence: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[2] %");
			$line++;
		}
		if(defined $self->{minLift}){
			$worksheet->write("A$line","Lift: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[3]");
			$line++;
		}
		if(defined $self->{minLeverage}){
			$worksheet->write("A$line","Leverage: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[4]");
			$line++;
		}
		if(defined $self->{minConviction}){
			$worksheet->write("A$line","Conviction: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[5]");
			$line++;
		}
		if(defined $self->{minCoverage}){
			$worksheet->write("A$line","Coverage: ${$self->{associationRules}{$rule}{\"R$rule\"}{rule}}[6] %");
			$line++;
		}
		$worksheet->write("A$line","Items:");
		$line++;
		foreach my$item(@{$self->{associationRules}{$rule}{"R$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->{minSupport}=1.55; # The minimum support, default values is 1(percent)

	$apriori->{minConfidence}=1.55; # The minimum confidence(percent, optional)

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

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

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

	$apriori->{minCoverage}=0; # The minimum coverage(percent, 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

	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 %
	R1	70,00	77,78	1,11	7,00	0,10	90,00
	R2	80,00	88,89	1,11	8,00	0,09	90,00
	R3	70,00	87,50	1,25	14,00	0,08	80,00
	R4	70,00	87,50	1,25	14,00	0,08	80,00
	R5	80,00	88,89	1,11	8,00	0,09	90,00
	R6	70,00	77,78	1,11	7,00	0,10	90,00

	Rule R1: { 103 } => { 101 }
	Support: 70,00 %
	Confidence: 77,78 %
	Lift: 1,11
	Leverage: 7,00
	Conviction: 0,10
	Coverage: 90,00 %
	Items:
	103 CEREAL
	101 MILK

	to be continued...

	#output file "output_itemset_size_3.txt"

	Rules	Support %	Confidence %	Lift	Leverage	Conviction	Coverage %
	R7	60,00	66,67	1,11	6,00	0,12	90,00
	R8	60,00	85,71	1,43	18,00	0,07	70,00
	R9	60,00	75,00	1,25	12,00	0,09	80,00
	R10	60,00	75,00	1,25	12,00	0,09	80,00

	Rule R7: { 103 } => { 101, 102 }
	Support: 60,00 %
	Confidence: 66,67 %
	Lift: 1,11
	Leverage: 6,00
	Conviction: 0,12
	Coverage: 90,00 %
	Items:
	103 CEREAL
	101 MILK
	102 BREAD

	Rule R8: { 101, 103 } => { 102 }
	Support: 60,00 %
	Confidence: 85,71 %
	Lift: 1,43
	Leverage: 18,00
	Conviction: 0,07
	Coverage: 70,00 %
	Items:
	101 MILK
	103 CEREAL
	102 BREAD

	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->{minSupport}=1.55;

	$apriori->{minConfidence}=1.55;

	$apriori->{minLift}=1;

	$apriori->{minLeverage}=0;

	$apriori->{minConviction}=0;

	$apriori->{minCoverage}=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 minSupport

The minimum support.(percent)

=head2 minConfidence

The minimum confidence.(percent, optional)

=head2 minLift

The minimum lift.(optional)

=head2 minLeverage

The minimum leverage.(optional)

=head2 minConviction

The minimum conviction.(optional)

=head2 minCoverage

The minimum coverage.(percent, optional)

=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

Reference to array, to add the transactions of each item per key.

=head2 frequentItemset

Frequent itemset.

=head2 associationRules

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

	$self->{associationRules} = {
                                    '1' => {
                                                'R1' => {
                                                            'items' => [
                                                                         '103',
                                                                         '101'
                                                                       ],
                                                            'rule' => [
                                                                        '{ 103 } => { 101 }',
                                                                        '70,00',
                                                                        '77,78',
                                                                        '1,11',
                                                                        '7,00',
                                                                        '0,10',
                                                                        '90,00'
                                                                      ]
                                                        }
                                            },
                                            # 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

=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 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
