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

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

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 63w+ 字,讲解图 2808+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2200+ 小伙伴加入学习 ,欢迎点击围观

在 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 非常适合排名问题。它不仅消除了大部分计算开销,而且还消除了实现开销。

相关文章