# 常用算法封装

# 方向1

# 1. 推荐算法类

算法 适用场景 特点
基于内容的推荐(Content-Based Filtering) 商品/文章/电影等有详细属性 直接根据物品的标签、关键词、类别等做相似度推荐,解决冷启动问题
基于规则的推荐 业务规则固定 比如销量前N、评分最高N、最近浏览过的类似商品
基于关联规则(Apriori/FP-Growth) 电商、购物车 分析“买了A的人也买了B”
基于图的推荐(Graph-based) 社交、知识图谱 用物品-用户的关系图计算物品间关联
协同过滤(Collaborative Filtering) 用户-物品评分或行为数据 基于用户相似度(UserCF)或物品相似度(ItemCF)推荐,易受冷启动影响
矩阵分解(SVD/SVD++) 用户-物品评分矩阵 比协同过滤更高阶,可捕捉潜在特征
深度学习推荐(DeepFM、YouTube DNN 推荐) 用户数据大 适合做个亮点展示,复杂度高

# 2. 个性化排序/打分类

即使推荐结果相同,也可以通过排序优化个性化效果:

  • 学习排序(Learning to Rank):比如用 XGBoost / LightGBM 排序模型
  • 多因素加权:比如 (协同过滤得分 × 0.6) + (物品热度 × 0.4)

# 协同过滤

  1. 基于用户的协同过滤(User-Based CF)
    • 根据用户之间的相似度,找出与目标用户兴趣最相似的其他用户。
    • 根据这些相似用户的评分预测目标用户可能喜欢的物品。
  2. 基于物品的协同过滤(Item-Based CF)
    • 根据物品之间的相似度,找出与用户已评分物品相似的其他物品。
    • 通过相似物品的加权评分预测用户可能感兴趣的物品。

主要流程

  1. 数据初始化
    • 构造函数接收 ratings(用户 → 物品 → 评分)数据。
    • 计算所有用户之间的相似度(用于 User-Based CF)。
    • 转置评分矩阵,计算物品之间的相似度(用于 Item-Based CF)。
  2. 推荐计算
    • User-Based CF:从相似用户中收集未评分物品 → 计算加权预测分 → 排序取前 N 个。
    • Item-Based CF:从已评分物品的相似物品中收集未评分物品 → 计算加权预测分 → 排序取前 N 个。
  3. 余弦相似度计算
    • 用余弦相似度公式衡量两个用户或两个物品的兴趣相似程度。
flowchart TD
    A[输入用户评分数据] --> B[计算用户相似度]
    A --> C[转置评分矩阵]
    C --> D[计算物品相似度]

    E[调用推荐方法] --> F{选择推荐模式}
    F -->|用户协同过滤| G[获取相似用户]
    G --> H[预测未评分物品的评分]
    H --> I[排序并取前N个物品]

    F -->|物品协同过滤| J[获取相似物品]
    J --> K[预测未评分物品的评分]
    K --> I[排序并取前N个物品]

    B -.-> L[相似度计算]
    D -.-> L

# 封装

package com.base.utils;

import java.util.*;
import java.util.stream.Collectors;

public class RecommendationUtil {

    public enum RecommendationMode {
        USER_BASED,
        ITEM_BASED
    }

    private final Map<Integer, Map<Integer, Double>> ratings;
    private final Map<Integer, Map<Integer, Double>> userSimilarities;
    private final Map<Integer, Map<Integer, Double>> itemSimilarities;
    private final Map<Integer, Map<Integer, Double>> transposedRatings;


    public RecommendationUtil(Map<Integer, Map<Integer, Double>> ratings) {
        this.ratings = ratings;
        // 用于基于用户的协同过滤
        this.userSimilarities = calculateAllUserSimilarities();

        // 用于基于物品的协同过滤
        this.transposedRatings = transposeRatings();
        this.itemSimilarities = calculateAllItemSimilarities();
    }

    private Map<Integer, Map<Integer, Double>> transposeRatings() {
        Map<Integer, Map<Integer, Double>> transposed = new HashMap<>();
        for (Map.Entry<Integer, Map<Integer, Double>> userEntry : ratings.entrySet()) {
            int userId = userEntry.getKey();
            for (Map.Entry<Integer, Double> itemEntry : userEntry.getValue().entrySet()) {
                int itemId = itemEntry.getKey();
                double score = itemEntry.getValue();
                transposed.computeIfAbsent(itemId, k -> new HashMap<>()).put(userId, score);
            }
        }
        return transposed;
    }

    private Map<Integer, Map<Integer, Double>> calculateAllUserSimilarities() {
        Map<Integer, Map<Integer, Double>> similarities = new HashMap<>();
        List<Integer> userIds = new ArrayList<>(ratings.keySet());
        for (int i = 0; i < userIds.size(); i++) {
            for (int j = i; j < userIds.size(); j++) {
                int userId1 = userIds.get(i);
                int userId2 = userIds.get(j);
                if (userId1 != userId2) {
                    double sim = cosineSimilarity(ratings.get(userId1), ratings.get(userId2));
                    if (sim > 0) { // 只存储正相似度
                        similarities.computeIfAbsent(userId1, k -> new HashMap<>()).put(userId2, sim);
                        similarities.computeIfAbsent(userId2, k -> new HashMap<>()).put(userId1, sim);
                    }
                }
            }
        }
        return similarities;
    }

    private Map<Integer, Map<Integer, Double>> calculateAllItemSimilarities() {
        Map<Integer, Map<Integer, Double>> similarities = new HashMap<>();
        List<Integer> itemIds = new ArrayList<>(transposedRatings.keySet());
        for (int i = 0; i < itemIds.size(); i++) {
            for (int j = i; j < itemIds.size(); j++) {
                int itemId1 = itemIds.get(i);
                int itemId2 = itemIds.get(j);
                if (itemId1 != itemId2) {
                    double sim = cosineSimilarity(transposedRatings.get(itemId1), transposedRatings.get(itemId2));
                    if (sim > 0) { // 只存储正相似度
                        similarities.computeIfAbsent(itemId1, k -> new HashMap<>()).put(itemId2, sim);
                        similarities.computeIfAbsent(itemId2, k -> new HashMap<>()).put(itemId1, sim);
                    }
                }
            }
        }
        return similarities;
    }

    /**
     * 根据选择的模式获取推荐结果
     *
     * @param userId 用户ID
     * @param topN   返回的推荐数量
     * @param mode   推荐模式(USER_BASED 或 ITEM_BASED)
     * @return 推荐的物品ID及其预测得分列表
     */
    public List<Map.Entry<Integer, Double>> getRecommendations(int userId, int topN, RecommendationMode mode) {
        switch (mode) {
            case USER_BASED:
                return userBasedCF(userId, topN);
            case ITEM_BASED:
                return itemBasedCF(userId, topN);
            default:
                throw new IllegalArgumentException("无效的推荐模式");
        }
    }

    private List<Map.Entry<Integer, Double>> userBasedCF(int userId, int topN) {
        // 1. 获取当前用户的相似用户
        Map<Integer, Double> currentUserSimilarities = userSimilarities.getOrDefault(userId, Collections.emptyMap());

        // 2. 根据相似用户预测未评分物品的评分
        Map<Integer, Double> scorePredictions = new HashMap<>();
        Map<Integer, Double> weightedSumOfSimilarities = new HashMap<>();

        for (Map.Entry<Integer, Double> simEntry : currentUserSimilarities.entrySet()) {
            Integer otherUserId = simEntry.getKey();
            double similarity = simEntry.getValue();

            for (Map.Entry<Integer, Double> itemEntry : ratings.get(otherUserId).entrySet()) {
                Integer itemId = itemEntry.getKey();
                // 只推荐用户没有评分过的物品
                if (!ratings.get(userId).containsKey(itemId)) {
                    scorePredictions.merge(itemId, similarity * itemEntry.getValue(), Double::sum);
                    weightedSumOfSimilarities.merge(itemId, similarity, Double::sum);
                }
            }
        }

        // 归一化评分
        Map<Integer, Double> normalizedScorePredictions = new HashMap<>();
        for(Map.Entry<Integer, Double> entry : scorePredictions.entrySet()){
            int itemId = entry.getKey();
            double totalSimilarity = weightedSumOfSimilarities.get(itemId);
            if(totalSimilarity > 0){
                normalizedScorePredictions.put(itemId, entry.getValue() / totalSimilarity);
            }
        }

        // 3. 按预测分数排序并取前 N 个
        return normalizedScorePredictions.entrySet()
                .stream()
                .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
                .limit(topN)
                .collect(Collectors.toList());
    }

    private List<Map.Entry<Integer, Double>> itemBasedCF(int userId, int topN) {
        Map<Integer, Double> userRatings = ratings.get(userId);
        Map<Integer, Double> scorePredictions = new HashMap<>();
        Map<Integer, Double> sumOfSimilarities = new HashMap<>();

        // 对用户已评分的每个物品
        for (Map.Entry<Integer, Double> ratedItemEntry : userRatings.entrySet()) {
            int ratedItemId = ratedItemEntry.getKey();
            double rating = ratedItemEntry.getValue();

            // 找到与该物品相似的其他物品
            Map<Integer, Double> similarItems = itemSimilarities.getOrDefault(ratedItemId, Collections.emptyMap());

            for (Map.Entry<Integer, Double> simEntry : similarItems.entrySet()) {
                int similarItemId = simEntry.getKey();
                double similarity = simEntry.getValue();

                // 如果用户没有对该相似物品评分
                if (!userRatings.containsKey(similarItemId)) {
                    // 累积加权得分
                    scorePredictions.merge(similarItemId, similarity * rating, Double::sum);
                    // 累积相似度
                    sumOfSimilarities.merge(similarItemId, similarity, Double::sum);
                }
            }
        }

        // 归一化评分
        Map<Integer, Double> normalizedScorePredictions = new HashMap<>();
        for (Map.Entry<Integer, Double> entry : scorePredictions.entrySet()) {
            int itemId = entry.getKey();
            double totalSimilarity = sumOfSimilarities.getOrDefault(itemId, 0.0);
            if (totalSimilarity > 0) {
                normalizedScorePredictions.put(itemId, entry.getValue() / totalSimilarity);
            }
        }

        // 排序并取前 N 个推荐
        return normalizedScorePredictions.entrySet()
                .stream()
                .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
                .limit(topN)
                .collect(Collectors.toList());
    }


    /**
     * 计算两个向量(Map)的余弦相似度
     */
    private double cosineSimilarity(Map<Integer, Double> vec1, Map<Integer, Double> vec2) {
        double dotProduct = 0.0, normA = 0.0, normB = 0.0;

        Set<Integer> commonKeys = new HashSet<>(vec1.keySet());
        commonKeys.retainAll(vec2.keySet());

        for (Integer key : commonKeys) {
            dotProduct += vec1.get(key) * vec2.get(key);
        }

        for (double val : vec1.values()) {
            normA += Math.pow(val, 2);
        }
        for (double val : vec2.values()) {
            normB += Math.pow(val, 2);
        }

        if (normA == 0 || normB == 0) {
            return 0.0;
        }
        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
    }
}

# 调用示例

package com.base.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestRecommendation {

    // 1. 定义数据模型,模拟数据表中的一条记录
    static class Rating {
        private final int userId;
        private final int itemId;
        private final double rating;

        public Rating(int userId, int itemId, double rating) {
            this.userId = userId;
            this.itemId = itemId;
            this.rating = rating;
        }

        public int getUserId() {
            return userId;
        }

        public int getItemId() {
            return itemId;
        }

        public double getRating() {
            return rating;
        }
    }

    // 2. 模拟从数据源(如数据库)加载数据
    public static List<Rating> loadRatingsData() {
        List<Rating> ratings = new ArrayList<>();
        ratings.add(new Rating(1, 101, 5.0));
        ratings.add(new Rating(1, 102, 3.0));
        ratings.add(new Rating(1, 103, 4.0));
        ratings.add(new Rating(2, 101, 4.0));
        ratings.add(new Rating(2, 102, 2.0));
        ratings.add(new Rating(2, 104, 5.0));
        ratings.add(new Rating(3, 101, 4.0));
        ratings.add(new Rating(3, 103, 5.0));
        ratings.add(new Rating(3, 104, 3.0));
        ratings.add(new Rating(4, 102, 4.0));
        ratings.add(new Rating(4, 105, 5.0));
        ratings.add(new Rating(4, 106, 4.5));
        ratings.add(new Rating(5, 103, 5.0));
        ratings.add(new Rating(5, 105, 4.0));
        ratings.add(new Rating(5, 107, 4.5));
        return ratings;
    }

    // 3. 将加载的数据转换为推荐引擎所需的格式
    public static Map<Integer, Map<Integer, Double>> transformToUserRatingsMap(List<Rating> ratings) {
        Map<Integer, Map<Integer, Double>> userRatings = new HashMap<>();
        for (Rating rating : ratings) {
            userRatings.computeIfAbsent(rating.getUserId(), k -> new HashMap<>())
                       .put(rating.getItemId(), rating.getRating());
        }
        return userRatings;
    }

    public static void main(String[] args) {
        // 1. 从模拟数据源加载数据
        List<Rating> ratingData = loadRatingsData();

        // 2. 将数据转换为推荐引擎所需的格式
        Map<Integer, Map<Integer, Double>> ratings = transformToUserRatingsMap(ratingData);

        // 3. 创建推荐引擎实例
        RecommendationUtil recommender = new RecommendationUtil(ratings);

        int userId = 1;
        int topN = 2;

        System.out.println("为用户 " + userId + " 推荐 " + topN + " 个物品:");

        // 1. 基于用户的协同过滤推荐
        List<Map.Entry<Integer, Double>> userBasedRecommendations = recommender.getRecommendations(userId, topN, RecommendationUtil.RecommendationMode.USER_BASED);
        System.out.println("基于用户的推荐结果 (User-Based): " + userBasedRecommendations);

        // 2. 基于物品的协同过滤推荐
        List<Map.Entry<Integer, Double>> itemBasedRecommendations = recommender.getRecommendations(userId, topN, RecommendationUtil.RecommendationMode.ITEM_BASED);
        System.out.println("基于物品的推荐结果 (Item-Based): " + itemBasedRecommendations);

        // 为另一个用户推荐
        userId = 4;
        System.out.println("\n为用户 " + userId + " 推荐 " + topN + " 个物品:");

        userBasedRecommendations = recommender.getRecommendations(userId, topN, RecommendationUtil.RecommendationMode.USER_BASED);
        System.out.println("基于用户的推荐结果 (User-Based): " + userBasedRecommendations);

        itemBasedRecommendations = recommender.getRecommendations(userId, topN, RecommendationUtil.RecommendationMode.ITEM_BASED);
        System.out.println("基于物品的推荐结果 (Item-Based): " + itemBasedRecommendations);
    }
}

输出如下:

为用户 1 推荐 2 个物品:

基于用户的推荐结果 (User-Based): [106=4.5, 107=4.5]
基于物品的推荐结果 (Item-Based): [104=4.2998742574479225, 107=4.0]

为用户 4 推荐 2 个物品:
基于用户的推荐结果 (User-Based): [104=5.0, 103=4.600933258255171]
基于物品的推荐结果 (Item-Based): [107=5.0, 103=4.583628681216292]

# 基于内容

  1. 单物品内容推荐recommendByContentTFIDF
    • 输入一个目标物品 ID,计算它与所有其他物品的相似度
    • 排序取前 N 个相似度最高的物品作为推荐结果
  2. 基于用户画像推荐recommendForUserProfile
    • 将用户历史交互过的物品的标签集合成用户画像
    • 用 TF-IDF 表示用户画像向量
    • 计算用户画像与其他物品的相似度
    • 排除用户已拥有的物品,排序取前 N 个推荐

主要步骤

  1. TF 计算:统计标签在物品中的出现频率
  2. IDF 计算:统计标签在所有物品中出现的文档频率,计算逆文档频率
  3. TF-IDF 向量:结合 TF 和 IDF 计算物品的内容向量
  4. 余弦相似度:衡量两个向量(物品或用户画像)之间的相似程度
  5. 排序推荐:按相似度从高到低排序,取前 N 个结果
flowchart TD
    A[输入物品及标签数据] --> B[计算TF]
    B --> C[计算IDF]
    C --> D[计算TF-IDF向量]
    
    D --> E[单物品推荐]
    E --> F[计算物品相似度]
    F --> G[排序取TopN结果]

    D --> H[用户画像推荐]
    H --> I[合并用户历史物品标签]
    I --> J[计算用户画像TF-IDF向量]
    J --> K[计算用户画像与物品相似度]
    K --> G[排序取TopN结果]

# 封装

package com.base.ContentBasedFiltering;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 基于内容的推荐工具类 (TF-IDF + 余弦相似度)
 *
 * 优化说明:
 * 1. 将TF, IDF, TF-IDF的计算拆分为独立的辅助方法,提高代码复用性和可读性。
 * 2. 使用 Map<String, Double> 作为TF-IDF向量的稀疏表示,比 double[] 更节省内存。
 * 3. 新增 recommendForUserProfile 方法,用于基于用户的多个历史物品进行推荐。
 */
public class ContentBasedRecommendationUtil {

    /**
     * 基于单个目标物品,推荐最相似的TopN个其他物品。
     *
     * @param allItems     所有物品及其内容标签的Map, e.g., {101: ["Java", "后端"]}
     * @param targetItemId 目标物品ID
     * @param topN         推荐数量
     * @return 推荐的物品ID及其相似度得分列表
     */
    public static List<Map.Entry<Integer, Double>> recommendByContentTFIDF(
            Map<Integer, List<String>> allItems,
            int targetItemId,
            int topN) {

        if (!allItems.containsKey(targetItemId)) {
            throw new IllegalArgumentException("目标物品ID不存在: " + targetItemId);
        }

        // 1. 计算所有物品的TF-IDF向量
        Map<Integer, Map<String, Double>> allTfIdfVectors = calculateAllTfIdfVectors(allItems);

        // 2. 获取目标物品的向量
        Map<String, Double> targetVector = allTfIdfVectors.get(targetItemId);

        // 3. 计算目标向量与其他所有物品向量的余弦相似度
        Map<Integer, Double> similarities = new HashMap<>();
        for (Map.Entry<Integer, Map<String, Double>> entry : allTfIdfVectors.entrySet()) {
            Integer currentItemId = entry.getKey();
            // 跳过目标物品本身
            if (currentItemId.equals(targetItemId)) {
                continue;
            }
            double similarity = cosineSimilarity(targetVector, entry.getValue());
            if (similarity > 0) { // 只保留有相似度的物品
                similarities.put(currentItemId, similarity);
            }
        }

        // 4. 排序并返回TopN结果
        return similarities.entrySet().stream()
                .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
                .limit(topN)
                .collect(Collectors.toList());
    }

    /**
     * 基于用户的一组历史物品(用户画像),推荐最符合其兴趣的TopN个新物品。
     *
     * @param allItems    所有物品及其内容标签的Map
     * @param userItemIds 用户已经交互过的物品ID列表
     * @param topN        推荐数量
     * @return 推荐的物品ID及其相似度得分列表
     */
    public static List<Map.Entry<Integer, Double>> recommendForUserProfile(
            Map<Integer, List<String>> allItems,
            List<Integer> userItemIds,
            int topN) {

        // 1. 聚合用户画像的标签
        List<String> userProfileTags = new ArrayList<>();
        for (Integer itemId : userItemIds) {
            if (allItems.containsKey(itemId)) {
                userProfileTags.addAll(allItems.get(itemId));
            }
        }

        if (userProfileTags.isEmpty()) {
            return Collections.emptyList(); // 如果用户画像为空,无法推荐
        }

        // 2. 计算所有物品的TF-IDF向量和IDF值
        Map<String, Double> idfMap = calculateIdf(allItems);
        Map<Integer, Map<String, Double>> allTfIdfVectors = calculateAllTfIdfVectors(allItems, idfMap);

        // 3. 计算用户画像的TF-IDF向量
        Map<String, Double> userProfileTf = calculateTf(userProfileTags);
        Map<String, Double> userProfileTfIdfVector = calculateTfIdf(userProfileTf, idfMap);

        // 4. 计算用户画像向量与所有其他物品向量的余弦相似度
        Map<Integer, Double> similarities = new HashMap<>();
        for (Map.Entry<Integer, Map<String, Double>> entry : allTfIdfVectors.entrySet()) {
            Integer currentItemId = entry.getKey();

            // 排除用户已经拥有的物品
            if (!userItemIds.contains(currentItemId)) {
                double similarity = cosineSimilarity(userProfileTfIdfVector, entry.getValue());
                if (similarity > 0) {
                    similarities.put(currentItemId, similarity);
                }
            }
        }

        // 5. 排序并返回TopN结果
        return similarities.entrySet().stream()
                .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
                .limit(topN)
                .collect(Collectors.toList());
    }


    // --- 私有辅助方法 ---

    /**
     * 计算整个数据集中所有物品的TF-IDF向量
     */
    private static Map<Integer, Map<String, Double>> calculateAllTfIdfVectors(Map<Integer, List<String>> allItems) {
        Map<String, Double> idfMap = calculateIdf(allItems);
        return calculateAllTfIdfVectors(allItems, idfMap);
    }

    /**
     * 计算整个数据集中所有物品的TF-IDF向量 (重载方法,接收已计算好的IDF)
     */
    private static Map<Integer, Map<String, Double>> calculateAllTfIdfVectors(Map<Integer, List<String>> allItems, Map<String, Double> idfMap) {
        Map<Integer, Map<String, Double>> allTfIdfVectors = new HashMap<>();
        for (Map.Entry<Integer, List<String>> entry : allItems.entrySet()) {
            Map<String, Double> tf = calculateTf(entry.getValue());
            Map<String, Double> tfIdf = calculateTfIdf(tf, idfMap);
            allTfIdfVectors.put(entry.getKey(), tfIdf);
        }
        return allTfIdfVectors;
    }

    /**
     * 计算单个文档(物品)的词频 (Term Frequency)
     * @param tags 物品的标签列表
     * @return 一个Map,键是标签,值是该标签的TF值
     */
    private static Map<String, Double> calculateTf(List<String> tags) {
        Map<String, Double> tfMap = new HashMap<>();
        if (tags == null || tags.isEmpty()) {
            return tfMap;
        }
        long totalTerms = tags.size();
        // 使用Map来统计词频,比Collections.frequency更高效
        Map<String, Long> termCounts = tags.stream()
                .collect(Collectors.groupingBy(tag -> tag, Collectors.counting()));

        for (Map.Entry<String, Long> entry : termCounts.entrySet()) {
            tfMap.put(entry.getKey(), (double) entry.getValue() / totalTerms);
        }
        return tfMap;
    }

    /**
     * 计算数据集中所有词的逆文档频率 (Inverse Document Frequency)
     * @param allItems 所有物品及其标签
     * @return 一个Map,键是标签,值是该标签的IDF值
     */
    private static Map<String, Double> calculateIdf(Map<Integer, List<String>> allItems) {
        Map<String, Double> idfMap = new HashMap<>();
        int totalDocs = allItems.size();
        if (totalDocs == 0) {
            return idfMap;
        }

        // 先获取所有唯一的词
        Set<String> allUniqueTags = allItems.values().stream()
                .flatMap(List::stream)
                .collect(Collectors.toSet());

        // 计算每个词出现在多少个文档中
        for (String tag : allUniqueTags) {
            int docCount = 0;
            for (List<String> tags : allItems.values()) {
                // 使用Set进行判断,避免重复计算
                if (new HashSet<>(tags).contains(tag)) {
                    docCount++;
                }
            }
            // 加1平滑,避免分母为0
            idfMap.put(tag, Math.log((double) totalDocs / (docCount + 1.0)));
        }
        return idfMap;
    }

    /**
     * 计算TF-IDF值
     * @param tfMap 单个物品的TF Map
     * @param idfMap 整个数据集的IDF Map
     * @return 一个Map,表示该物品的TF-IDF向量
     */
    private static Map<String, Double> calculateTfIdf(Map<String, Double> tfMap, Map<String, Double> idfMap) {
        Map<String, Double> tfIdfVector = new HashMap<>();
        for (Map.Entry<String, Double> entry : tfMap.entrySet()) {
            String tag = entry.getKey();
            double tf = entry.getValue();
            double idf = idfMap.getOrDefault(tag, 0.0); // 如果IDF中没有这个词,则IDF值为0
            tfIdfVector.put(tag, tf * idf);
        }
        return tfIdfVector;
    }

    /**
     * 计算两个稀疏向量(用Map表示)的余弦相似度
     */
    private static double cosineSimilarity(Map<String, Double> vec1, Map<String, Double> vec2) {
        if (vec1 == null || vec2 == null || vec1.isEmpty() || vec2.isEmpty()) {
            return 0.0;
        }

        // 求点积
        double dotProduct = 0.0;
        // 遍历较小的map以提高效率
        Map<String, Double> smallerVec = vec1.size() < vec2.size() ? vec1 : vec2;
        Map<String, Double> largerVec = vec1.size() < vec2.size() ? vec2 : vec1;

        for (Map.Entry<String, Double> entry : smallerVec.entrySet()) {
            if (largerVec.containsKey(entry.getKey())) {
                dotProduct += entry.getValue() * largerVec.get(entry.getKey());
            }
        }

        // 求向量的模
        double normA = 0.0;
        for (double val : vec1.values()) {
            normA += val * val;
        }
        normA = Math.sqrt(normA);

        double normB = 0.0;
        for (double val : vec2.values()) {
            normB += val * val;
        }
        normB = Math.sqrt(normB);

        // 计算余弦相似度
        if (normA == 0 || normB == 0) {
            return 0.0;
        } else {
            return dotProduct / (normA * normB);
        }
    }
}

# 调用示例

package com.base.ContentBasedFiltering;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestContentBasedTFIDF {
    public static void main(String[] args) {
        Map<Integer, List<String>> items = new HashMap<>();
        items.put(101, Arrays.asList("Java", "编程", "后端", "Java"));
        items.put(102, Arrays.asList("Python", "人工智能", "数据分析","算法"));
        items.put(103, Arrays.asList("Java", "Spring Boot", "微服务"));
        items.put(104, Arrays.asList("大数据", "Hadoop", "分布式"));
        items.put(105, Arrays.asList("Java", "算法", "LeetCode"));

        // --- 场景一:基于单个物品推荐 (原逻辑) ---
        int targetItemId = 101;
        int topNForItem = 3;
        System.out.println("--- 场景一:为物品 " + targetItemId + " (\"" + String.join(", ", items.get(targetItemId)) + "\") 推荐相似物品 ---");
        List<Map.Entry<Integer, Double>> singleItemResult =
                ContentBasedRecommendationUtil.recommendByContentTFIDF(items, targetItemId, topNForItem);
        printRecommendations(singleItemResult, items);


        System.out.println("\n--------------------------------------------------\n");


        // --- 场景二:基于用户的物品历史进行推荐 ---
        // 假设一个用户喜欢物品 101 ("Java", "编程") 和 105 ("Java", "算法")
        // 他的用户画像会同时包含 "Java", "编程", "后端", "算法", "LeetCode" 等标签
        List<Integer> userHistoryItemIds = Arrays.asList(101, 105);
        int topNForUser = 3;

        System.out.println("--- 场景二:为拥有物品 " + userHistoryItemIds + " 的用户进行推荐 ---");

        // 调用新的、基于用户画像的推荐方法
        List<Map.Entry<Integer, Double>> userRecommendations =
                ContentBasedRecommendationUtil.recommendForUserProfile(items, userHistoryItemIds, topNForUser);

        printRecommendations(userRecommendations, items);
    }

    /**
     * 辅助方法,用于格式化并打印推荐结果
     * @param recommendations 推荐结果列表
     * @param allItems 所有物品的数据,用于查找标签
     */
    private static void printRecommendations(List<Map.Entry<Integer, Double>> recommendations, Map<Integer, List<String>> allItems) {
        if (recommendations == null || recommendations.isEmpty()) {
            System.out.println("没有找到推荐结果。");
        } else {
            System.out.println("推荐结果如下:");
            for (Map.Entry<Integer, Double> entry : recommendations) {
                Integer recommendedItemId = entry.getKey();
                Double similarityScore = entry.getValue();
                // 额外打印出物品的标签,方便理解推荐原因
                System.out.printf("  -> 推荐物品 ID: %-5d | 相似度: %.4f | 物品标签: %s%n",
                        recommendedItemId,
                        similarityScore,
                        allItems.get(recommendedItemId));
            }
        }
    }
}

输出

--- 场景一:为物品 101 ("Java, 编程, 后端, Java") 推荐相似物品 ---
推荐结果如下:
  -> 推荐物品 ID: 105   | 相似度: 0.0677 | 物品标签: [Java, 算法, LeetCode]
  -> 推荐物品 ID: 103   | 相似度: 0.0553 | 物品标签: [Java, Spring Boot, 微服务]

--------------------------------------------------

--- 场景二:为拥有物品 [101, 105] 的用户进行推荐 ---
推荐结果如下:
  -> 推荐物品 ID: 102   | 相似度: 0.0871 | 物品标签: [Python, 人工智能, 数据分析, 算法]
  -> 推荐物品 ID: 103   | 相似度: 0.0632 | 物品标签: [Java, Spring Boot, 微服务]

# 基于关联规则

规则挖掘(mineRules

  • 输入交易记录(每个交易是一组物品)
  • 统计所有可能的二项集(两个商品的组合)出现的频率
  • 计算 支持度(Support):二项集出现次数 / 总交易数
  • 如果支持度 ≥ minSupport,计算 置信度(Confidence)
    • conf(A→B) = 同时买 A 和 B 的交易数 / 买 A 的交易数
    • conf(B→A) 类似计算
  • 如果置信度 ≥ minConfidence,将规则加入规则列表

推荐(recommendForUser

  • 根据用户已购买商品,匹配规则左侧(LHS)
  • 将规则右侧(RHS)中用户未买过的商品加入推荐集合
  • 累加多个规则的置信度作为推荐分数
  • 按分数排序,取前 N 个推荐

辅助方法

  • calcConfidence:计算给定商品对的置信度
flowchart TD
    A[输入交易数据] --> B[统计所有二项集频次]
    B --> C[计算每个二项集的支持度]
    C --> D{支持度>=阈值?}
    D -->|是| E[计算两个方向的置信度]
    E --> F{置信度>=阈值?}
    F -->|是| G[保存关联规则]
    F -->|否| H[丢弃规则]
    D -->|否| H

    G --> I[用户推荐]
    I --> J[匹配用户已购商品与规则LHS]
    J --> K[收集RHS中未购买的商品]
    K --> L[累计置信度作为推荐分数]
    L --> M[排序并取TopN推荐结果]

# 封装

package com.base.AssociationRuleMining;

import java.util.*;

public class AssociationRuleUtil {

    // 保存规则(左边 -> 右边)
    private static List<Rule> minedRules = new ArrayList<>();

    public static class Rule {
        Set<String> lhs; // 条件
        Set<String> rhs; // 推荐项
        double support;
        double confidence;

        public Rule(Set<String> lhs, Set<String> rhs, double support, double confidence) {
            this.lhs = lhs;
            this.rhs = rhs;
            this.support = support;
            this.confidence = confidence;
        }
    }

    /**
     * 挖掘简单二项集关联规则
     */
    public static void mineRules(
            List<Set<String>> transactions,
            double minSupport,
            double minConfidence) {

        minedRules.clear();

        Map<Set<String>, Integer> itemCount = new HashMap<>();
        int total = transactions.size();

        // 统计二项集频次
        for (Set<String> transaction : transactions) {
            List<String> items = new ArrayList<>(transaction);
            for (int i = 0; i < items.size(); i++) {
                for (int j = i + 1; j < items.size(); j++) {
                    Set<String> pair = new HashSet<>();
                    pair.add(items.get(i));
                    pair.add(items.get(j));
                    itemCount.put(pair, itemCount.getOrDefault(pair, 0) + 1);
                }
            }
        }

        // 生成规则
        for (Map.Entry<Set<String>, Integer> entry : itemCount.entrySet()) {
            double support = (double) entry.getValue() / total;
            if (support >= minSupport) {
                List<String> items = new ArrayList<>(entry.getKey());
                String A = items.get(0);
                String B = items.get(1);

                double confAB = calcConfidence(A, B, transactions);
                double confBA = calcConfidence(B, A, transactions);

                if (confAB >= minConfidence) {
                    minedRules.add(new Rule(Set.of(A), Set.of(B), support, confAB));
                }
                if (confBA >= minConfidence) {
                    minedRules.add(new Rule(Set.of(B), Set.of(A), support, confBA));
                }
            }
        }
    }

    /**
     * 给定用户已购买商品,返回推荐列表
     */
    public static List<String> recommendForUser(Set<String> purchased, int topN) {
        // 保存推荐商品和累计置信度
        Map<String, Double> recMap = new HashMap<>();

        for (Rule rule : minedRules) {
            // 如果用户购买的商品与规则左边有交集
            if (!Collections.disjoint(purchased, rule.lhs)) {
                for (String rec : rule.rhs) {
                    if (!purchased.contains(rec)) {
                        // 累加置信度作为评分
                        recMap.put(rec, recMap.getOrDefault(rec, 0.0) + rule.confidence);
                    }
                }
            }
        }

        // 按累计置信度排序并取 topN
        return recMap.entrySet().stream()
                .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue()))
                .limit(topN)
                .map(Map.Entry::getKey)
                .toList();
    }



    private static double calcConfidence(String A, String B, List<Set<String>> transactions) {
        int countA = 0, countAB = 0;
        for (Set<String> t : transactions) {
            if (t.contains(A)) {
                countA++;
                if (t.contains(B)) {
                    countAB++;
                }
            }
        }
        return countA == 0 ? 0 : (double) countAB / countA;
    }
}

# 调用示例

package com.base.AssociationRuleMining;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class TestAprioriRecommend {
    public static void main(String[] args) {
        // 历史交易数据
        List<Set<String>> transactions = new ArrayList<>();
        transactions.add(Set.of("面包", "牛奶", "鸡蛋"));
        transactions.add(Set.of("面包", "可乐", "牙膏"));
        transactions.add(Set.of("牛奶", "鸡蛋", "面包"));
        transactions.add(Set.of("面包", "牛奶", "可乐"));
        transactions.add(Set.of("面包", "鸡蛋", "可乐"));
        transactions.add(Set.of("牛奶", "鸡蛋"));
        transactions.add(Set.of("面包", "牛奶", "牙膏"));
        transactions.add(Set.of("可乐", "牙膏"));
        transactions.add(Set.of("面包", "可乐"));
        transactions.add(Set.of("面包", "牛奶", "鸡蛋", "可乐"));

        // 挖掘规则
        AssociationRuleUtil.mineRules(transactions, 0.2, 0.5);


        // 模拟某用户已购买商品
        Set<String> userCart = Set.of("面包");

        // 获取推荐结果
        List<String> recs = AssociationRuleUtil.recommendForUser(userCart, 5);
        System.out.println("已买: " + userCart);
        System.out.println("推荐: " + recs);
    }
}

输出:

已买: [面包]
推荐: [牛奶, 可乐, 鸡蛋]

# 方向2

# 敏感词过滤

package com.example.util;

import java.util.*;

/**
 * 敏感词过滤工具类(DFA 算法实现)
 */
public class SensitiveWordFilter {

    // 敏感词字典树
    private static Map<String, Object> sensitiveWordMap = new HashMap<>();
    // 默认替换符
    private static final String DEFAULT_REPLACEMENT = "***";

    // ✅ 默认敏感词库(可自行扩展)
    private static final Set<String> DEFAULT_SENSITIVE_WORDS = new HashSet<>(Arrays.asList(
            "傻逼", "垃圾", "暴力", "操你妈", "傻子", "智障",
            "色情", "裸聊", "嫖娼", "赌博", "吸毒", "卖淫",
            "台独", "港独", "法轮功", "恐怖袭击", "炸弹"
    ));

    /**
     * 初始化敏感词库(如果用户不传,就使用内置默认词库)
     */
    public static void init(Set<String> sensitiveWords) {
        if (sensitiveWords == null || sensitiveWords.isEmpty()) {
            sensitiveWords = DEFAULT_SENSITIVE_WORDS;
        }
        sensitiveWordMap = new HashMap<>();
        for (String word : sensitiveWords) {
            Map<String, Object> nowMap = sensitiveWordMap;
            for (int i = 0; i < word.length(); i++) {
                String keyChar = String.valueOf(word.charAt(i));
                Object map = nowMap.get(keyChar);
                if (map != null) {
                    nowMap = (Map<String, Object>) map;
                } else {
                    Map<String, Object> newMap = new HashMap<>();
                    newMap.put("isEnd", "0");
                    nowMap.put(keyChar, newMap);
                    nowMap = newMap;
                }
                if (i == word.length() - 1) {
                    nowMap.put("isEnd", "1");
                }
            }
        }
    }

    /**
     * 过滤文本中的敏感词
     */
    public static String filter(String text) {
        return filter(text, DEFAULT_REPLACEMENT);
    }

    public static String filter(String text, String replacement) {
        StringBuilder result = new StringBuilder();
        int i = 0;
        while (i < text.length()) {
            int length = checkSensitiveWord(text, i);
            if (length > 0) {
                result.append(replacement);
                i += length;
            } else {
                result.append(text.charAt(i));
                i++;
            }
        }
        return result.toString();
    }

    private static int checkSensitiveWord(String text, int beginIndex) {
        Map<String, Object> nowMap = sensitiveWordMap;
        int matchLength = 0;
        for (int i = beginIndex; i < text.length(); i++) {
            String word = String.valueOf(text.charAt(i));
            Object next = nowMap.get(word);
            if (next != null) {
                nowMap = (Map<String, Object>) next;
                matchLength++;
                if ("1".equals(nowMap.get("isEnd"))) {
                    return matchLength;
                }
            } else {
                break;
            }
        }
        return 0;
    }

    // 测试
    public static void main(String[] args) {
        // ❌ 不传,自动加载默认词库
        SensitiveWordFilter.init(null);

        String text = "这地方真垃圾,还遇到个傻逼导游,简直暴力对待游客!";
        System.out.println("原文:" + text);
        System.out.println("过滤后:" + SensitiveWordFilter.filter(text));
    }
}

效果

原文:这地方真垃圾,还遇到个傻逼导游,简直暴力对待游客!
过滤后:这地方真***,还遇到个***导游,简直***对待游客!
Last Updated: 9/13/2025, 3:32:02 PM