我是靠谱客的博主 愉快山水,最近开发中收集的这篇文章主要介绍java字符串比较,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

应该场景:
有一批酒店的产品名字,名字不规则,有中文有英文也会有特殊符号,现需要按这个产品的名称将其对应到相应的房型上。这时就需要按字符进行比较。去匹配相似度最高的房型名称之上。经过对数据的分析,最后有中文的名称采用分词的方法进行相似对比,英文的文本之间的相似度计算用的是余弦距离,先哈希过。下面是计算两个List的余弦距离。

英文字符进行相似度比较

package com.e100.hotelcore.stringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class EnStringCompare {
public static double getSimilarity(ArrayList<String> doc1, ArrayList<String> doc2) {
if (doc1 != null && doc1.size() > 0 && doc2 != null && doc2.size() > 0) {
Map<Long, int[]> AlgorithmMap = new HashMap<Long, int[]>();
for (int i = 0; i < doc1.size(); i++) {
String d1 = doc1.get(i);
long sIndex = hashId(d1);
int[] fq = AlgorithmMap.get(sIndex);
if (fq != null) {
fq[0]++;
} else {
fq = new int[2];
fq[0] = 1;
fq[1] = 0;
AlgorithmMap.put(sIndex, fq);
}
}
for (int i = 0; i < doc2.size(); i++) {
String d2 = doc2.get(i);
long sIndex = hashId(d2);
int[] fq = AlgorithmMap.get(sIndex);
if (fq != null) {
fq[1]++;
} else {
fq = new int[2];
fq[0] = 0;
fq[1] = 1;
AlgorithmMap.put(sIndex, fq);
}
}
Iterator<Long> iterator = AlgorithmMap.keySet().iterator();
double sqdoc1 = 0;
double sqdoc2 = 0;
double denominator = 0;
while (iterator.hasNext()) {
int[] c = AlgorithmMap.get(iterator.next());
denominator += c[0] * c[1];
sqdoc1 += c[0] * c[0];
sqdoc2 += c[1] * c[1];
}
return denominator / Math.sqrt(sqdoc1 * sqdoc2);
} else {
return 0;
}
}
public static long hashId(String s) {
long seed = 131; // 31 131 1313 13131 131313 etc.. BKDRHash

long hash = 0;
for (int i = 0; i < s.length(); i++) {
hash = (hash * seed) + s.charAt(i);
}
return hash;
}
public static void main(String[] args) {
ArrayList<String> t1 = new ArrayList<String>();
ArrayList<String> t2 = new ArrayList<String>();
ArrayList<String> t3 = new ArrayList<String>();
t1.add("double");
t1.add("or");
t1.add("twin");
t1.add("superior");
t2.add("superior");
t2.add("twin");
t2.add("double");
t3.add("superior");
t3.add("suite");
t3.add("standard");
t3.add("zone");
System.out.println(getSimilarity(t1, t2));
System.out.println(getSimilarity(t1, t3));
}
}

余弦相似度计算字符串相似率

比较中文比较准确,英文区分大少写,按空格分词,空格的多少也会影响到相似度,所以上面的方法比较英文比较准确。

1、pom.xml

展示一些主要的jar包

	<!--结合操作工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<!--bean实体注解工具包-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--汉语言包,主要用于分词-->
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>hanlp</artifactId>
<version>portable-1.6.5</version>
</dependency>

2、main方法

 public static void main(String[] args) {
String content1 = "run of the house";
String content2 = "1 bed presidential suite space zone";
String content3 = "Premier";
String content4 = "Superior";
double score = CosineSimilarity.getSimilarity(content1, content2);
System.out.println("相似度:" + score);
score = CosineSimilarity.getSimilarity(content1, content3);
System.out.println("相似度:" + score);
score = CosineSimilarity.getSimilarity(content1, content4);
System.out.println("相似度:" + score);
}

3、Tokenizer(分词工具类)

	package com.e100.hotelcore.stringUtils;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import java.util.List;
import java.util.stream.Collectors;
/**
* 中文分词工具类*/
public class Tokenizer {
/**
* 分词*/
public static List<Word> segment(String sentence) {
//1、 采用HanLP中文自然语言处理中标准分词进行分词
List<Term> termList = HanLP.segment(sentence);
//上面控制台打印信息就是这里输出的
System.out.println(termList.toString());
//2、重新封装到Word对象中(term.word代表分词后的词语,term.nature代表改词的词性)
return termList.stream().map(term -> new Word(term.word, term.nature.toString())).collect(Collectors.toList());
}
}

4、Word(封装分词结果)

package com.e100.hotelcore.stringUtils;
import lombok.Data;
import java.util.Objects;
/**
* 封装分词结果*/
@Data
public class Word implements Comparable {
// 词名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 词性
private String pos;
// 权重,用于词向量分析
private Float weight;
public Word(String name, String pos) {
this.name = name;
this.pos = pos;
}
@Override
public int hashCode() {
return Objects.hashCode(this.name);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Word other = (Word) obj;
return Objects.equals(this.name, other.name);
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
if (name != null) {
str.append(name);
}
if (pos != null) {
str.append("/").append(pos);
}
return str.toString();
}
@Override
public int compareTo(Object o) {
if (this == o) {
return 0;
}
if (this.name == null) {
return -1;
}
if (o == null) {
return 1;
}
if (!(o instanceof Word)) {
return 1;
}
String t = ((Word) o).getName();
if (t == null) {
return 1;
}
return this.name.compareTo(t);
}
public Float getWeight() {
return weight;
}
public void setWeight(Float weight) {
this.weight = weight;
}
}

5、CosineSimilarity

package com.e100.hotelcore.stringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 判定方式:余弦相似度,通过计算两个向量的夹角余弦值来评估他们的相似度 余弦夹角原理: 向量a=(x1,y1),向量b=(x2,y2)
* similarity=a.b/|a|*|b| a.b=x1x2+y1y2
* |a|=根号[(x1)^2+(y1)^2],|b|=根号[(x2)^2+(y2)^2]
*/
public class CosineSimilarity {
protected static final Logger LOGGER = LoggerFactory.getLogger(CosineSimilarity.class);
/**
* 1、计算两个字符串的相似度
*/
public static double getSimilarity(String text1, String text2) {
// 如果wei空,或者字符长度为0,则代表完全相同
if (StringUtils.isBlank(text1) && StringUtils.isBlank(text2)) {
return 1.0;
}
// 如果一个为0或者空,一个不为,那说明完全不相似
if (StringUtils.isBlank(text1) || StringUtils.isBlank(text2)) {
return 0.0;
}
// 这个代表如果两个字符串相等那当然返回1了(这个我为了让它也分词计算一下,所以注释掉了)
//
if (text1.equalsIgnoreCase(text2)) {
//
return 1.0;
//
}
// 第一步:进行分词
List<Word> words1 = Tokenizer.segment(text1);
List<Word> words2 = Tokenizer.segment(text2);
return getSimilarity(words1, words2);
}
/**
* 2、对于计算出的相似度保留小数点后六位
*/
public static double getSimilarity(List<Word> words1, List<Word> words2) {
double score = getSimilarityImpl(words1, words2);
// (int) (score * 1000000 + 0.5)其实代表保留小数点后六位
// ,因为1034234.213强制转换不就是1034234。对于强制转换添加0.5就等于四舍五入
score = (int) (score * 1000000 + 0.5) / (double) 1000000;
return score;
}
/**
* 文本相似度计算 判定方式:余弦相似度,通过计算两个向量的夹角余弦值来评估他们的相似度 余弦夹角原理: 向量a=(x1,y1),向量b=(x2,y2) similarity=a.b/|a|*|b| a.b=x1x2+y1y2
* |a|=根号[(x1)^2+(y1)^2],|b|=根号[(x2)^2+(y2)^2]
*/
public static double getSimilarityImpl(List<Word> words1, List<Word> words2) {
// 向每一个Word对象的属性都注入weight(权重)属性值
taggingWeightByFrequency(words1, words2);
//第二步:计算词频
//通过上一步让每个Word对象都有权重值,那么在封装到map中(key是词,value是该词出现的次数(即权重))
Map<String, Float> weightMap1 = getFastSearchMap(words1);
Map<String, Float> weightMap2 = getFastSearchMap(words2);
//将所有词都装入set容器中
Set<Word> words = new HashSet<>();
words.addAll(words1);
words.addAll(words2);
AtomicFloat ab = new AtomicFloat();// a.b
AtomicFloat aa = new AtomicFloat();// |a|的平方
AtomicFloat bb = new AtomicFloat();// |b|的平方
// 第三步:写出词频向量,后进行计算
words.parallelStream().forEach(word -> {
//看同一词在a、b两个集合出现的此次
Float x1 = weightMap1.get(word.getName());
Float x2 = weightMap2.get(word.getName());
if (x1 != null && x2 != null) {
//x1x2
float oneOfTheDimension = x1 * x2;
//+
ab.addAndGet(oneOfTheDimension);
}
if (x1 != null) {
//(x1)^2
float oneOfTheDimension = x1 * x1;
//+
aa.addAndGet(oneOfTheDimension);
}
if (x2 != null) {
//(x2)^2
float oneOfTheDimension = x2 * x2;
//+
bb.addAndGet(oneOfTheDimension);
}
});
//|a| 对aa开方
double aaa = Math.sqrt(aa.doubleValue());
//|b| 对bb开方
double bbb = Math.sqrt(bb.doubleValue());
//使用BigDecimal保证精确计算浮点数
//double aabb = aaa * bbb;
BigDecimal aabb = BigDecimal.valueOf(aaa).multiply(BigDecimal.valueOf(bbb));
//similarity=a.b/|a|*|b|
//divide参数说明:aabb被除数,9表示小数点后保留9位,最后一个表示用标准的四舍五入法
double cos = BigDecimal.valueOf(ab.get()).divide(aabb, 9, BigDecimal.ROUND_HALF_UP).doubleValue();
return cos;
}
/**
* 向每一个Word对象的属性都注入weight(权重)属性值
*/
protected static void taggingWeightByFrequency(List<Word> words1, List<Word> words2) {
if (words1.get(0).getWeight() != null && words2.get(0).getWeight() != null) {
return;
}
//词频统计(key是词,value是该词在这段句子中出现的次数)
Map<String, AtomicInteger> frequency1 = getFrequency(words1);
Map<String, AtomicInteger> frequency2 = getFrequency(words2);
//如果是DEBUG模式输出词频统计信息
//
if (LOGGER.isDebugEnabled()) {
//
LOGGER.debug("词频统计1:n{}", getWordsFrequencyString(frequency1));
//
LOGGER.debug("词频统计2:n{}", getWordsFrequencyString(frequency2));
//
}
// 标注权重(该词出现的次数)
words1.parallelStream().forEach(word -> word.setWeight(frequency1.get(word.getName()).floatValue()));
words2.parallelStream().forEach(word -> word.setWeight(frequency2.get(word.getName()).floatValue()));
}
/**
* 统计词频
* @return 词频统计图
*/
private static Map<String, AtomicInteger> getFrequency(List<Word> words) {
Map<String, AtomicInteger> freq = new HashMap<>();
//这步很帅哦
words.forEach(i -> freq.computeIfAbsent(i.getName(), k -> new AtomicInteger()).incrementAndGet());
return freq;
}
/**
* 输出:词频统计信息
*/
private static String getWordsFrequencyString(Map<String, AtomicInteger> frequency) {
StringBuilder str = new StringBuilder();
if (frequency != null && !frequency.isEmpty()) {
AtomicInteger integer = new AtomicInteger();
frequency.entrySet().stream().sorted((a, b) -> b.getValue().get() - a.getValue().get()).forEach(
i -> str.append("t").append(integer.incrementAndGet()).append("、").append(i.getKey()).append("=")
.append(i.getValue()).append("n"));
}
str.setLength(str.length() - 1);
return str.toString();
}
/**
* 构造权重快速搜索容器
*/
protected static Map<String, Float> getFastSearchMap(List<Word> words) {
if (CollectionUtils.isEmpty(words)) {
return Collections.emptyMap();
}
Map<String, Float> weightMap = new ConcurrentHashMap<>(words.size());
words.parallelStream().forEach(i -> {
if (i.getWeight() != null) {
weightMap.put(i.getName(), i.getWeight());
} else {
LOGGER.error("no word weight info:" + i.getName());
}
});
return weightMap;
}
}

6、AtomicFloat原子类

package com.e100.hotelcore.stringUtils;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicFloat extends Number{
private AtomicInteger bits;
public AtomicFloat() {
this(0f);
}
public AtomicFloat(float initialValue) {
bits = new AtomicInteger(Float.floatToIntBits(initialValue));
}
//叠加
public final float addAndGet(float delta) {
float expect;
float update;
do {
expect = get();
update = expect + delta;
} while (!this.compareAndSet(expect, update));
return update;
}
public final float getAndAdd(float delta) {
float expect;
float update;
do {
expect = get();
update = expect + delta;
} while (!this.compareAndSet(expect, update));
return expect;
}
public final float getAndDecrement() {
return getAndAdd(-1);
}
public final float decrementAndGet() {
return addAndGet(-1);
}
public final float getAndIncrement() {
return getAndAdd(1);
}
public final float incrementAndGet() {
return addAndGet(1);
}
public final float getAndSet(float newValue) {
float expect;
do {
expect = get();
} while (!this.compareAndSet(expect, newValue));
return expect;
}
public final boolean compareAndSet(float expect, float update) {
return bits.compareAndSet(Float.floatToIntBits(expect), Float.floatToIntBits(update));
}
public final void set(float newValue) {
bits.set(Float.floatToIntBits(newValue));
}
public final float get() {
return Float.intBitsToFloat(bits.get());
}
@Override
public float floatValue() {
return get();
}
@Override
public double doubleValue() {
return (double) floatValue();
}
@Override
public int intValue() {
return (int) get();
}
@Override
public long longValue() {
return (long) get();
}
@Override
public String toString() {
return Float.toString(get());
}
}

7、总结

把大致思路再捋一下:

(1)先分词: 分词当然要按一定规则,不然随便分那也没有意义,那这里通过采用HanLP中文自然语言处理中标准分词进行分词。

(2)统计词频: 就统计上面词出现的次数。

(3)通过每一个词出现的次数,变成一个向量,通过向量公式计算相似率。

最后

以上就是愉快山水为你收集整理的java字符串比较的全部内容,希望文章能够帮你解决java字符串比较所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(47)

评论列表共有 0 条评论

立即
投稿
返回
顶部