在 Node.js 和 MongoDB 环境中使用 MapReduce 进行搜索排名

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

在 Node.js 和 MongoDB 环境中使用 MapReduce 进行 Tf-Idf 排名

在开发文档搜索应用程序时,其中一个挑战是根据您搜索的术语的出现对结果进行排序。 Tf-Idf 是一种数值统计,可帮助您权衡搜索结果。

Tf 代表词频。

Idf 代表反向文档频率。

为了掌握,我们将在 javascript 中开发一个 tf-idf 示例,作为节点模块。


 function TfIdf() {
}

TfIdf.prototype.weights = function(documents,term) {

var results = []

var idf = this.idf(documents,term)

for(var i=0;i<documents.length;i++) {

    var tf = this.tf(documents[i],term)
    var tfidf = tf*idf
    var result = {weight:tfidf,doc:documents[i]}    

    results.push(result)
}

return results

}

TfIdf.prototype.tf = function(words,term) {

var result = 0

for(var i=0;i<words.length;i++) {

    var word = words[i]

    if(word.indexOf(term)!=-1) {
        result = result+1
    }    
}

return result/words.length

}

TfIdf.prototype.idf = function(documents,term) {

var occurence = 0

for(var j=0;j<documents.length;j++) {

    var doc = documents[j]

    if(this.__wordInsideDoc(doc,term)){
        occurence = occurence+1
    }                  
}

if(occurence==0) {
    return undefined    
}

return Math.log(documents.length/occurence)

}

TfIdf.prototype.__wordInsideDoc = function(doc,term) {

for(var i=0;i<doc.length;i++) {

    var word = doc[i]

    if(word.indexOf(term)!=-1) {
        return true
    }
}    

return false

}

module.exports = TfIdf

函数 weights 将接受要搜索的文档和术语。

下面是一个例子


 function TfIdf() {
}

TfIdf.prototype.weights = function(documents,term) {

var results = []

var idf = this.idf(documents,term)

for(var i=0;i<documents.length;i++) {

    var tf = this.tf(documents[i],term)
    var tfidf = tf*idf
    var result = {weight:tfidf,doc:documents[i]}    

    results.push(result)
}

return results

}

TfIdf.prototype.tf = function(words,term) {

var result = 0

for(var i=0;i<words.length;i++) {

    var word = words[i]

    if(word.indexOf(term)!=-1) {
        result = result+1
    }    
}

return result/words.length

}

TfIdf.prototype.idf = function(documents,term) {

var occurence = 0

for(var j=0;j<documents.length;j++) {

    var doc = documents[j]

    if(this.__wordInsideDoc(doc,term)){
        occurence = occurence+1
    }                  
}

if(occurence==0) {
    return undefined    
}

return Math.log(documents.length/occurence)

}

TfIdf.prototype.__wordInsideDoc = function(doc,term) {

for(var i=0;i<doc.length;i++) {

    var word = doc[i]

    if(word.indexOf(term)!=-1) {
        return true
    }
}    

return false

}

module.exports = TfIdf

结果是:


 function TfIdf() {
}

TfIdf.prototype.weights = function(documents,term) {

var results = []

var idf = this.idf(documents,term)

for(var i=0;i<documents.length;i++) {

    var tf = this.tf(documents[i],term)
    var tfidf = tf*idf
    var result = {weight:tfidf,doc:documents[i]}    

    results.push(result)
}

return results

}

TfIdf.prototype.tf = function(words,term) {

var result = 0

for(var i=0;i<words.length;i++) {

    var word = words[i]

    if(word.indexOf(term)!=-1) {
        result = result+1
    }    
}

return result/words.length

}

TfIdf.prototype.idf = function(documents,term) {

var occurence = 0

for(var j=0;j<documents.length;j++) {

    var doc = documents[j]

    if(this.__wordInsideDoc(doc,term)){
        occurence = occurence+1
    }                  
}

if(occurence==0) {
    return undefined    
}

return Math.log(documents.length/occurence)

}

TfIdf.prototype.__wordInsideDoc = function(doc,term) {

for(var i=0;i<doc.length;i++) {

    var word = doc[i]

    if(word.indexOf(term)!=-1) {
        return true
    }
}    

return false

}

module.exports = TfIdf

现在我们将继续使用 MapReduce 方法。

我将使用 node.js。

首先,我们将安装 MongoDB 驱动程序:


 function TfIdf() {
}

TfIdf.prototype.weights = function(documents,term) {

var results = []

var idf = this.idf(documents,term)

for(var i=0;i<documents.length;i++) {

    var tf = this.tf(documents[i],term)
    var tfidf = tf*idf
    var result = {weight:tfidf,doc:documents[i]}    

    results.push(result)
}

return results

}

TfIdf.prototype.tf = function(words,term) {

var result = 0

for(var i=0;i<words.length;i++) {

    var word = words[i]

    if(word.indexOf(term)!=-1) {
        result = result+1
    }    
}

return result/words.length

}

TfIdf.prototype.idf = function(documents,term) {

var occurence = 0

for(var j=0;j<documents.length;j++) {

    var doc = documents[j]

    if(this.__wordInsideDoc(doc,term)){
        occurence = occurence+1
    }                  
}

if(occurence==0) {
    return undefined    
}

return Math.log(documents.length/occurence)

}

TfIdf.prototype.__wordInsideDoc = function(doc,term) {

for(var i=0;i<doc.length;i++) {

    var word = doc[i]

    if(word.indexOf(term)!=-1) {
        return true
    }
}    

return false

}

module.exports = TfIdf

然后我们将设置我们的 Mongo 数据库连接。初始化后,如果没有记录,我们将填充数据库以进行测试。

这将是一个两阶段的过程。

在第一阶段,我们必须计算 idf。

为此,我们将发布 MapReduce。

必须传递术语变量才能被 MapReduce 进程使用。

为了在 MapReduce 上使用动态变量,我们将使用范围参数。


 function TfIdf() {
}

TfIdf.prototype.weights = function(documents,term) {

var results = []

var idf = this.idf(documents,term)

for(var i=0;i<documents.length;i++) {

    var tf = this.tf(documents[i],term)
    var tfidf = tf*idf
    var result = {weight:tfidf,doc:documents[i]}    

    results.push(result)
}

return results

}

TfIdf.prototype.tf = function(words,term) {

var result = 0

for(var i=0;i<words.length;i++) {

    var word = words[i]

    if(word.indexOf(term)!=-1) {
        result = result+1
    }    
}

return result/words.length

}

TfIdf.prototype.idf = function(documents,term) {

var occurence = 0

for(var j=0;j<documents.length;j++) {

    var doc = documents[j]

    if(this.__wordInsideDoc(doc,term)){
        occurence = occurence+1
    }                  
}

if(occurence==0) {
    return undefined    
}

return Math.log(documents.length/occurence)

}

TfIdf.prototype.__wordInsideDoc = function(doc,term) {

for(var i=0;i<doc.length;i++) {

    var word = doc[i]

    if(word.indexOf(term)!=-1) {
        return true
    }
}    

return false

}

module.exports = TfIdf

结果是一个数字。

在第二阶段,我们必须计算每个文档的 tf,并将结果与​​之前计算的 idf 值相乘。

MapReduce 也将用于这种情况。

这次通过范围参数,我们将传递我们搜索的术语以及 idf 变量。


 function TfIdf() {
}

TfIdf.prototype.weights = function(documents,term) {

var results = []

var idf = this.idf(documents,term)

for(var i=0;i<documents.length;i++) {

    var tf = this.tf(documents[i],term)
    var tfidf = tf*idf
    var result = {weight:tfidf,doc:documents[i]}    

    results.push(result)
}

return results

}

TfIdf.prototype.tf = function(words,term) {

var result = 0

for(var i=0;i<words.length;i++) {

    var word = words[i]

    if(word.indexOf(term)!=-1) {
        result = result+1
    }    
}

return result/words.length

}

TfIdf.prototype.idf = function(documents,term) {

var occurence = 0

for(var j=0;j<documents.length;j++) {

    var doc = documents[j]

    if(this.__wordInsideDoc(doc,term)){
        occurence = occurence+1
    }                  
}

if(occurence==0) {
    return undefined    
}

return Math.log(documents.length/occurence)

}

TfIdf.prototype.__wordInsideDoc = function(doc,term) {

for(var i=0;i<doc.length;i++) {

    var word = doc[i]

    if(word.indexOf(term)!=-1) {
        return true
    }
}    

return false

}

module.exports = TfIdf

我们将实现结合前两个步骤的 tfIdf 函数。

该函数将我们需要搜索的术语作为参数。


 function TfIdf() {
}

TfIdf.prototype.weights = function(documents,term) {

var results = []

var idf = this.idf(documents,term)

for(var i=0;i<documents.length;i++) {

    var tf = this.tf(documents[i],term)
    var tfidf = tf*idf
    var result = {weight:tfidf,doc:documents[i]}    

    results.push(result)
}

return results

}

TfIdf.prototype.tf = function(words,term) {

var result = 0

for(var i=0;i<words.length;i++) {

    var word = words[i]

    if(word.indexOf(term)!=-1) {
        result = result+1
    }    
}

return result/words.length

}

TfIdf.prototype.idf = function(documents,term) {

var occurence = 0

for(var j=0;j<documents.length;j++) {

    var doc = documents[j]

    if(this.__wordInsideDoc(doc,term)){
        occurence = occurence+1
    }                  
}

if(occurence==0) {
    return undefined    
}

return Math.log(documents.length/occurence)

}

TfIdf.prototype.__wordInsideDoc = function(doc,term) {

for(var i=0;i<doc.length;i++) {

    var word = doc[i]

    if(word.indexOf(term)!=-1) {
        return true
    }
}    

return false

}

module.exports = TfIdf

我们的测试展示案例:


 function TfIdf() {
}

TfIdf.prototype.weights = function(documents,term) {

var results = []

var idf = this.idf(documents,term)

for(var i=0;i<documents.length;i++) {

    var tf = this.tf(documents[i],term)
    var tfidf = tf*idf
    var result = {weight:tfidf,doc:documents[i]}    

    results.push(result)
}

return results

}

TfIdf.prototype.tf = function(words,term) {

var result = 0

for(var i=0;i<words.length;i++) {

    var word = words[i]

    if(word.indexOf(term)!=-1) {
        result = result+1
    }    
}

return result/words.length

}

TfIdf.prototype.idf = function(documents,term) {

var occurence = 0

for(var j=0;j<documents.length;j++) {

    var doc = documents[j]

    if(this.__wordInsideDoc(doc,term)){
        occurence = occurence+1
    }                  
}

if(occurence==0) {
    return undefined    
}

return Math.log(documents.length/occurence)

}

TfIdf.prototype.__wordInsideDoc = function(doc,term) {

for(var i=0;i<doc.length;i++) {

    var word = doc[i]

    if(word.indexOf(term)!=-1) {
        return true
    }
}    

return false

}

module.exports = TfIdf

对于这两种情况,我们得到相同的结果。


 function TfIdf() {
}

TfIdf.prototype.weights = function(documents,term) {

var results = []

var idf = this.idf(documents,term)

for(var i=0;i<documents.length;i++) {

    var tf = this.tf(documents[i],term)
    var tfidf = tf*idf
    var result = {weight:tfidf,doc:documents[i]}    

    results.push(result)
}

return results

}

TfIdf.prototype.tf = function(words,term) {

var result = 0

for(var i=0;i<words.length;i++) {

    var word = words[i]

    if(word.indexOf(term)!=-1) {
        result = result+1
    }    
}

return result/words.length

}

TfIdf.prototype.idf = function(documents,term) {

var occurence = 0

for(var j=0;j<documents.length;j++) {

    var doc = documents[j]

    if(this.__wordInsideDoc(doc,term)){
        occurence = occurence+1
    }                  
}

if(occurence==0) {
    return undefined    
}

return Math.log(documents.length/occurence)

}

TfIdf.prototype.__wordInsideDoc = function(doc,term) {

for(var i=0;i<doc.length;i++) {

    var word = doc[i]

    if(word.indexOf(term)!=-1) {
        return true
    }
}    

return false

}

module.exports = TfIdf

为什么我应该使用 MapReduce 来解决这个问题?

tf-idf 排序问题是一个包含计算的问题,可以并行化。
顺序方法可能是其他环境的一种选择,但对于 Node.js 来说有很多缺点。

Node.js 是一个单线程环境,它不是为繁重的计算任务而设计的。

它的魔力与它执行 I/O 操作的能力有关。

考虑大型数据集问题的场景。

虽然 Node.js 进程将执行耗时的计算,但发出的请求将无法正确执行。

然而,基于 Node.js 的解决方案有一些解决方法,例如生成额外的节点并实现它们之间的通信方式。

总结

MapReduce 非常适合排名问题。它不仅消除了大部分计算开销,而且还消除了实现开销。