//注:本代码实现Apriori的产生频繁集操作,没有产生关联规则,代码测试没有问题
package com.test;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.LinkedList;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
public class Apriori {
int i = 1;
public static String SPLIT = "、";
public static void main(String[] args) throws IOException {
double minsup = 0.3;
Apriori ap = new Apriori();
List<Set<String>> data = ap.getData();
ap.operateFunc(data, null, minsup, 1);
}
/**
* 实施Apriori的核心方法
* @param data 原始数据集
* @param fData 频繁集
* @param minsup 最小支持度
*/
public void operateFunc(List<Set<String>> data, List<Set<String>> fData, Double minsup, int k){
Map<Set<String>, Double> keySupport = new HashMap<Set<String>, Double>();
List<Set<String>> cData = new LinkedList<Set<String>>();
if(fData == null){ //产生f1频繁集的数据来源
Set<String> items = getItems(data);
for(String str: items){ //原理上是不用将items重新添加到Set中,但这样的话 f1项集的数据来源为Set<String> 在计算最小支持度和减枝操作都不能使用数据类型为List<Set<String>> 的方法了,为了统一,不用写countSupport方法和prune方法的重载,故将其添加到Set集合中
Set<String> set = new HashSet<String>();
set.add(str);
cData.add(set);
}
}
else{
cData = combine(fData, ++k);
}
for(Set<String> set: cData){
keySupport.put(set, countSupport(data, set)); //根据候选集计算其每一个候选条目的支持度
}
List<Set<String>> newFData = prune(keySupport, minsup); //候选集经过减枝成为新一轮的频繁集
//k+1项频繁集为空,结束
if(newFData.isEmpty()){
return;
}
System.out.println("第 " + i +" 次迭代");
for(Set<String> res: newFData){
System.out.println( keySupport.get(res) + " " + res);
}
i++;
operateFunc(data, newFData, minsup, k);
}
/**
* 自连接操作
* @param fData 每一次迭代所产生的频繁集数据
* @param k 代表第k次的迭代
* @return
*/
public List<Set<String>> combine(List<Set<String>> fData, int k){
List<Set<String>> comRes = new LinkedList<Set<String>>();
for(int i = 0; i < fData.size(); i++){
Set<String> set1 = fData.get(i);
for(int j = i + 1; j < fData.size(); j++){
Set<String> set2 = new HashSet<String>(fData.get(j));
set2.addAll(set1);
if(!comRes.contains(set2) && set2.size() == k){ //set无序 例如set1集合有{"a","b"}, set2集合有{"b","a"}采用list的contains方法可以检测出set1和set2是一样的,
comRes.add(set2); //在数据上没有什么区别,不会因为顺序的不同而认为这是两组不同的数据,这在自连接的时候不我们去编写 函数去判断
}
}
}
return comRes;
}
/**
* 计算单个条目在原始数据的支持度
* @param data 原始数据集合
* @param subSet 单个候选条目
* @return 返回每一个条目在原始数据的支持度
*/
public Double countSupport(List<Set<String>> data, Set<String> subSet){
int count = 0;
for(Set<String> dat: data){
if(dat.containsAll(subSet)){
count++;
}
}
return 1.0 * count / data.size();
}
/**
* 得到新的频繁集
* @param cData 每次迭代产生的候选集
* @param minsup 最小支持度
* @return 经过减枝得到新的频繁集
*/
public List<Set<String>> prune(Map<Set<String>, Double> cData, Double minsup){
List<Set<String>> pruRes = new LinkedList<Set<String>>();
for(Map.Entry<Set<String>, Double> entry: cData.entrySet()){
if(entry.getValue() > minsup){
pruRes.add(entry.getKey());
}
}
return pruRes;
}
/**
* 获取原始数据库的不重复的项
* @param data 传入的原始文本数据
* @return 返回不重复的数据条目
*/
public Set<String> getItems(List<Set<String>> data){
Set<String> items = new HashSet<String>();
for(Set<String> lineData: data){
for(String str: lineData){
items.add(str);
}
}
return items;
}
/**
* @return 返回文本数据 List<Set<String>>类型
* @throws IOException
*/
public List<Set<String>> getData() throws IOException{
String path = System.getProperty("user.dir") + "/data.txt";
List<Set<String>> data = new LinkedList<Set<String>>();
try {
FileReader fr = new FileReader(path);
@SuppressWarnings("resource")
BufferedReader br = new BufferedReader(fr);
String line = br.readLine();
while(line != null){
Set<String> set = new HashSet<String>();
Collections.addAll(set, line.split(SPLIT));
data.add(set);
line = br.readLine();
}
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
return data;
}
}
数据 在第一层目录下建立data.txt 文本文件(中药数据)
苍术、厚朴、半夏、人参、茯苓、草果、藿香、橘皮、甘草
半夏、白术、生姜、茯苓、人参、桂心、甘草
乌药、人参、白术、川芎、当归、茯神、甘草、白芷
人参、白术、茯苓、山药、陈皮、木香、砂仁、炙黄芪、当归
人参、鹿茸、补骨脂、巴戟天、当归、杜仲、牛膝、茯苓