要求是这样的:
给定一段中文答案, 和标准的中文文字的答案做比对,最终得到完整的分数.
因为用户的答案中涉及到中文, 所以就必须使用中文分词器, 最终选定的是HanLP ,非常的方便, 资源链接如下:
https://github.com/hankcs/HanLP/tree/1.x 可以自行学习使用.
首先项目中引入HanLP的maven坐标:
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>hanlp</artifactId>
<version>portable-1.7.7</version>
</dependency>
我们使用HanLP分词之后, 使用 向量余弦算法计算两个文本的相似性 ,下面就是写了一个分词的工具类:
package com.taohan.online.exam.util;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* 分词工具类
*/
public class HanlpUtil {
/**
* 判断标点符号正则表达式, 去掉标点符号
*/
private static Pattern PATTERN = Pattern.compile("\\pP");
public static void main(String[] args) {
System.out.println(cosine("天气预报说,明天会下雨,你明天早上去上班的时候记得带上伞。"
,"你明天早上去上班的时候记得带上伞,天气预报说的可能会下雨。"));
}
/**
* 0.5543
*
* 词向量余弦算法计算文本相似度
*
* @return
*/
public static double cosine(String userAnswer, String standAnswer) {
List<String> originWord = getWords(standAnswer);
List<String> targetWord = getWords(userAnswer);
Map<String, int[]> wordDict = new HashMap<>();
for (String word : originWord) {
if (!wordDict.containsKey(word)) {
int[] value = new int[2];
value[0] = 1;
wordDict.put(word, value);
} else {
wordDict.get(word)[0] += 1;
}
}
for (String word : targetWord) {
if (!wordDict.containsKey(word)) {
int[] value = new int[2];
value[1] = 1;
wordDict.put(word, value);
} else {
wordDict.get(word)[1] += 1;
}
}
int dictNum = 0, originNum = 0, targetNum = 0;
for (Map.Entry<String, int[]> entry : wordDict.entrySet()) {
int origin = entry.getValue()[0];
int des = entry.getValue()[1];
originNum += origin * origin;
targetNum += des * des;
dictNum += origin * des;
}
double sqrt = Math.sqrt(originNum * targetNum);
BigDecimal scale = new BigDecimal(dictNum).divide(new BigDecimal(sqrt), 4, BigDecimal.ROUND_HALF_UP)
.setScale(4, BigDecimal.ROUND_HALF_UP);
return scale.doubleValue();
}
/**
* 分词
*
* @param str
* @return
*/
public static List<String> getWords(String str) {
List<String> list = new ArrayList<>();
if (StringUtils.isBlank(str)) {
return list;
}
List<Term> segment = HanLP.segment(str);
for (Term term : segment) {
//https://github.com/hankcs/HanLP/tree/1.x
if (!PATTERN.matcher(term.word).matches()) {
list.add(term.word);
}
}
return list;
}
}
中文分词就解决了, 还有向量余弦算法计算文本相似度 的问题也解决了,
下面是一个用户答案的评分的工具类, 以及其使用的示例,
用户输入用户的答案, 用户答案和标准答案的多个关键字进行比较, 得到关键词双向匹配得分, 再和标准答案进行两个答案的文本比较得到相似性得分, , 两个得分计算平均值, 得到最后的得分.
package com.taohan.online.exam.util;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CompareUtil {
public static void main(String[] args) {
String userAnswer = "多态是在程序还没运行时不知道调用哪个函数,在程序执行中,根据情况动态确定,操作很灵活";
String standAnswer = "多态是在程序还没运行时不知道调用函数,在程序执行,根据情况动态确定";
List<String> keywords = Stream.of("多态", "程序", "函数名", "参数", "运行情况").collect(Collectors.toList());
double score = getUserAnswerScore(userAnswer, standAnswer, keywords);
System.out.println(score);
}
/**
* 获取最后的得分
*
* @param userAnswer 用户答案
* @param standAnswer 标准答案
* @param keywords 关键词
* @return
*/
public static double getUserAnswerScore(String userAnswer, String standAnswer, List<String> keywords) {
if (StringUtils.equals(userAnswer, standAnswer)) {
return 1.0;
}
if (StringUtils.isBlank(userAnswer)) {
return 0.0;
}
double textSameScore = HanlpUtil.cosine(userAnswer, standAnswer);
double score = 0;
if (keywords != null && keywords.size() > 0) {
score = getCompareScore(keywords, userAnswer);
} else {
score = textSameScore;
}
//计算两者的平均数
BigDecimal scale = new BigDecimal(textSameScore).add(new BigDecimal(score))
.divide(new BigDecimal(2), 4, BigDecimal.ROUND_HALF_UP)
.setScale(4, BigDecimal.ROUND_HALF_UP);
return scale.doubleValue();
}
/**
* 0.4254
* 获得关键词双向匹配得分
*
* @param keywords
* @param userAnswer
* @return
*/
public static double getCompareScore(List<String> keywords, String userAnswer) {
List<BigDecimal> list = new ArrayList<>();
for (String keyword : keywords) {
BigDecimal precent = getPrecent(userAnswer, keyword);
list.add(precent);
}
BigDecimal sum = new BigDecimal("0");
for (BigDecimal bigDecimal : list) {
sum = sum.add(bigDecimal);
}
BigDecimal scale = sum.divide(new BigDecimal(list.size()), 4, BigDecimal.ROUND_HALF_UP)
.setScale(4, BigDecimal.ROUND_HALF_UP);
return scale.doubleValue();
}
/**
* 比较两个字符串的相似度
*
* @param answer
* @param oneWord
* @return
*/
public static BigDecimal getPrecent(String answer, String oneWord) {
int index = 0;
if (oneWord == null || oneWord.length() < 1) {
return new BigDecimal(0);
}
if (answer.indexOf(oneWord) != -1) {
index = oneWord.length();
} else {
int length = oneWord.length();
for (int i = 0; i < length; i++) {
String a = oneWord.substring(i, length - 1);
String b = oneWord.substring(i + 1, length);
if (answer.indexOf(a) != -1) {
index = a.length();
break;
}
if (answer.indexOf(b) != -1) {
index = b.length();
break;
}
}
}
if (index == 0) {
for (int i = 0; i < oneWord.length(); i++) {
if (answer.contains(String.valueOf(oneWord.charAt(i)))) {
index = 1;
}
}
}
BigDecimal decimal = new BigDecimal(index)
.divide(new BigDecimal(oneWord.length()), 2, BigDecimal.ROUND_HALF_UP)
.setScale(4, BigDecimal.ROUND_HALF_UP);
return decimal;
}
}
其实就是两个工具类的事情, 很简单.
这个是别人花钱雇我写的, 为了保证原创性, 暂时不会公开出来(大概4个月后会公开). 仅供大家参考