按包含过滤,然后按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
,那么它应该按以下顺序显示结果。
也就是说,我们正在寻找从逻辑开始然后包含逻辑的结果。
我尝试了以下方法,但没有奏效,有人可以指导我吗?
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应用搜索过滤器