Javascript 什么';autosuggest最合适的数据结构是什么?
我想实现一个autosuggest组件。对于每个用户输入,组件应该提供零个或多个建议 例如,如果用户类型Javascript 什么';autosuggest最合适的数据结构是什么?,javascript,node.js,data-structures,Javascript,Node.js,Data Structures,我想实现一个autosuggest组件。对于每个用户输入,组件应该提供零个或多个建议 例如,如果用户类型“公园”,建议可以是:[“帕克维尔”、“帕克伍德”、“贝尔公园”] 要求很简单: 它应该不区分大小写(对于'park'、'park'和'park',用户应该得到相同的建议) 它应该在每个单词的开头匹配('pa'将匹配'Parkville','Bell Park',和'Very cool Park',但不匹配'Carpark') 您会选择什么数据结构在Javascript中实现这一点 有没有
“公园”
,建议可以是:[“帕克维尔”、“帕克伍德”、“贝尔公园”]
要求很简单:
- 它应该不区分大小写(对于
、'park'
和'park'
,用户应该得到相同的建议)'park'
- 它应该在每个单词的开头匹配(
将匹配'pa'
,'Parkville'
,和'Bell Park'
,但不匹配'Very cool Park'
)'Carpark'
如果您需要更简单的东西,您可以查看是否有任何Levenshtein距离的实现。Soundex是一种更酷的算法,它根据单词的语音特性进行评分。我认为这类任务的最佳数据结构是一个。关于大小写不敏感-在添加到trie之前,只需将每个单词小写,并对大小写较低的单词执行搜索 当您到达trie的某个点时,会有许多子节点满足字符串的要求—字符串的前缀为从根到当前点 输出建议-递归地从当前点(从根到用户键入的前缀)遍历,并在标记为叶的节点上打印建议。10次输出后停止打印,因为trie可能有许多令人满意的单词 以下是js实现:,以及其他许多实现。只需搜索js+trie。可能是trie+autosuggest+js也可以) 更新 如果您想在
O(1)
(当然,对于每个建议都是O(1))中输出所有变量,而不需要递归遍历,那么可以在每个节点中存储引用的arraylist。Arraylist存储属于节点的所有单词的索引,每个值都是其他字典araylist中的索引
诸如此类:
在dict中添加单词:
O(word\u len)
是否在trie中(已添加)。如果不是,则添加到字典并记住“存储”中的索引
Go to node with prefix==typed word;
for(int i=0;i<min(curNode.references.length(),10);i++)
print(dict[curNode.references[i]];
换言之,短语代码与单字代码相同。引用是一样的,因为我们把所有的部分作为一个短语存储在一个单元格中。不同之处在于分部分拆分和添加法尔酶。很简单,你看
更新
顺便说一句,存储引用在内存消耗方面并不“昂贵”——单词中的每个字母都是trie中的某个节点,这意味着某个arraylist中有一个条目(全局存储数组中该单词的一个整数索引)。因此,您只需要额外的O(dictionary_length)内存,即~50000*20=10000000个整数~4MB,假设每个单词最多有20个字母。所以,所需内存的上限是4MB
更新
关于东鹰
好的,在发布任何想法之前,我想警告一下,这是非常奇怪的autosuggest行为,通常autosuggest匹配一个前缀,但不是所有前缀
有一个非常简单的想法,它将增加这样几个前缀的搜索复杂度,并行搜索一些delta,其中delta复杂度=查找集合交集的复杂度
,其中a=存储中的索引,b=pharse中的索引。
对于简单的单词b=0或-1或任何特殊值a
索引(其中b=1
放在某个集合中)。像往常一样找到ri
节点,迭代引用并将这些a
索引放到另一个集合中,其中b=2
,依此类推。求指数集的交集。按索引输出存储字,其中索引属于集合的交集当你搜索的不是短语而是简单的单词时,你会反复搜索所有的参考项。有时候简单的方法是最好的。你说你的字典里有大约50000条词条。你不会说其中有多少有多个词(例如“贝尔公园”或“马丁·路德·金大道”,等等)。为了便于讨论,我们假设每个词典条目的平均字数为2 我对Javascript不是很在行,所以我将用一般术语来描述它,但您应该能够相当容易地实现它 在预处理步骤中,创建数据库中所有项目的数组(即所有项目)。例如:
Carpark
Cool Park
Park
Pike's Peak
...
然后,创建一个映射,其中包含每个单词的一个条目,以及包含该单词的第一个数组中该项目的索引列表。因此,第二个数组将包含:
Carpark, {0}
Cool, {1}
Park, {1,2}
Pike's, {3}
Peak, {3}
按单词对第二个数组排序。所以顺序是{停车场,酷,公园,山顶,派克}
当用户键入“p”时,对单词数组进行二进制搜索,以找到以“p”开头的第一个单词。您可以从该点开始顺序扫描数据,直到找到一个不以P
开头的单词。访问每个单词时,将索引列表中引用的单词添加到输出中。(您必须在此处执行一些重复数据消除,但这很简单。)
二进制搜索是O(logn),所以找到第一个单词会非常快。尤其是在数据量如此之小的情况下。如果您正在为每个键入的字母执行HTTP请求,那么通信时间将缩短处理时间。没有一个特别好的理由试图加快这一进程
Carpark
Cool Park
Park
Pike's Peak
...
Carpark, {0}
Cool, {1}
Park, {1,2}
Pike's, {3}
Peak, {3}
function TrieNode(ch) {
this.key = ch;
this.isTail = false;
this.children = [];
}
TrieNode.prototype = {
constructor : TrieNode,
/**
* insert keyword into trie
*/
insert : function(word) {
if (word && word.length == 0)
return;
var key = word[0];
if (this.children[key] == null) {
this.children[key] = new TrieNode(key);
}
if (word.length == 1) {
this.children[key].isTail = true;
} else if (word.length > 1) {
this.children[key].insert(word.slice(1));
}
},
/**
* return whether a word are stored in trie
*/
search : function(word) {
if (word && word.length == 0 || this.children[word[0]] == null)
return false;
if (word.length == 1) {
return this.children[word[0]].isTail;
} else {
return this.children[word[0]].search(word.slice(1));
}
},
/**
* NOTICE: this function works only if length of prefix longer than minimum trigger length
*
* @param prefix
* keywords prefix
* @returns {Array} all keywords start with prefix
*/
retrieve : function(prefix) {
var MINIMUM_TRIGGER_LENGTH = 1;
if (prefix == null || prefix.length < MINIMUM_TRIGGER_LENGTH)
return [];
var curNode = this.walk(prefix);
var collection = [];
curNode && curNode.freetrieve(prefix, collection);
return collection;
},
walk : function(prefix) {
if (prefix.length == 1) {
return this.children[prefix];
}
if (this.children[prefix[0]] == null) {
return null;
}
return this.children[prefix[0]].walk(prefix.slice(1));
},
freetrieve : function(got, collection) {
for ( var k in this.children) {
var child = this.children[k];
if (child.isTail) {
collection.push(got + child.key);
}
child.freetrieve(got + child.key, collection);
}
}
}
// USAGE lists below
function initTrieEasily(keywords){
let trie= new TrieNode();
keywords.forEach(word => trie.insert(word));
return trie;
}
var words=['mavic','matrix','matrice','mavis','hello world'];
var trie=initTrieEasily(words);
trie.retrieve('ma'); // ["mavic", "mavis", "matrix", "matrice"]
trie.retrieve("mat") // ["matrix", "matrice"]
trie.search("hello"); // "false"
trie.search("hello world"); //"true"
trie.insert("hello");
trie.search("hello"); // "true"
trie.retrieve("hel"); // ["hello", "hello world"]