日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

搜索推荐系统根据用户搜索频率(热搜)排序

發布時間:2024/10/12 windows 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 搜索推荐系统根据用户搜索频率(热搜)排序 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

之前寫的三叉樹,有點兒簡單,并不能滿足實際項目的需要。先簡單分析一下solr中搜索推薦系統的核心算法。

  wiki中有關于solr的搜索推薦的詳細描述,但是核心算法需要自己查看源代碼。關于wiki上的解讀,之前做了一次簡單的翻譯,根據此文檔,詳細研讀了源代碼,先把核心思想呈現出來。

   基本流程如下:當用戶輸入搜索詞語前綴時,通過前端調用solr的suggest,找到Suggeser對象,Suggester根據匹配的field從主索引庫中讀取field下面的terms,來構建dictionry,由于主索引庫中的terms是經過合并和排序的,索引在構建三叉樹的時候,省去了用pinyin4j組件進行排序的過程。接下來,就是通過對字典的折中處理,來實現自平衡的三叉樹,以提高檢索效率。三叉樹構建完之后,進行前綴匹配查詢,搜索出所有符合要求的詞元,然后加入到優先級隊列中,構建有限容量的堆,調整堆頂的值為最小。之所以Lucene自己寫了PriorityQueue<T>,而不用jdk自身的,是因為jdk的PriorityQueue<T>,容量可以擴展的,他會把所有匹配出來的詞元都加進去,然后輸出top N詞元,這明顯是內存浪費。之前的一篇關于從海量數據中,查找出top N數據的博客中,已經闡述了堆排序的思想,不贅述。最后通過優先級隊列輸出結果。

  Suggester---Lookup----LookupImpl(TSTLookup、JaSpellLookup、FSTLookup),之前研讀的是TSTLookup。排序的核心思想是:構建完字典之后,得到Dictionry對象,由Dictionary對象得到InputIterator,對字典進行掃描讀取,能讀取到兩個變量:一個為term,另一個為term的權重,排序用的。對字典掃描結束后,把terms和weight分別加載到兩個list中,以便插入三叉樹中。那么,三叉樹節點對象的設計,就很重要了。封裝以下屬性:storedChar、val(weight)、token(最后節點存儲的term成詞)。插入的具體邏輯,自己對上次的寫的三叉樹,進行了改進,代碼如下:

package chinese.utility.ternaryTree;
/**
* 三叉樹節點
* @author TongXueQiang
* @date 2016/03/12
* @since JDK 1.7
*/
public class TernaryNode {
public char storedChar; //節點存儲的單個字符
public String token;//最后一個節點存儲的term(成詞)
public TernaryNode leftNode,centerNode,rightNode;//子節點

public TernaryNode (char storedChar) {
this.storedChar = storedChar;
}
}

package chinese.utility.ternaryTree;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
* 自定義三叉樹
*?
* @author TongXueQiang
* @date 2016/03/12
* @since JDK 1.7
*/
public class TernaryTree {
// 根節點,不存儲字符
private static TernaryNode root = new TernaryNode('\0');

/**
* 向樹中插入字符
*?
* @param TernaryNode
* @param word
* @return
*/
public void insert(String word) {
root = insert(root, word, 0);
}

public TernaryNode insert(TernaryNode currentTernaryNode, String word, int index) {
if (word == null || word.length() < index) {
return currentTernaryNode;
}
char[] charArray = word.toCharArray();

if (currentTernaryNode == null) {
currentTernaryNode = new TernaryNode(charArray[index]);
}

if (currentTernaryNode.storedChar > charArray[index]) {
currentTernaryNode.leftNode = insert(currentTernaryNode.leftNode, word, index);
} else if (currentTernaryNode.storedChar < charArray[index]) {
currentTernaryNode.rightNode = insert(currentTernaryNode.rightNode, word, index);
} else {
if (index != word.length() - 1) {
currentTernaryNode.centerNode = insert(currentTernaryNode.centerNode, word, index + 1);
} else {
currentTernaryNode.token = word;
}
}

return currentTernaryNode;
}

/**
* 查找以指定前綴開頭的所有字符串
*?
* @param prefix
* @return
*/
public List<TernaryNode> prefixCompletion(String prefix) {
// 首先查找前綴的最后一個節點
TernaryNode TernaryNode = findNode(prefix);
return prefixCompletion(TernaryNode);
}

public List<TernaryNode> prefixCompletion(TernaryNode p) {
List<TernaryNode> suggest = new ArrayList<TernaryNode>();

if (p == null) return suggest;
if ((p.centerNode == null) && (p.token == null)) return suggest;
if ((p.centerNode == null) && (p.token != null)) {
suggest.add(p);
return suggest;
}

if (p.token != null) {
suggest.add(p);
}

p = p.centerNode;

Stack<TernaryNode> s = new Stack<TernaryNode>();
s.push(p);
while (!s.isEmpty()) {
TernaryNode top = s.pop();

if (top.token != null) {
suggest.add(top);
}
if (top.centerNode != null) {
s.push(top.centerNode);
}
if (top.leftNode != null) {
s.push(top.leftNode);
}
if (top.rightNode != null) {
s.push(top.rightNode);
}
}
return suggest;
}

/**
* 查找前綴的下一個centerTernaryNode,作為searchPrefix的開始節點
*?
* @param prefix
* @return
*/
public TernaryNode findNode(String prefix) {
return findNode(root, prefix, 0);
}

private TernaryNode findNode(TernaryNode TernaryNode, String prefix, int index) {
if (prefix == null || prefix.equals("")) {
return null;
}
if (TernaryNode == null) {
return null;
}
char[] charArray = prefix.toCharArray();
// 如果當前字符小于當前節點存儲的字符,查找左節點
if (charArray[index] < TernaryNode.storedChar) {
return findNode(TernaryNode.leftNode, prefix, index);
}
// 如果當前字符大于當前節點存儲的字符,查找右節點
else if (charArray[index] > TernaryNode.storedChar) {
return findNode(TernaryNode.rightNode, prefix, index);
} else {// 相等
// 遞歸終止條件
if (index !=charArray.length - 1) {
return findNode(TernaryNode.centerNode, prefix, ++index);
}?
}
return TernaryNode;
}
}

改進后的三叉樹,前綴匹配查找后得到結果不再是簡單的字符串,而是節點對象,里面封裝了匹配的term和weight值。接下來,把得到的suggest加入得到優先級隊列中,得到升序的結果。測試代碼如下:

?

package chinese.utility.test;

import java.util.Stack;

import chinese.utility.utils.PriorityQueue;

/**
* 輸出topN,用Lucene的PriorityQueue,思路和之前寫過的堆排序類似,隊列中容量為N,
* 當向隊列中插入的時候,調整堆,讓堆頂元素最小,當超過容量的時候,在插入的時候,如果插入
* 的元素比堆頂元素大,則替換之,如果小,廢棄之。最后按升序排列輸出topN.要實現lessThan
* 方法,確定比較的項目,比如輸入token的權重weight.
* @author hadoop
* @date 2016/03/11
*/
public class MyPriorityQueue extends PriorityQueue<Object> {

public MyPriorityQueue(int num) {
super(num);
}

@Override
protected boolean lessThan(Object a, Object b) {
return (Integer)a < (Integer)b;
}

public static void main(String[] f) {
MyPriorityQueue priQueue = new MyPriorityQueue(3);?
priQueue.insertWithOverflow(100);
priQueue.insertWithOverflow(98);
priQueue.insertWithOverflow(84);
priQueue.insertWithOverflow(78);
// 打印結果,輸出前三個最大值,按降序排列
Stack<Integer> stack = new Stack<Integer>();?
Integer v = (Integer) priQueue.pop();

while (v != null) {
stack.push(v);
v = (Integer) priQueue.pop();
}

Integer num = stack.pop();
while (num != null) {
System.out.println(num);
if (stack.isEmpty()) break;
num = stack.pop();
}
}
}

結果是:

100
98
84

然而,上述排序是根據term的詞庫頻率,并不是根據用戶的搜索頻率,沒有實現熱搜。要想實現熱搜,需要用另一個方案。之前的一篇博客中,有這么一道題目:用戶在進行搜索時,搜索引擎的日志,會記錄下用戶的搜索軌跡。比如有一篇文檔,里面有千萬級的數量,去除重復后可能就有300萬行,如何從中找出top 10的搜索字符串?之前只是寫了思路,沒有給出具體實現。實際項目中,需要借鑒這個思路,重新整理一下:

第一步:對文本進行排序和折中處理,更新文本,要要用到pinyin4j項目包;

第二步:把更新后的字典,加載到三叉樹中,實現平衡的三叉樹,自定義的三叉樹要增加節點字符出現次數的變量,以便實現詞頻統計;

第三步:遍歷字典,每次讀到的詞語,用三叉樹查詢,得到頻率,然后把讀到的詞語和頻率寫到另一個文件中,用空格分開,類似于Key-value鍵值對形式;

第四步:和從海量數據中查找出前k個最小或最大值的算法(java)問題雷同,從海量數據中查找出前10個最小值;

第五步:得到最小頻率值的堆后,從新的文本中找到對應的詞語,加入到set中,統一頻率的詞語會有很多,而不是一個。

在實際項目中,如何記錄用戶的搜索頻率?上述思路只能作為借鑒,看一下美團是如何做的:

考慮專門為關鍵字建立一個索引collection,利用solr前綴查詢實現。solr中的copyField能很好解決我們同時索引多個字段(漢字、pinyin, abbre)的需求,且field的multiValued屬性設置為true時能解決同一個關鍵字的多音字組合問題。配置如下:

schema.xml:<field name="kw" type="string" indexed="true" stored="true" /> <field name="pinyin" type="string" indexed="true" stored="false" multiValued="true"/> <field name="abbre" type="string" indexed="true" stored="false" multiValued="true"/> <field name="kwfreq" type="int" indexed="true" stored="true" /> <field name="_version_" type="long" indexed="true" stored="true"/> <field name="suggest" type="suggest_text" indexed="true" stored="false" multiValued="true" /> ------------------multiValued表示字段是多值的------------------------------------- <uniqueKey>kw</uniqueKey> <defaultSearchField>suggest</defaultSearchField>說明: kw為原始關鍵字 pinyin和abbre的multiValued=true,在使用solrj建此索引時,定義成集合類型即可:如關鍵字“重慶”的pinyin字段為{chongqing,zhongqing}, abbre字段為{cq, zq} kwfreq為用戶搜索關鍵的頻率,用于查詢的時候排序-------------------------------------------------------<copyField source="kw" dest="suggest" /> <copyField source="pinyin" dest="suggest" /> <copyField source="abbre" dest="suggest" />------------------suggest_text----------------------------------<fieldType name="suggest_text" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="true"><analyzer type="index"><tokenizer class="solr.KeywordTokenizerFactory" />&lt;filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true" /><filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" /><filter class="solr.LowerCaseFilterFactory" /><filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt" /></analyzer><analyzer type="query"><tokenizer class="solr.KeywordTokenizerFactory" /><filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" /><filter class="solr.LowerCaseFilterFactory" /><filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt" /></analyzer> </fieldType>

KeywordTokenizerFactory:這個分詞器不進行任何分詞!整個字符流變為單個詞元。String域類型也有類似的效果,但是它不能配置文本分析的其它處理組件,比如大小寫轉換。任何用于排序和大部分Faceting功能的索引域,這個索引域只有能一個原始域值中的一個詞元。

前綴查詢構造:

private SolrQuery getSuggestQuery(String prefix, Integer limit) {SolrQuery solrQuery = new SolrQuery();StringBuilder sb = new StringBuilder();sb.append(“suggest:").append(prefix).append("*");solrQuery.setQuery(sb.toString());solrQuery.addField("kw");solrQuery.addField("kwfreq");solrQuery.addSort("kwfreq", SolrQuery.ORDER.desc);solrQuery.setStart(0);solrQuery.setRows(limit);return solrQuery; }

轉載于:https://www.cnblogs.com/txq157/p/5269896.html

總結

以上是生活随笔為你收集整理的搜索推荐系统根据用户搜索频率(热搜)排序的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。