上回说到可能有好多人匹配不到学习路径,需要一个其他内容的推荐,所以今天就做了这个:
生成部分的主要逻辑是查询用户最近的学习记录,对记录中的每一个项,查询它到达知识图谱中其他项的支持度,并且分别累加,这样就得到了用户最近学习序列到图中所有项的支持度,按降序排列,筛选前n个且不在最近学习序列中的元素作为推荐事件推荐给用户,实现如下:
/**
* 根据有向图生成推荐的项(事件、课程)
* @param recentSeq 用户的最近访问序列
* @param recNum 要推荐的项数
* @return 包含字符串代表的推荐项的向量
*/
private Vector generateRecItemsFromGraph(Vector> recentSeq, int recNum) {
Vector recItems = new Vector<>();
int itemCount = ap.itemList.size();
Map itemToIndex = ap.itemMaps;
// 对最近项集中的项的相关联项计数
int[] allItems = new int[itemCount];
Vector recentItems = new Vector<>();
// 对最近项集中每个项集
for (Vector items : recentSeq) {
// 每个项集中的每个项
for (String item : items) {
// 添加到列表中
recentItems.add(item);
}
}
for (String item : recentItems) {
// 从图中找到各个其他项的支持度 累加
int recentItemIndex = itemToIndex.get(item);
for (int assItemIndex = 0; assItemIndex < itemCount; assItemIndex++) {
if (recentItemIndex == assItemIndex) {
continue;
}
allItems[assItemIndex] += graph[recentItemIndex][assItemIndex];
}
}
int[] indexs = sortedIndexByValue(allItems);
for (int i = 0; i < indexs.length && i < recNum; i++) {
String recItem = ap.itemList.get(indexs[i]);
// Vector> allUserItems = new Vector<>();
// for (Vector vector : sr.data) {
//
// }
if (recentItems.contains(recItem)) {
continue;
}
recItems.add(recItem);
}
return recItems;
}
/**
* 把数组内数据按降序排列 并返回每个数对应的原下标
* @param nums 要排序的数组
* @return 数组存放的排序后的下标
*/
private int[] sortedIndexByValue(int[] nums) {
int[] indexs = new int[nums.length];
for (int i = 0; i < indexs.length; i++) {
indexs[i] = i;
}
for (int i = 0; i < indexs.length; i++) {
for (int j = 1; j < indexs.length-i; j++) {
if (nums[j-1] > nums[j]) {
int temp;
temp = nums[j-1];
nums[j-1] = nums[j];
nums[j]=temp;
// int temp;
temp = indexs[j-1];
indexs[j-1] = indexs[j];
indexs[j]=temp;
}
}
}
return indexs;
}
然后是显示,这部分并没有什么技术含量了,只是把之前各个部分整合起来,然后写的规整一点。其中稍微研究了一下的是有一个序列和子序列的问题,就是有可能会有【【1】【2】【1,2】】这种情况,这种其实是有意义的,表示的是先学1,再学2,然后同时复习1,2,但是说实话直接这么显示出来看起来有点蠢,所以想了个算法把频繁序列里这种东西都去掉了,算法在代码注释中:
package aprioriAll;
import java.util.Vector;
public class RecomendInfo {
static String noSeqInfo = "抱歉,系统并没有匹配到合适的学习路径。";
static String hasSeqInfo = "系统根据您最近的学习内容,为您推荐的学习路径是:\n";
boolean hasSeq = false;
String seqInfo = "";
String recItemInfo = "";
public String generateSeqInfo(Vector> seq,Vector> recentItems) {
hasSeq = true;
seqInfo = seqToString(seq)
+ ",\n其中您学过了:\n"
+ seqToString(recentItems)
+ "\n请查漏补缺,并进行接下来的学习。"
+ "";
return seqInfo;
}
public String getSeqInfo() {
if (hasSeq) {
return hasSeqInfo + seqInfo;
}else {
return noSeqInfo;
}
}
private String seqToString(Vector> longseq) {
// 不修正
// Vector> seq = longseq;
Vector> seq = refineSeq(longseq);
String result = "";
for (int i = 0; i < seq.size()-1; i++) {
result += (seq.get(i).toString() + " ==> ");
}
result += (seq.lastElement().toString());
return result;
}
/**
* 修正序列中多个子项之后跟着一个和项集的问题 虽然这是有意义的(学习顺序) 但是看起来有点蠢 可以不采用
* @param seq 需要修正的序列
* @return 修正后的序列
*/
private Vector> refineSeq(Vector> seq) {
// 被抛弃的算法 找每一个事件集是否被其他事件集包含
// Vector> tempSeq = new Vector<>();
// for (int i = 0; i < seq.size(); i++) {
// Vector curItemSet = seq.get(i);
// boolean containInOther = false;
// for (int j = i+1; j < seq.size(); j++) {
// Vector testItemSet = seq.get(j);
// if (testItemSet.containsAll(curItemSet)) {
// containInOther = true;
// break;
// }
// }
// if (!containInOther) {
// tempSeq.add(curItemSet);
// }
// }
// return tempSeq;
// 更快的算法 从后向前检查是否当前项集包含前一个
Vector> tempSeq = new Vector<>(seq);
for (int i = tempSeq.size()-1; i > 0; i--) {
Vector latter = tempSeq.get(i);
Vector former = tempSeq.get(i-1);
if (latter.containsAll(former)) {
tempSeq.remove(i-1);
}
}
return tempSeq;
}
public String generateRecItemInfo(Vector recItems) {
// TODO Auto-generated method stub
if (recItems.isEmpty()) {
recItemInfo = "暂无推荐信息,请自由探索!";
} else {
recItemInfo = ""
+ "系统根据您最近的学习内容,为您推荐的学习内容是:\n"
+ recItems
+ "";
}
return recItemInfo;
}
public String getRecItemInfo() {
return recItemInfo;
}
}
/**
* 生成推荐并显示
*/
private void generateRecommend() {
// TODO Auto-generated method stub
sr = new SequenceRecommender(ap.resultSet);
Vector freqSeq = sr.matchSequence(ap.data);
System.out.println(freqSeq);
// 对每个用户
for (int i = 0; i < freqSeq.size(); i++) {
RecomendInfo ri = new RecomendInfo();
if (freqSeq.get(i) == -1) {
ri.hasSeq = false;
Vector recItems = generateRecItemsFromGraph(sr.data.get(i),2);
ri.generateRecItemInfo(recItems);
} else {
ri.hasSeq = true;
ri.generateSeqInfo(ap.resultSet.get(freqSeq.get(i)), sr.data.get(i));
Vector recItems = generateRecItemsFromGraph(sr.data.get(i),2);
ri.generateRecItemInfo(recItems);
}
outputRecommend(ri);
}
}
private void outputRecommend(RecomendInfo ri) {
System.out.println("==============================");
System.out.println(ri.getSeqInfo());
System.out.println(ri.getRecItemInfo());
System.out.println("==============================");
}
最后的输出结果大概是这样的:
[Java, 高等数学, 数据结构, 数据库, 计算机网络, C++, 智能软件, 线性代数, 非关系型数据库]
8 2 4 2 2 0 0 0 0
1 14 4 2 1 0 0 4 1
3 3 18 5 2 1 0 2 1
1 1 1 14 6 1 1 2 1
0 0 0 6 12 0 0 0 0
0 0 1 1 0 0 1 0 0
0 0 0 0 0 1 0 0 0
0 5 1 1 0 0 0 12 1
0 0 0 1 0 0 0 1 0
[-1, -1, -1, 6, -1]
==============================
抱歉,系统并没有匹配到合适的学习路径。
系统根据您最近的学习内容,为您推荐的学习内容是:
[智能软件, C++]
==============================
==============================
抱歉,系统并没有匹配到合适的学习路径。
系统根据您最近的学习内容,为您推荐的学习内容是:
[Java, 高等数学]
==============================
==============================
抱歉,系统并没有匹配到合适的学习路径。
系统根据您最近的学习内容,为您推荐的学习内容是:
[Java]
==============================
==============================
系统根据您最近的学习内容,为您推荐的学习路径是:
[Java] ==> [高等数学] ==> [数据库, 计算机网络],
其中您学过了:
[高等数学] ==> [数据库, 计算机网络]
请查漏补缺,并进行接下来的学习。
系统根据您最近的学习内容,为您推荐的学习内容是:
[C++]
==============================
==============================
抱歉,系统并没有匹配到合适的学习路径。
系统根据您最近的学习内容,为您推荐的学习内容是:
[C++, 智能软件]
==============================
当然了,这只是输出到控制台的,也可以调整一下打到json里,这样就可以接到网页上去了。还是用的之前的测试数据集,所以可以看到依然有昨天说的那个问题,很多都匹配不到序列,但是已经有针对各自的推荐了。让我比较惊喜的是实验结果看起来并不是单纯的推荐数据库里频繁的东西,的确每个人都不太一样,而且有的人因为已经学的多,也的确能推荐到比较冷门的东西,比如智能软件,看来这几天编的算法还有点成效。