前面一篇文章介绍了SimHash算法的基本原理和主要的应用场景。由于SimHash算法是一种在大规模数据下解决文本相似与否的算法,这篇文章主要介绍一下自己基于Scala实现的一种精简版SimHash算法。
算法思路:
对于一个文本内容,先使用正则表达式对无意义字符进行过滤,再以滑动切片的方法将文本切成一个字符串数组,其中滑动切片窗口的阈值视情况而定。然后对该字符串数组,进行权重派分。代码中给出了前缀、后缀和平摊的方案进行派分,权重赋值为1和3(详细请看代码)。然后通过哈希函数对每个分片字符串进行计算,并生成该分片的权重数组。接下来把所有分片权重数组累加,再将64位权重数组大于0的位置1,其余置0,生成最终的SimHash值。
哈希值计算,由于生成64位哈希值,为了方便后面操作,使用位数组。
def byte2Bools(b: Byte) =
(0 to 7).foldLeft(ArrayBuffer[Boolean]())((bs, i) => bs += isBitSet(b, i)).reverse
def isBitSet(byte: Byte, bit: Int) =
((byte >> bit) & 1) == 1
def hashFuc(st:String):ArrayBuffer[Boolean] ={
var str
=org.apache.commons.codec.digest.DigestUtils.md5Hex(st)
var hashV = new java.math.BigInteger(str,16)
var hashArrByte =hashV.toByteArray()
var hashArrBoolean = ArrayBuffer[Boolean]()
var len = hashArrByte.length
for(i<- 0 to len-1){
var temArr = byte2Bools(hashArrByte(i))
hashArrBoolean++=temArr
}
hashArrBoolean
}
正则过滤函数,用来给文本去掉意义不大的字符。
def tokenlize(str:String):ArrayBuffer[String]={
var reg = """[\w\u4e00-\u9fcc]+""".r
var s = str.toLowerCase()
var reStr = (reg findAllIn s).mkString("")
var ans = slide(reStr)
ans
}
滑动切片函数,用来将文本切片。
def slide(str:String): ArrayBuffer[String] ={
val slideWidth = 4
var len = str.length
var res = ArrayBuffer[String]()
var a = 0
if(len>=4){
for(a <- 0 to (len - slideWidth)){
var s = str.slice(a,a+slideWidth)
res+=s
}
}else{
res+= str
}
res
}
对切片数组赋予权重
def weightCul(Type:String,length:Int):ArrayBuffer[Int] = {
var weights = ArrayBuffer[Int]()
val len = length/2 + 1
val a = 1
if(weightType=="pre"){
for(a<- 1 to len){
weights+= 3
}
for(a<- 1 to length-len){
weights+= 1
}
}else{
if(weightType=="suf"){
for (a<- 1 to length-len){
weights+= 1
}
for (a<- 1 to len){
weights+= 3
}
}else{
for (a<- 1 to length){
weights+= 1
}
}
}
weights
}
对文本进行SimHash值计算
def buildByArr(fea:ArrayBuffer[(String, Int)]):String = {
var v = ArrayBuffer[Int]()
val f = 64
for (i<- 1 to f){
v+= 0
}
var len=fea.length
for (j<- 0 to len-1){
var feature = fea(j)
var hashArr = hashFuc(feature._1)
var weight = feature._2
var hLen = hashArr.length
for(a<-0 to f-1){
if ((hashArr(hLen-1-a))){
v(a)+= weight
}else{
v(a)-= weight
}
}
}
var list = new java.math.BigInteger("0")
for(k<- 0 to f-1){
if(v(k)>0){
list = list.setBit(k)
}
}
val simHash = list.toString
SimHash
}
至此,就可以计算出在不同权重方案下SimHash的值。
这种计算方法比较适合较短文本的计算,长文本采用分词的方式较为有效。