JS - 是否有更有效的方法将数组中的值与目标搜索词进行比较

JS - Is there a more efficient way to compare values in an array to a target search term

我的目标是在一组 object 中搜索标题相似或与搜索词完全匹配的内容。问题是我想优先考虑完全匹配而不是仅包含字符串的匹配。

当前代码通过多次循环来做到这一点,每次使用不同的条件,returns object 如果匹配。


class Item {
    constructor(title) {
        this.title = title;
    }
}

function findMatch(term) {
    const items = [new Item("term"), new Item("Longer Term"), new Item("Completely Different Title")];

    // Check if any match the search term exactly
    for (var item of items) {
        if (item.title === term) return item;
    }

    // Check if any match the search term, ignoring case
    for (var item of items) {
        if (item.title.toLowerCase() === term.toLowerCase()) return item;
    }

    // Check if any start with the search term
    for (var item of items) {
        if (item.title.toLowerCase().startsWith(term.toLowerCase())) return item;
    }

    // Check if any end with the search term
    for (var item of items) {
        if (item.title.toLowerCase().endsWith(term.toLowerCase())) return item;
    }

    // Check if any contain the search term
    for (var item of items) {
        if (item.title.toLowerCase().includes(term.toLowerCase())) return item;
    }
    
    return null;
}

console.log(findMatch("different")); // Item with title "Completely Different Title"

有没有一种方法可以更有效地执行此操作,例如在一个循环中 - 或者有没有更好的方法来搜索字符串?

我研究过使用 Levenshtein 算法,但这不适用于搜索“Comp”并获得标题为“Completely Different Title”的项目,因为“Comp”和“Completely Different Title”之间有很多不同之处" 比 "Comp" 和 "term" 之间的距离要小 - 有没有办法将相同的想法纳入此搜索?

如果您正在寻找 效率,我能想到的唯一可以减少处理的改进是提前将字符串小写,而不是将每个字符串中的每个值都小写环形。不过,这可能是一个非常微小的改进,在大多数情况下是不明显的。

class Item {
    constructor(title) {
        this.title = title;
        this.lowerTitle = title.toLowerCase();
    }
}
function findMatch(term) {
    const lowerTerm = term.toLowerCase();
    // use item.lowerTitle and lowerTerm when appropriate

您要实现的逻辑从根本上需要对所有元素进行循环以寻找一个条件,然后对所有元素进行另一个循环以寻找另一个条件,等等。因此没有真正的方法可以提高当前实现的计算复杂性.

您可以将部分或全部条件与正则表达式结合起来,但这会破坏要 returned 的匹配类型的优先顺序。

如果您想使代码 更短 并且更易于维护,这很简单 - 您可以使用一组回调,按顺序为每个项目调用:

const comparers = [
  (a, b) => a === b,
  (a, b) => a.startsWith(b),
  (a, b) => a.endsWith(b),
  (a, b) => a.includes(b),
]
for (const fn of comparers) {
  if (fn(item.lowerTitle, lowerTerm)) return item;
}

Is there a way to incorporate the same idea into this search?

检查 Levenshtein 距离会有点不同。您需要无条件地循环所有项目,并在循环完成后 return 找到最佳匹配项,而不是循环项目并 return 匹配一个项目。

let bestItem;
let lowestDistance = Infinity;
for (const item of items) {
  const dist = compare(item.lowerTitle, lowerTerm);
  if (dist < lowestDistance) {
    bestItem = item;
    lowestDistance = dist;
  }
}
return bestItem;

你至少会而不是最后.includes检查。根据您想要的逻辑,您也可以删除 startsWithendsWith 检查作为交换。

您可以根据“相似性”为您的项目分配 score,然后根据该分数和 return 匹配项过滤和排序项目:

class Item {
  constructor(title) {
    this.title = title;
  }
}
const items = [new Item("term"), new Item("Longer Term"), new Item("Completely Different Title")];

function findMatch(term) {
  for (var item of items) {
    // Check if any match the search term exactly
    if (item.title === term) item.score = 10000;

    // Check if any match the search term, ignoring case
    else if (item.title.toLowerCase() === term.toLowerCase()) item.score = 1000;

    // Check if any start with the search term
    else if (item.title.toLowerCase().startsWith(term.toLowerCase())) item.score = 100;

    // Check if any end with the search term
    else if (item.title.toLowerCase().endsWith(term.toLowerCase())) item.score = 10;

    // Check if any contain the search term
    else if (item.title.toLowerCase().includes(term.toLowerCase())) item.score = 1;
  }
  return items.filter(i => 0 < i.score).sort((a, b) => b.score - a.score).map(i => delete i.score && i)
}

console.log(findMatch("different")); // Item with title "Completely Different Title"
console.log(findMatch("term"));