在过去的一年左右的时间里,我已经构建了无数角度驱动的搜索式搜索,我喜欢它们实现起来的容易程度。但是,我从来没有找到一个好的解决方案的一个问题是如何正确地对这些搜索结果进行排序。我真的很感激任何有关如何做到这一点的想法。
这是我的问题:
请参阅This Plunker以获取参考
如果你看一下上面这个超简单的例子,我有一个我希望能够搜索的样本宠物列表。如果我在字段中输入任何文本,宠物列表就会缩小。这一切都很棒。我的问题是,搜索结果的排序与它们与输入文本的相似程度无关。
例如:
列表中共有8只宠物,使用列表name
指令中的orderBy:'name'
过滤器按ng-repeat
排序。如果我只输入字符T
到输入中,我只得到5个剩余的结果。但是列表顶部的宠物是“blackie”(因为blackie是列表中第一个有t
的宠物,因为blackie的type
是“cat”,其中包含{ {1}})。
我希望看到的是通过它们与搜索文本的相似性来对剩余结果进行排序的方法。因此,在上面的示例中,我不会在列表顶部看到“blackie”,而是会看到“tom”(位于已过滤列表的底部),因为tom的名称实际上以字母t
开头这是我开始搜索的内容,更有可能是我想要找到的内容。
有关如何实现此类搜索算法的任何想法?我无法在谷歌的所有搜索中找到任何东西,也没有尝试编写这样的算法。
提前感谢您的想法!
答案 0 :(得分:6)
定义您自己的过滤器以缩小列表范围,并为每个项目计算排名并将计算的排名分配给该项目。然后按等级排序。
像这样更改HTML:
<pre ng-repeat="pet in pets | ranked:inputText | orderBy:'rank':true">{{pet | json}}</pre>
添加以下过滤器:
app.filter('ranked', function() {
return function(obj, searchKey)
{
if (!obj)
return obj;
for(var i =0; i< obj.length; i++)
{
var currentObj = obj[i];
var numOfName = currentObj.name.indexOf(searchKey) + 1;
var numOfType = currentObj.type.indexOf(searchKey) + 1;
var numOfColor = currentObj.color.indexOf(searchKey) + 1;
currentObj.rank = numOfName * 10 + numOfType * 5 + numOfColor;
}
return obj;
}
});
答案 1 :(得分:2)
一个简单的方法是JSON.stringify
整个对象,在它上面运行一个正则表达式,并为每次出现(DEMO)分发点数:
app.filter('search', function() {
return function(items, str) {
if(str == '') return items;
var filtered = [];
var rgx = new RegExp(str, 'gi');
angular.forEach(items, function(item) {
item.points = (JSON.stringify(item).match(rgx) || []).length;
if(item.points > 0) filtered.push(item);
});
return filtered;
}
});
使用ng-repeat
中带有orderBy
点的过滤器:
<pre ng-repeat="pet in pets | search : inputText | orderBy : 'points' : true">
说实话,我不知道这个规模有多好,如果最好循环通过属性而不是stringify。但是我能看到它的效果非常好。
这是一个稍微修改过的版本,如果一个单词以给定的搜索字符串(DEMO)开头,则会发出一个额外的点:
//..
var rgxstart = new RegExp(': ?"'+str);
angular.forEach(items, function(item) {
var stringified = JSON.stringify(item);
item.points = (stringified.match(rgx) || []).length;
if(rgxstart.test(stringified)) item.points++;
if(item.points > 0) filtered.push(item);
});
//...
当然,这个解决方案非常通用,并且工作方式不需要了解对象本身,这也意味着它无法提供最佳结果。
答案 2 :(得分:1)
我真的很惊讶这么棒的答案来得这么快!多谢你们!
窃取@ Aidin的回答,我决定采用他的解决方案,但过滤器略有不同:
app.filter('rankSearch', function() {
return function(array, searchKey, props){
if (!array || !searchKey || !props) return array;
for(var i =0; i<array.length; i++){
var obj = array[i];
obj.rankSearch = 0;
for(var j=0; j<props.length; j++){
var index = obj[props[j]].indexOf(searchKey);
// i should probably spend time tweaking these arbitrary numbers
// to find good values that produce the best results, but
// here's what I have so far...
obj.rankSearch += (index === -1 ? 15 : index) * ((j+1)*8);
}
}
return array;
}
});
允许我在标记中执行此操作:
<pre ng-repeat="pet in pets | rankSearch:inputText:['name','type','color'] | orderBy:'rankSearch'">{{pet | json}}</pre>
我在这个解决方案中看到的一个潜在问题是,如果数组中的任何对象已经具有rankSearch
属性(尽管不太可能),它将被过滤器清除。因此,我将其从@ Aidin的rank
更改为rankSearch
,以使其更加独特。此外,这会使优先级较高的项目的数字较小,因此您不必将reverse
标记添加到orderBy
过滤器的末尾。
但是这也允许您为任何对象数组重用此过滤器,因为您可以直接从标记中指定要排名的对象属性。
您可以查看结果Plunker here。
无论如何,感谢大家的意见和建议!