【问题标题】:Improve string - prefix matching performance提高字符串 - 前缀匹配性能
【发布时间】:2021-08-23 17:23:17
【问题描述】:

我正在寻找一种方法来加快我的幼稚字符串匹配过程:

// Treat this as pseudo code
function find(input: string, prefixes: string[]) {
  for (let i = 0; i < prefixes.length; i++) {
    const prefix = prefixes[i];
    
    if (input.startsWith(prefix)) {
      return prefix;
    }
  }

  return null;
}

const prefixes = [ "Hey", "Hi", "Hola", ... ];
const prefix = find("Hey, I'm Michael", prefixes);

我研究了一些概率数据结构,例如布隆过滤器,但找不到适合我需要的数据结构。话虽如此,我实际上并不关心获得匹配的前缀,我也不需要 100% 保证匹配存在。我只需要知道输入是否绝对不包含任何前缀或它可能。

我还看到了一篇关于 Burst Tries 算法的文章,据我所知,该算法将起到类似的作用。坦率地说,虽然我对算法的了解不够深入,无法掌握完整的实现细节并确保这是我正在寻找的。​​p>

旁注: 我假设这个函数将获得的 99.95% 的输入不会匹配任何前缀。因此,我希望这是一个优化步骤,仅处理可能具有我正在寻找的前缀的字符串。

非常感谢任何帮助或建议:3

【问题讨论】:

  • 我们已经讨论了多少个字符串
  • 您可以将前缀添加到 trie 中,遍历输入字符串直到没有匹配项。这可能会更快,具体取决于您拥有多少元素。
  • 输入约束是什么?前缀字符串的数量。前缀字符串的最大长度。要匹配的字符串数。
  • 一种选择是创建 10 个布隆过滤器,每个前缀长度一个。如果每个前缀长度的结果是“绝对不在集合中”,则字符串不匹配。我将构建代码,以便从计算哈希的代码中检查布隆过滤器。换句话说,每个哈希都使用输入中的第一个字符进行更新,并检查 Bloom-length-1 过滤器。添加一个字符,检查 Bloom-length-2 等。大多数情况下,代码必须将哈希值一直到 10 个字符,以证明字符串不匹配。
  • 问题是你是否每次都得到新的前缀(函数所暗示的)或者它们很少改变,所以你可以对它们做一些预先计算。在这种情况下,我会选择 MinusFour 的建议。

标签: javascript string algorithm string-comparison


【解决方案1】:

如果前缀是预先知道的并且可以进行预处理,您可以尝试尝试一下。特别是如果它们将短至 10 个字符。这意味着每次检查大约需要 10 次比较。不知道能做得更好。

function buildTrie(trie, words){
  for (let word of words){
    let _trie = trie;

    for (let i=0; i<word.length; i++){
      const letter = word[i];
      _trie[letter] = _trie[letter] || {};

      if (i == word.length - 1)
        _trie[letter]['leaf'] = true;

      _trie = _trie[letter];
    }
  }

  return trie;
}


function find(trie, str, i=0){
  const letter = str[i];
  
  if (!trie[letter])
    return false;
    
  if (trie[letter]['leaf'])
    return true;
    
  return find(trie[letter], str, i + 1);
}


const prefixes = [ "Hey", "Heya", "Hi", "Hola"];
const trie = buildTrie({}, prefixes)

console.log(trie)

console.log(find(trie, "Hey, I'm Michael"));
console.log(find(trie, "Heuy, I'm Michael"));

【讨论】:

    【解决方案2】:

    这与 גלעד ברקן 的 the answer 没有逻辑上的区别,但它以完全不同的代码风格显示使用特里树。 (它还使用$ 而不是leaf 作为终止符;符号将是一个不错的选择。)

    const trie = (words) => 
      words .reduce (insertWord, {}) 
    const insertWord = (trie, [c, ...cs]) => 
      c ? {...trie, [c]: insertWord (trie [c] || {}, cs)} : {...trie, $: 1}
    const hasPrefix = (trie) => ([c, ...cs]) =>
      '$' in trie ? true : c ? c in trie && hasPrefix (trie [c]) (cs) : true
    const testPrefixes = (prefixes) => 
      hasPrefix (trie (prefixes))
    
    const hasGreeting = testPrefixes (["Hey", "Hi", "Hola", "Howdy"])
    
    console .log (hasGreeting ("Hey, I'm Michael"))
    console .log (hasGreeting ("Hello, Michael. I'm Michelle"))
    
    console .log (trie ((["Hey", "Hi", "Hola", "Howdy"])))
    .as-console-wrapper {max-height: 100% !important; top: 0}

    testPrefixes 接受前缀列表并返回一个函数,该函数将报告字符串是否以这些前缀之一开头。它通过创建一个 trie 并将其部分应用于hasPrefix 来实现这一点。在内部,通过将 insertWord 折叠到初始空对象上来构建 trie。

    当然,这仅在您的用例具有可用于多次调用的前缀时才有意义。如果不是,我觉得比const testPrefixes = (prefixes) =&gt; (word) =&gt; prefixes .some ((pfx) =&gt; word .startsWith (pfx))好一点

    【讨论】:

      【解决方案3】:

      要在字符串中搜索很多可能的子串,可以使用Rabin-Karp算法中的idea。

      在我的程序Banmoron 中,我使用此算法通过搜索子字符串选择恶意请求。请参阅github 上的来源。

      【讨论】:

        【解决方案4】:

        编辑:startsWith 比 indexOf 快是有道理的,我很难找到比较两者的基准,我确实找到的一个取决于浏览器速度,并且 chrome 运行 indexOf 比 startWith 快,我很想了解更多。以下是原答案:

        对于大多数这些情况,我最近对 ​​.indexOf 做了很多。根据我阅读的内容,性能优于大多数循环情况,并且易于使用,这里是两个函数的示例:

        1. findOne:返回找到的第一个条目(打破可以提高性能的循环)。
        2. findAll:遍历所有前缀并返回一个数组 找到前缀

        如果您是专门寻找前缀(因此索引值等于0,只需更改函数以表示它

        input.indexOf(prefixes[i]) === 0
        

        而不是

        input.indexOf(prefixes[i]) >= 0
        

        这里是sn-p的代码:

        const exampleString = "Hello, I am Michael, bey?";
        const examplePrefixes = ["Hello", "Holla", "bey?"];
        
        function findOne(input, prefixes) {
          // Loop through prefixes to test if is in the string
          for (let i = 0; i < prefixes.length; i++) {
            // If the prefix does not exist in the string it returns a value of -1
            if (input.indexOf(prefixes[i]) >= 0) {
              // Retrun the prefix value if it is found
              return prefixes[i];
            }
          }
          // Return null if nothing is found
          return null
        }
        
        function findAll(input, prefixes) {
          // Initialize return array
          let retArr = [];
          // Loop through prefixes to test if is in the string
          for (let i = 0; i < prefixes.length; i++) {
            // If the prefix does not exist in the string it returns a value of -1
            if (input.indexOf(prefixes[i]) >= 0) {
              // If the prefix exists, push it onto the return array
              retArr.push(prefixes[i]);
            }
          }
          // return the array after looping through each prefix
          return retArr.length !==0 ?  retArr :  null
        }
        
        let value1 = findOne(exampleString, examplePrefixes);
        let value2 = findAll(exampleString, examplePrefixes);
        
        console.log(value1); // Hello
        console.log(value2); // [ 'Hello', 'bey?' ]
        

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-01-09
        • 1970-01-01
        • 2022-06-10
        • 1970-01-01
        • 1970-01-01
        • 2011-10-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多