创新实训(22)——所有推荐算法的整合

前言

到目前为止,我实现了基于流行度和新鲜度的推荐算法/基于文本相似度的推荐/用户协同过滤推荐/分类(标签)协同过滤推荐。我们需要将其整合到一个Util中,方便调用。

推荐算法的整合

/**
 * 文章推荐的接口
 * 可以有很多推荐算法分别实现这个接口,以便于我们后面切换推荐算法或者同时使用多个推荐算法
 * 接口里面的基本方法在设计中,还没有定型,可以根据需要或者实际情况修改
 * 用户id也许可以用用户名,但是看实际情况,id不会重复
 * 返回的文章id会简单一点,不过文章内容也行
 * 也可以有推荐文章tag的方法,这种可以给用户推荐关注的tag主题等
 */
@Service
public class RecommendedUtil {

    @Resource
    private FreshPopularRecommended freshPopularRecommended;

    @Resource
    private SimilarityRecommended similarityRecommended;

    @Resource
    private CFRecommended cfRecommended;

    @Resource
    private ArticleTagDao articleTagDao;

    @Resource
    private ArticleCategoryDao articleCategoryDao;
    /**
     * 根据流行度和新鲜度获取博客的列表  可以进行分页查询(注意与 数据库的limit和offset不同 需要转化一下)
     * @param start   开始位置   start = offset
     * @param end   结束位置     end = offset+limit
     * @return   返回值为articleId组成的list
     */
    public  List<Integer> recommendArticleByFreshPopular(int start,int end)
    {
        return freshPopularRecommended.getArticleByFreshPopular(start,end);
    }

    /**
     *根据文章id  获取相似文章  返回值为articleId列表   应该显示在文章的详细内容最下面
     * @param articleId    当前正在浏览的文章id
     * @return  返回结果为文章id列表  有10个  是提前物化好 存在redis当中的
     */
    public List<Integer> recommendSimilarityArticle(int articleId)
    {
        return similarityRecommended.getSimilarityArticle(articleId);
    }

   
}

说明:
基于流行度和新鲜度的推荐与基于文本相似度的推荐,直接调用了前几天写好的方法。

基于标签的推荐的整合

 /**
     * 基于用户id的个性推荐  基于用户浏览博客文章的标签信息
     * 通过标签筛选博客  随机返回 对应标签的博客
     * 每次请求此接口  返回值都不同
     * @param userId
     * @return
     */
    public  List<Integer> recommendArticleByTag(int userId) throws IOException, TasteException {
        //基于用户的协同过滤 推荐得到的标签id
        List<RecommendedItem> userBasedList = cfRecommended.userBasedRecommendedWithTag(userId,10);
        //基于标签id的协同过滤 推荐得到的标签id
        List<RecommendedItem> itemBaseList = cfRecommended.itemBaseRecommendedWithTag(userId,10);
        Map<Integer,Double> score = new HashMap<>();
        for(RecommendedItem recommendedItem :userBasedList)
        {
            int itemId = (int) recommendedItem.getItemID();
            if(!score.containsKey(itemId))
            {
                score.put(itemId,0.0);
            }
            score.put(itemId,score.get(itemId)+recommendedItem.getValue());
        }
        List<Map.Entry<Integer,Double>> list = new ArrayList<>(score.entrySet());
        list.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));
        int index = 0;
        List<Integer> tagIdList = new ArrayList<>();
        for (Map.Entry<Integer,Double> t : list) {
            tagIdList.add(t.getKey());
            if (++index > 10) {
                break;
            }
        }
        //根据标签 随机选出20篇文章
        List<Integer> articleList = articleTagDao.selectArticleByTagList(userId,tagIdList,20);
        return articleList;
    }

基于文章标签的推荐:将基于用户的协同过滤算法与基于标签的协同过滤算法进行了整合,我通过这两个不同的方法分别推荐出了10个标签,然后将这20个标签通过评分进行排序,选出了综合评分最高的10个标签。然后通过这10个标签,从数据库中查找符合标签并且用户没有浏览过的博客文章。

sql语句的设计:


    /**
     * 根据标签列表 从文章中选出满足标签信息 且用户没有看过的文章  随即返回size个
     * @param userId
     * @param tagIdList   标签列表
     * @param size  随即返回size个
     * @return
     */
    @Select("<script>" +
            "select\n" +
            " distinct B.article_id\n" +
            "from\n" +
            " user_browsing_history as A , article_tag as B\n" +
            "where A.user_id = #{userId} \n" +
            " and B.tag_id in" +
            "<foreach collection='tagIdList' item='item' open='(' separator=',' close=')'>" +
            "#{item} "+
            "</foreach>  ORDER BY  RAND() LIMIT #{size}" +
            "</script>")
    List<Integer> selectArticleByTagList(@Param("userId") int userId ,@Param("tagIdList") List<Integer> tagIdList,@Param("size") int size);

写这个sql语句时,由于要筛选的博客标签是一个列表(推荐出的博客标签的列表),所以用到了mybatis提供的foreach方法进行Arraylist的遍历

 "<foreach collection='tagIdList' item='item' open='(' separator=',' close=')'> 
 #{item} </foreach>  ORDER BY  RAND() LIMIT #{size}"

遍历所有的tag列表。最终与用户浏览记录表做自然连接,返回相应的数据即可。

foreach的用法: foreach元素的属性主要有item,index,collection,open,separator,close。

item:集合中元素迭代时的别名,该参数为必选。
index:在list和数组中,index是元素的序号,在map中,index是元素的key,该参数可选
open:foreach代码的开始符号,一般是(和close=")“合用。常用在in(),values()时。该参数可选
separator:元素之间的分隔符,例如在in()的时候,separator=”,“会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选。
close: foreach代码的关闭符号,一般是)和open=”("合用。常用在in(),values()时。该参数可选。
collection:
要做foreach的对象,作为入参时,List对象默认用"list"代替作为键,数组对象有"array"代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param(“keyName”)来设置键,设置keyName后,list,array将会失效。
除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List
ids。入参是User对象,那么这个collection = “ids”.如果User有属性Ids
ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = “ids.id”
在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:

如果传入的是单参数且参数类型是一个List的时候,collection属性值为list .
如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array .
如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key.

基于分类的推荐的整合

思路和上面的类似,就不再叙述了

 /**
     *  基于用户id的个性推荐  基于用户浏览博客文章的分类信息
     *   通过分类筛选博客  随机返回 对应分类的博客
     * 每次请求此接口  返回值都不同
     * @param userId
     * @return
     * @throws IOException
     * @throws TasteException
     */
    public  List<Integer> recommendArticleByCategory(int userId) throws IOException, TasteException
    {
        //基于用户的协同过滤 推荐得到的标签id
        List<RecommendedItem> userBasedList = cfRecommended.userBasedRecommendedWithCategory(userId,3);
        //基于标签id的协同过滤 推荐得到的标签id
        List<RecommendedItem> itemBaseList = cfRecommended.itemBaseRecommendedWithCategory(userId,3);
        Map<Integer,Double> score = new HashMap<>();
        for(RecommendedItem recommendedItem :userBasedList)
        {
            int itemId = (int) recommendedItem.getItemID();
            if(!score.containsKey(itemId))
            {
                score.put(itemId,0.0);
            }
            score.put(itemId,score.get(itemId)+recommendedItem.getValue());
        }
        List<Map.Entry<Integer,Double>> list = new ArrayList<>(score.entrySet());
        list.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));
        int index = 0;
        List<Integer> tagIdList = new ArrayList<>();
        for (Map.Entry<Integer,Double> t : list) {
            tagIdList.add(t.getKey());
            if (++index > 4) {
                break;
            }
        }
        //根据标签 随机选出20篇文章
        List<Integer> articleList = articleCategoryDao.selectArticleByCategoryList(userId,tagIdList,20);
      return articleList;
    }

sql语句:

    /**
     * 根据分类的id列表   从文章中选出满足分类且 用户没有看过的文章   随即返回size个
     * @param userId
     * @param categoryIdList
     * @param size
     * @return
     */
    @Select("<script>" +
            "select\n" +
            " distinct B.article_id\n" +
            "from\n" +
            " user_browsing_history as A , article_category as B\n" +
            "where A.user_id = #{userId} \n" +
            " and B.category_id in" +
            "<foreach collection='categoryIdList' item='item' open='(' separator=',' close=')'>" +
            "#{item} "+
            "</foreach>  ORDER BY  RAND() LIMIT #{size}" +
            "</script>")
    List<Integer> selectArticleByCategoryList(@Param("userId") int userId , @Param("categoryIdList") List<Integer> categoryIdList, @Param("size") int size);

至此,基本的推荐算法就设计完毕了。接下来准备写有关文章的相关接口以及有关elasticsearch实现的检索功能。

猜你喜欢

转载自blog.csdn.net/baidu_41871794/article/details/106820257