Bladeren bron

feat: 完成推荐系统

181250114 4 jaren geleden
bovenliggende
commit
4e1c9f3922

+ 179 - 0
src/main/java/com/seecoder/dataanalysis/analysis/impl/RecommandServiceImpl.java

@@ -0,0 +1,179 @@
+package com.seecoder.dataanalysis.analysis.impl;
+
+import cn.seecoder.bok.common.vo.QuestionVO;
+import com.seecoder.dataanalysis.analysis.EvalDataAnalysisService;
+import com.seecoder.dataanalysis.analysis.RecommandService;
+import com.seecoder.dataanalysis.data.dao.competency.CompetencyTreeDao;
+import com.seecoder.dataanalysis.data.entity.competency.CompetencyTree;
+import com.seecoder.dataanalysis.data.entity.competency.KSDScore;
+import com.seecoder.dataanalysis.logic.question.QuestionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+@Service
+public class RecommandServiceImpl implements RecommandService {
+    //默认推荐的题目数量
+    public static final int QUESTION_NUM = 30;
+
+    @Autowired
+    private EvalDataAnalysisService evalDataAnalysisService;
+    @Autowired
+    private QuestionService questionService;
+    @Autowired
+    private CompetencyTreeDao competencyTreeDao;
+
+    @Override
+    public List<QuestionVO> getRecommandById(int userId){
+        Random random = new Random();
+        //获取学生的做题情况
+        Map<String,Double> errorQuestions = evalDataAnalysisService.getStudentErrorQuestions(userId);
+        //学生的错题列表,只存储0分的题目
+        //本次只考虑SR类型的错题,其他都不添加
+        //TODO 针对多个知识点的情况
+        String target = "SR";
+        List<String> errors = new ArrayList<>();
+        for(Map.Entry<String,Double> entry : errorQuestions.entrySet()){
+            String questionId = entry.getKey();
+            if(entry.getValue().equals(0.0)){
+                List<String> ks = questionService.getKnowledgesById(questionId);
+                for(String k : ks){
+                    if(k.contains(target)){
+                        errors.add(questionId);
+                        break;
+                    }
+                }
+            }
+        }
+        CompetencyTree competencyTree;
+        //获取学生的能力树
+        Optional<CompetencyTree> result = competencyTreeDao.findById(userId);
+        if(!result.isPresent()){
+            competencyTree = new CompetencyTree(userId);
+        }else {
+            competencyTree = competencyTreeDao.findById(userId).get();
+        }
+        //先推荐没做过的(没做过的类型再到同类型没做过的题目),再推荐分支中分数低的
+        //本次题目都是SR类的,所以只查看SR分支
+        List<String> tarKnows = new ArrayList<>();
+        tarKnows.add("SR");
+        //每种类型推荐多少题目,由知识点数目而定
+        int eachNum = QUESTION_NUM / tarKnows.size();
+        //知识点和对应分数
+        Map<String,Double> knowlegdeAndScores = new HashMap<>();
+        for(String tarKnow :tarKnows){
+            String[] tars = tarKnow.split("\\.");
+            List<KSDScore> ksdScoreList = competencyTree.getSubCompetencyTree();
+            for(KSDScore ksdScore : ksdScoreList){
+                //先匹配最外层
+                if(ksdScore.getCompetency().equals(tars[0])){
+                    if(!knowlegdeAndScores.containsKey(tars[0])){
+                        knowlegdeAndScores.put(tars[0],ksdScore.getTotalScore());
+                    }
+                    //再匹配第二层
+                    //TODO 完善匹配机制
+                    if(tars.length>1){}
+                    //将分支下所有子分支都添加
+                    if(ksdScore.getChildren()!=null&&ksdScore.getChildren().size()>0){
+                        for(KSDScore subKSD1 : ksdScore.getChildren()){
+                            if(!knowlegdeAndScores.containsKey(subKSD1.getCompetency())){
+                                knowlegdeAndScores.put(subKSD1.getCompetency(),subKSD1.getTotalScore());
+                            }
+                            if(subKSD1.getChildren()!=null&&subKSD1.getChildren().size()>0){
+                                for(KSDScore subKSD2 : subKSD1.getChildren()){
+                                    if(!knowlegdeAndScores.containsKey(subKSD2.getCompetency())){
+                                        knowlegdeAndScores.put(subKSD2.getCompetency(),subKSD2.getTotalScore());
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        List<Map.Entry<String,Double>> list = new ArrayList<>(knowlegdeAndScores.entrySet());
+        Collections.sort(list,(a,b)->(int)((double)a.getValue()-(double)b.getValue()));
+        //根据得分升序排列,得到学生相对薄弱的部分
+        Map<String,Double> afterSort = new LinkedHashMap<>();
+        for(Map.Entry<String,Double> o : list){
+            afterSort.put(o.getKey(),o.getValue());
+        }
+
+
+//        for(Map.Entry<String,Double> entry : afterSort.entrySet()){
+//            System.out.println(entry.getKey()+": "+entry.getValue());
+//        }
+
+        //还需要添加的题目数目,姑且设计为5错题+15低分+10大类中未做过的,剩下不足的由未做过的题目补充
+        int left = QUESTION_NUM;
+        //最终生成试卷的所有题号的集合
+        List<String> total = new ArrayList<>();
+
+        //随机选择5道错题
+        for(int i=0;errors.size()>0&&i<5;i++){
+            int r = random.nextInt(errors.size());
+            total.add(errors.get(r));
+            left--;
+            errors.remove(errors.get(r));
+        }
+
+        //随机选择10道和低分类似类似的题目
+        List<String> similar = new ArrayList<>();
+        int index = 0;
+        for(Map.Entry<String,Double> entry : afterSort.entrySet()){
+            if(index>=5){
+                break;
+            }
+            String tar = entry.getKey();
+            if(tar.split("\\.").length>1){
+                List<String> ks = new ArrayList<>();
+                ks.add(tar);
+                List<QuestionVO> questions = questionService.searchQuestions(ks);
+                for(QuestionVO questionVO : questions){
+                    String questionId = questionVO.getId();
+                    if(total.contains(questionId)==false && similar.contains(questionId)==false){
+                        similar.add(questionId);
+                    }
+                }
+            }else {
+                break;
+            }
+            index++;
+        }
+        //随机选择15道相似题目
+        for(int i=0;similar.size()>0&&i<15;i++){
+            int r = random.nextInt(similar.size());
+            total.add(similar.get(r));
+            left--;
+            similar.remove(similar.get(r));
+        }
+
+
+        //从大类中随机选择剩下的所有题目
+        List<QuestionVO> questions = questionService.searchQuestions(tarKnows);
+        List<String> undos = new ArrayList<>();
+        for(QuestionVO questionVO : questions){
+            String questionId = questionVO.getId();
+            if(total.contains(questionId)==false){
+                undos.add(questionId);
+            }
+        }
+        while(undos.size()>0 && left > 0){
+            int r = random.nextInt(undos.size());
+            total.add(undos.get(r));
+            left--;
+            undos.remove(undos.get(r));
+        }
+
+        for(String s : total){
+            System.out.println(s);
+        }
+        System.out.println(total.size());
+
+        List<QuestionVO> res = new ArrayList<>();
+
+        return res;
+    }
+}

+ 3 - 1
src/test/java/com/seecoder/dataanalysis/eval/impl/EvalDataAnalysisServiceImplTest.java

@@ -155,8 +155,10 @@ public class EvalDataAnalysisServiceImplTest {
         Question question1 = questionService.getQuestionByIdForAnalysis("43604f6f-f829-4aaf-ab97-47b99c20ede0");
         Question question2 = questionService.getQuestionByIdForAnalysis("b39005ff-23c0-43a2-8174-f370f774eea8");
         Map<String, Double> quesScore1 = new HashMap<>();
-        quesScore1.put(question1.getQuestionId(),80.0);
+//        quesScore1.put(question1.getQuestionId(),80.0);
         quesScore1.put(question2.getQuestionId(),30.0);
+        quesScore1.put(question1.getQuestionId(),0.0);
+//        quesScore1.put(question2.getQuestionId(),0.0);
 
         EvalExamRecord evalExamRecord1 = new EvalExamRecord()
                 .setRecordId(1)

+ 18 - 0
src/test/java/com/seecoder/dataanalysis/eval/impl/RecommandServiceImplTest.java

@@ -0,0 +1,18 @@
+package com.seecoder.dataanalysis.eval.impl;
+
+import com.seecoder.dataanalysis.analysis.RecommandService;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+public class RecommandServiceImplTest {
+
+    @Autowired
+    private RecommandService recommandService;
+
+    @Test
+    void testRecommand(){
+        recommandService.getRecommandById(2);
+    }
+}