按包含过滤,然后按startsWith排序

Posted

技术标签:

【中文标题】按包含过滤,然后按startsWith排序【英文标题】:Filter by includes and then sort by startsWith 【发布时间】:2021-09-22 17:40:36 【问题描述】:

我正在实现一个typeahead,我们需要过滤它应该开始(排序)结果的记录StartsWith,然后includes

样本数据:

Dotnet Developer  
Azure Administrator  
Microsoft Azure  
Azure DevOps  
UX Developer  
IOT Azure  

如果我开始输入Azu,那么它应该按以下顺序显示结果。

Azu是管理员 Azu重新开发运维 微软Azu重新 物联网Azu重新

也就是说,我们正在寻找从逻辑开始然后包含逻辑的结果。

我尝试了以下方法,但没有奏效,有人可以指导我吗?

abc.filter(v => v.includes(filterTerm)).sort(function(a, b) 
  return a.startsWith(filterTerm)
);  

还附上jsfiddle 和一些示例数据。

【问题讨论】:

你应该返回一个数字给排序函数,而不是一个布尔值 【参考方案1】:

我会提前对列表进行排序。现在,您只需要对 input 事件进行去抖动处理,并使用正则表达式来突出显示结果中的匹配项。

const data = `
  Dotnet Developer  
  Azure Administrator  
  Microsoft Azure  
  Azure DevOps  
  UX Developer  
  IOT Azure 
`.trim().split('\n').map(line => line.trim()).sort();

// https://www.joshwcomeau.com/snippets/javascript/debounce/
const debounce = (callback, wait) => 
  let timeoutId = null;
  return (...args) => 
    window.clearTimeout(timeoutId);
    timeoutId = window.setTimeout(() => 
      callback.apply(null, args);
    , wait);
  ;


const emptyEl = (el) => 
  while (el.firstChild) el.firstChild.remove();
;

const renderResults = (target, term, hits) => 
  emptyEl(target);
  if (term.length > 0 && hits.length > 0) 
    const regex = new RegExp(`($term)`, 'i');
    const listEl = document.createElement('ul');
    hits.forEach(hit => 
      const listItemEl = document.createElement('li');
      listItemEl.innerhtml = hit.replace(regex, '<strong>$1</strong>');
      listEl.append(listItemEl);
    );
    target.append(listEl);
   else 
    const emptyEl = document.createElement('p');
    emptyEl.textContent = 'No Results...';
    target.append(emptyEl);
  


const onSearch = debounce((e) => 
  const term = e.target.value.trim();
  const results = document.querySelector('.results');
  const hits = data.filter(text => text.includes(term));
  renderResults(results, term, hits);
, 250);

document.querySelector('.search').addEventListener('input', onSearch);
strong  font-weight: bold; color: green; 
<input type="search" class="search" placeholder="Search..." />
<h2>Results</h2>
<div class="results">
  <p>No Results...</p>
</div>

【讨论】:

【参考方案2】:

您可以计算一个分数(0 或 1)来表示结果的优先级,并使用它来排序:

const abc = [
  "Dotnet Developer (should not appear)",
  "Azure Administrator (should be first)",
  "Microsoft Azure (should be second)",
  "Azure DevOps (should be first)",
  "UX Developer (should not appear)",
  "IOT Azure (should be second)",
];

const filterTerm = "Azu";

const score = searched => string => string.startsWith(searched) ? 1 : 0;

const result = abc.filter(v => v.includes(filterTerm)).sort((a, b) => 
  const [aScore, bScore] = [a, b].map(score(filterTerm));
  return bScore - aScore;
);

console.log(result);

【讨论】:

【参考方案3】:

像这样更改您的自定义排序。当b 以您的首选字符串开头时,我将返回 1。返回 1 时,将 b 放在 a 前面。

var abc = ['test', 'test 123', 'test 234', 'abc', 'abc 123', 'xyz', '123', '1235'];
var filterTerm = '123';

var includesList = abc.filter(v => v.includes(filterTerm));

var sortedList = abc.filter(v => v.includes(filterTerm)).sort(function(a, b) 
  if(b.startsWith(filterTerm)) return 1;
  return -1;
);

console.log(sortedList);

【讨论】:

排序函数不应该只使用两个参数之一 哦。有什么具体原因吗? 因为每个“组”中项目的顺序可能取决于浏览器对Array.prototype.sort的实现【参考方案4】:

我认为这可行:

const search = (filterTerm, terms) => 
    filterTerm = filterTerm.toLowerCase();
    return terms
        .filter(v => v.toLowerCase().includes(filterTerm))
        .sort((a, b) => 
            const aStarts = a.toLowerCase().startsWith(filterTerm);
            const bStarts = b.toLowerCase().startsWith(filterTerm);
            if (aStarts && bStarts) return a.localeCompare(b);
            if (aStarts && !bStarts) return -1;
            if (!aStarts && bStarts) return 1;
            return a.localeCompare(b);
        );
;

console.log(search("azu", [
    "IOT Azure",
    "Dotnet Developer",
    "Microsoft Azure",
    "Azure DevOps",
    "UX Developer",
    "Azure Administrator",
]));

如果您的术语列表是预定义和固定的,但搜索频繁发生,您可以简化功能以供重复使用:

const search = terms => filterTerm => 
    filterTerm = filterTerm.toLowerCase();
    return terms
        .filter(v => v.toLowerCase().includes(filterTerm))
        .sort((a, b) => 
            const aStarts = a.toLowerCase().startsWith(filterTerm);
            const bStarts = b.toLowerCase().startsWith(filterTerm);
            if (aStarts && bStarts) return a.localeCompare(b);
            if (aStarts && !bStarts) return -1;
            if (!aStarts && bStarts) return 1;
            return a.localeCompare(b);
        );
;

const programmingTerms = search([
    "IOT Azure",
    "Dotnet Developer",
    "Microsoft Azure",
    "Azure DevOps",
    "UX Developer",
    "Azure Administrator",
]);

用法:

programmingTerms("Azu");  // -> ["Azure Administrator", "Azure DevOps", "IOT Azure", "Microsoft Azure"]
programmingTerms("Dev");  // -> ["Azure DevOps", "Dotnet Developer", "UX Developer"]

【讨论】:

【参考方案5】:

我已经调整了排序功能,它现在可以工作了。你需要给排序函数一个数字。

var abc = ['test', 'test 123', 'test 234', 'abc', 'abc 123', 'xyz', '123', '1235'];
var filterTerm = '123';

var includesList = abc.filter(v => v.includes(filterTerm));

var sortedList = includesList.sort((a, b) => 
  return a.indexOf(filterTerm) < b.indexOf(filterTerm) ? -1 : 1;
);

console.log(sortedList)

【讨论】:

以上是关于按包含过滤,然后按startsWith排序的主要内容,如果未能解决你的问题,请参考以下文章

如何在android中的简单列表视图上使用startsWith应用搜索过滤器

Django ORM 按过滤器排序

magento 按位置排序过滤器选项

Apollo GraphQL 服务器:按单独解析的字段过滤(或排序)

UITableView 通过按下按钮对内容进行排序[关闭]

手把手教你如何用java8新特性将List中按指定属性排序,过滤重复数据