/**
* 计算jaccard系数
* @param userIds1 List是收藏了某一首歌曲用户id集合,是int类型
* @param userIds2 List是收藏了另一首歌曲用户id集合,是int类型
* @return
*/
private static float jaccard(List userIds1, List userIds2) {
float intersectionNum=0;//等下计算Jaccard的时候我们需要产生小数,所以这里直接设置为float算了
Iterator it1 = userIds1.iterator();
Iterator it2 = userIds2.iterator();
while(it1.hasNext()){
int userId1=(int) it1.next();
while(it2.hasNext()){
int userId2=(int) it2.next();
if(userId1==userId2){
intersectionNum++;
}
}
}
float Jaccard=intersectionNum/(userIds1.size()+userIds2.size()-intersectionNum);
return Jaccard;
}
是由被用户收藏数最多的10000首歌曲。也就是我们数据库中:mostsmallfavorites表内的歌曲。具体来历请看:利用协同过滤算法:计算歌曲相似度
看一下其sql语句能想起很多,如下所示:
CREATE TABLE mostsmallFavorites SELECT *
FROM (
SELECT *
FROM moresmallfavorites, (
SELECT userid AS uid
FROM moresmallfavorites
GROUP BY userid
HAVING COUNT( userid ) >80
AND COUNT( userid ) <500
)a
WHERE moresmallfavorites.userid = a.uid
)b
CREATE TABLE simmusicjaccard(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
musicid INT,
simmusicid INT,
similarity FLOAT
)
查看其该歌的收藏用户列表:
查看与相似度最低的歌曲的用户列表:
有以下现象:
我觉得这个方式根本不适合用来计算歌曲相似度,反而非常适合用于计算用户相似度。因为用户收藏的歌曲非常多,所以产生的歌曲的重合度也想对比较高。
由此,我产生了另一个推荐引擎的思路:
/**
* 使用Jaccard系数的方式计算歌曲的相似度
* @author chouyou
*
*/
public class Jaccard {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int song1[]={1,2,3,4};
int song2[]={3,4,5,6};
float intersectionNum=0;//计算Jaccard时需要产生小数,故这里设置为float
int i,j;
for(i=0;i
计算结果:
/**
* 使用Jaccard系数的方式计算歌曲的相似度
* @author chouyou
*
*/
public class Jaccard {
/**
* @param args
*/
public static void main(String[] args) {
/**
* 储存与某一首歌相似度最高的10首歌曲
*/
float simSongTop10[][]=new float[3][2];
simSongTop10[0][0]=(float) 0.3;
simSongTop10[0][1]=3;
simSongTop10[1][0]=(float) 0.2;
simSongTop10[1][1]=2;
simSongTop10[2][0]=(float) 0.1;
simSongTop10[2][1]=1;
System.out.print("排序前"+"\n");
for(int i=0;i<3;i++){
System.out.print(simSongTop10[i][0]+" "+simSongTop10[i][1]+"\n");
}
float sortedSimSongTop10[][]=arraySort(simSongTop10,1,false);
System.out.print("排序后"+"\n");
for(int i=0;i<3;i++){
System.out.print(sortedSimSongTop10[i][0]+" "+sortedSimSongTop10[i][1]+"\n");
}
}
/**
* 对一个二维数组进行按某列排序,并且要保证
* @param array 需要排序的数组
* @param row 按第几列进行排序,从1开始
* @param sort true表示降序排列,false表示升序排列
* @return 排好序的二维数组
*/
public static float[][] arraySort( float array[][], int row, boolean sort )
{
if( array!=null && array.length > 0 ) // 假如传入的输入不为 NULL 值
{
int len = array.length; // 得到排序数组的长度
int width = array[0].length; // 得到数组的列数
boolean exchange = true; // 交换记录
float[] temp = new float[width]; // 用于存放临时值
if( len < row ) {
System.out.println( "错误信息:排序列数大于数组列数" );
return null;
}
for( int i=0; i=i; j--)
{
if( sort ) // 从高到底
{
if( array[j][row] < array[j+1][row] )
{
for(int t=0; t array[j+1][row] )
{
for(int t=0; t
结果:
TreeMap simAndsongid = new TreeMap(new Comparator(){
/*
* int compare(Object o1, Object o2) 返回一个基本类型的整型,
* 返回负数表示:o1 小于o2,
* 返回0 表示:o1和o2相等,
* 返回正数表示:o1大于o2。
*/
public int compare(Float o1, Float o2) {
//指定排序器按照降序排列
return o2.compareTo(o1);
}
});
Statement statement = conn.createStatement();
//String sql = "select * from student";
String sql,sql1;
sql = "SELECT musicid FROM mostsmallfavorites GROUP BY musicid";
ResultSet rs1 = statement.executeQuery(sql);
//int musicids[]=new int[10000];//用于储存需要计算相似度的所有歌曲的id
index=0;//就是数组下标
while(rs1.next()){
List userids= new ArrayList();
int musicid = rs1.getInt("musicid");
sql1 = "SELECT userid FROM mostsmallfavorites where musicid ="+musicid;
ResultSet rs2 = statement.executeQuery(sql1);
while(rs2.next()){
userids.add(rs2.getInt("userid"));
}
musicids.put(musicid, userids);
userids=null;
}
Statement statement = conn.createStatement();
Statement statement1 = conn.createStatement();
//String sql = "select * from student";
String sql,sql1;
sql = "SELECT musicid FROM mostsmallfavorites GROUP BY musicid";
ResultSet rs1 = statement.executeQuery(sql);
//int musicids[]=new int[10000];//用于储存需要计算相似度的所有歌曲的id
index=0;//就是数组下标
while(rs1.next()){
List userids= new ArrayList();
int musicid = rs1.getInt("musicid");
sql1 = "SELECT userid FROM mostsmallfavorites where musicid ="+musicid;
ResultSet rs2 = statement1.executeQuery(sql1);
while(rs2.next()){
userids.add(rs2.getInt("userid"));
}
musicids.put(musicid, userids);
userids=null;
}
之前由于使用python代码和皮尔逊方式计算歌曲的相似度,导致一共算了4天,才能将1万首歌曲的相似度计算完成,我本来想使用java和c/c++来计算其他方式的时候,但是这次Jaccard系数的计算过程特别简单,不像皮尔逊方式需要维护巨大的矩阵。所以我觉得这次java的计算速度快并不能说明java就比python快很多。
但是我惊奇的发现,java在读取数据库的速度的时候好慢哦,我这次计算居然要读接近7分钟,而我记得python当时读的更为复杂,才读了1分半钟。
本次计算非常简单,所以我觉得除了读取数据库的时间,其他计算和插入数据库的部分都在5分钟之内结束了。
import java.io.File;
import java.sql.*;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Comparator;
import java.util.TreeMap;
/**
* 使用Jaccard系数的方式计算歌曲的相似度
* @author chouyou
*
*/
public class Jaccard {
public static void main(String[] args) {
Hashtable musicids= new Hashtable();//键为歌曲id,值为收藏用户的列表
int i;
// 驱动程序名
String driver = "com.mysql.jdbc.Driver";
// URL指向要访问的数据库名test
String urlToDB = "jdbc:mysql://localhost:3306/musicrecomsys";//数据默认的端口访问是3306
//MySQL配置时的用户名
String user = "root";
// MySQL配置时的密码
String password = "root";
try {
// 加载驱动程序
Class.forName(driver);
// 连续数据库
Connection conn = DriverManager.getConnection(urlToDB, user, password);
if(!conn.isClosed())
System.out.println("Succeeded connecting to the Database!");
// statement用来执行SQL语句
Statement statement = conn.createStatement();
Statement statement1 = conn.createStatement();
//String sql = "select * from student";
String sql,sql1;
sql = "SELECT musicid FROM mostsmallfavorites GROUP BY musicid";
ResultSet rs1 = statement.executeQuery(sql);
System.out.println("开始读....");
while(rs1.next()){
List userids= new ArrayList();
int musicid = rs1.getInt("musicid");
sql1 = "SELECT userid FROM mostsmallfavorites where musicid ="+musicid;
ResultSet rs2 = statement1.executeQuery(sql1);
while(rs2.next()){
userids.add(rs2.getInt("userid"));
}
musicids.put(musicid, userids);
userids=null;
}
System.out.println("读完....");
Set keyset=musicids.keySet();//拿到所有的键
//遍历所有的键
Iterator it1 = keyset.iterator();
//指定排序器
TreeMap simAndsongid = new TreeMap(new Comparator(){
/*
* int compare(Object o1, Object o2) 返回一个基本类型的整型,
* 返回负数表示:o1 小于o2,
* 返回0 表示:o1和o2相等,
* 返回正数表示:o1大于o2。
*/
public int compare(Float o1, Float o2) {
//指定排序器按照降序排列
return o2.compareTo(o1);
}
});
int flag=0;//输出在控制台表示计算到了第几首。
while(it1.hasNext()){
flag++;
System.out.println("共:"+keyset.size()+" 正在计算第"+flag+"首");
int songId1=(int) it1.next();
List userIds1= new ArrayList();
userIds1=musicids.get(songId1);
Iterator it2= keyset.iterator();
while(it2.hasNext()){
int songId2=(int) it2.next();
if(songId1==songId2){
continue;
}
List userIds2= new ArrayList();
userIds2=musicids.get(songId2);
float similarity=jaccard(userIds1, userIds2);
//把其他歌曲与songId1的相似度存起来,当然只存不为0的。
if(similarity!=0){
simAndsongid.put(similarity, songId2);//每插进去一个就会有高到底的排序
}
}
//计算完一首歌曲,也就是songid1之后,将simAndsongid的前10名结果插入表:simmusicjaccard
for(i=0;i<10;i++){//因为只保留前10首到数据库,所以只循环10次
if(!simAndsongid.isEmpty()){
float similarity=simAndsongid.firstKey();//第一个就是相似度最高的一个
int songId2=simAndsongid.get(similarity);
sql = "INSERT INTO simmusicjaccard SET musicid=\""+songId1+
"\",simmusicid =\""+songId2+"\",similarity =\""+similarity+"\"";
simAndsongid.remove(similarity);//把这次循环最高的给删除掉,那么次高的就会变成最高的
}else{
System.out.println("ID:"+songId1+",无相似歌");
break;
}
}
simAndsongid.clear();
it2=null;
}
}
catch(ClassNotFoundException e) {
System.out.println("Sorry,can`t find the Driver!");
e.printStackTrace();
} catch(SQLException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
}
/**
* 计算jaccard系数
* @param userIds1 List是收藏了某一首歌曲用户id集合,是int类型
* @param userIds2 List是收藏了另一首歌曲用户id集合,是int类型
* @return
*/
private static float jaccard(List userIds1, List userIds2) {
float intersectionNum=0;//等下计算Jaccard的时候我们需要产生小数,所以这里直接设置为float算了
Iterator it1 = userIds1.iterator();
Iterator it2 = userIds2.iterator();
while(it1.hasNext()){
int userId1=(int) it1.next();
while(it2.hasNext()){
int userId2=(int) it2.next();
if(userId1==userId2){
intersectionNum++;
}
}
}
float Jaccard=intersectionNum/(userIds1.size()+userIds2.size()-intersectionNum);
return Jaccard;
}
}