AngularJS:按搜索文本的相似性排序搜索结果

时间:2014-04-24 21:01:40

标签: javascript angularjs

在过去的一年左右的时间里,我已经构建了无数角度驱动的搜索式搜索,我喜欢它们实现起来的容易程度。但是,我从来没有找到一个好的解决方案的一个问题是如何正确地对这些搜索结果进行排序。我真的很感激任何有关如何做到这一点的想法。

这是我的问题

请参阅This Plunker以获取参考

如果你看一下上面这个超简单的例子,我有一个我希望能够搜索的样本宠物列表。如果我在字段中输入任何文本,宠物列表就会缩小。这一切都很棒。我的问题是,搜索结果的排序与它们与输入文本的相似程度无关。

例如

列表中共有8只宠物,使用列表name指令中的orderBy:'name'过滤器按ng-repeat排序。如果我只输入字符T到输入中,我只得到5个剩余的结果。但是列表顶部的宠物是“blackie”(因为blackie是列表中第一个有t的宠物,因为blackie的type是“cat”,其中包含{ {1}})。

Sample search results

我希望看到的是通过它们与搜索文本的相似性来对剩余结果进行排序的方法。因此,在上面的示例中,我不会在列表顶部看到“blackie”,而是会看到“tom”(位于已过滤列表的底部),因为tom的名称实际上以字母t开头这是我开始搜索的内容,更有可能是我想要找到的内容。

有关如何实现此类搜索算法的任何想法?我无法在谷歌的所有搜索中找到任何东西,也没有尝试编写这样的算法。

提前感谢您的想法!

3 个答案:

答案 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

无论如何,感谢大家的意见和建议!