【问题标题】:Case-insensitive string replace-all in JavaScript without a regex不使用正则表达式的 JavaScript 中不区分大小写的字符串全部替换
【发布时间】:2015-06-29 05:30:31
【问题描述】:

我想在 JavaScript 中做一个不区分大小写的字符串全部替换,而不使用正则表达式(或在调用 replace 方法时使用正则表达式样式的字符串)。我找不到这个问题或答案,但如果我错过了,请链接它。

例如,将“abc”替换为“x”:

Find aBc&def stuff ABCabc 变为 Find x&def stuff xx

结果应保留未替换部分的原始大小写。

字符串中可能包含特殊字符,所以这就是我避免使用正则表达式的原因。我的特殊问题可能可以通过正则表达式解决,但我有兴趣完全避免它。

有几个问题和答案使用正则表达式,包括特殊字符的处理。特别是,bobince 的回答 https://stackoverflow.com/a/280837/292060 描述了如果不知道或根据原始字符串中的特定条件采取行动,这是不可能的。

我认为这将涉及循环和 indexOf,并遍历原始字符串,构建结果。

为了这个问题,假设性能不是主要问题。例如,循环字符是可以的。

有一些现有问题包括所有答案的正则表达式:

编辑:
从一些答案,一些澄清 - 我最初没有指定这些,但它们是典型的搜索/替换行为:

可以替换为相同的字符串,例如,将 'abc' 替换为 'Abc',例如修复名称的标题大小写。

不应重新检查替换,例如,将 'ab' 替换为 'abc' 应该可以。例如,将 abcc 中的 'abc' 替换为 'ab' 变为 abc 而不是 ab

我认为这些归结为应该完成替换,然后在字符串中继续前进,而不是“回头看”。

编辑: 以下是一些测试用例,仅供参考。我没有进入空字符串等,这可能也应该得到测试。 https://jsfiddle.net/k364st09/1/

("Find aBc&def abc", "abc", "xy")   - Find xy&def xy - general test
("Find aBc&def abc", "abc", "ABC")  - Find ABC&def ABC - replace same test, avoid infinite loop
("Find aBcc&def abc", "abc", "ab")  - Find abc&def ab - "move on" avoid double checking (fails if abcc becomes ab)
("abc def", "abc", "xy")            - xy def - Don't drop last characters.
("abcc def", "abc", "xy")           - xyc def  - Just a mix of "move on" and "don't drop last".

【问题讨论】:

  • 到目前为止的答案很好,我想在选择答案之前先尝试一下。我想确保索引替换不同大小的替换。还有是否应该“重新检查”替换(可能不是,因为这在搜索/替换中并不常见)。
  • 如果我理解正确为什么您不想使用正则表达式(因为用户可以输入可能包含正则表达式字符的 anhthing),您可能会转义正则表达式字符。我知道 jquery-ui 在它的自动完成功能中做到了这一点。
  • 这主要来自 bobince 的回答stackoverflow.com/a/280837/292060 的讨论。这将是用户对搜索引擎的广泛输入,我无法就哪些字符可以允许,哪些字符不能达成共识。所以我只是想避开它。
  • 好吧,我继续制作了一个交互式版本,这样您就可以尝试一堆不同的字符串。它使用 indexOf 方法和转义的正则表达式替换向您显示结果。正则表达式代码稍微简单一些,但这将有助于查看您是否可以破解它。
  • 我去看看,谢谢。它可能“只是”一种练习,但它是一个很好的练习。

标签: javascript string replace case-insensitive


【解决方案1】:
  1. 以空字符串开头并复制原始字符串。
  2. 在副本中查找要替换的字符串的索引(将它们都设置为小写会使搜索不区分大小写)。
  3. 如果不在副本中,请跳至步骤 7。
  4. 添加从副本到索引的所有内容,以及替换。
  5. 将副本修剪到要替换的部分之后的所有内容。
  6. 返回步骤 2。
  7. 添加副本的剩余部分。

只是为了好玩,我创建了一个交互式版本,您可以在其中查看正则表达式和 indexOf 的结果,以查看转义正则表达式是否会破坏任何内容。用于转义我从 jQuery UI 获取的正则表达式的方法。如果您将它包含在页面上,可以使用$.ui.autocomplete.escapeRegex 找到它。否则,这是一个非常小的函数。

这是非正则表达式函数,但由于交互部分添加了更多代码,我默认隐藏了完整的代码 sn-p。

function insensitiveReplaceAll(original, find, replace) {
  var str = "",
    remainder = original,
    lowFind = find.toLowerCase(),
    idx;

  while ((idx = remainder.toLowerCase().indexOf(lowFind)) !== -1) {
    str += remainder.substr(0, idx) + replace;

    remainder = remainder.substr(idx + find.length);
  }

  return str + remainder;
}

// example call:
insensitiveReplaceAll("Find aBcc&def stuff ABCabc", "abc", "ab");

function insensitiveReplaceAll(original, find, replace) {
  var str = "",
    remainder = original,
    lowFind = find.toLowerCase(),
    idx;

  while ((idx = remainder.toLowerCase().indexOf(lowFind)) !== -1) {
    str += remainder.substr(0, idx) + replace;

    remainder = remainder.substr(idx + find.length);
  }

  return str + remainder;
}

function escapeRegex(value) {
  return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
}

function updateResult() {
  var original = document.getElementById("original").value || "",
    find = document.getElementById("find").value || "",
    replace = document.getElementById("replace").value || "",
    resultEl = document.getElementById("result"),
    regexEl = document.getElementById("regex");

  if (original && find && replace) {
    regexEl.value = original.replace(new RegExp(escapeRegex(find), "gi"), replace);
    resultEl.value = insensitiveReplaceAll(original, find, replace);
  } else {
    regexEl.value = "";
    resultEl.value = "";
  }


}

document.addEventListener("input", updateResult);
window.addEventListener("load", updateResult);
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />

<div class="input-group input-group-sm">
  <span class="input-group-addon">Original</span>
  <input class="form-control" id="original" value="Find aBcc&def stuff ABCabc" />
</div>

<div class="input-group input-group-sm">
  <span class="input-group-addon">Find</span>
  <input class="form-control" id="find" value="abc" />
</div>

<div class="input-group input-group-sm">
  <span class="input-group-addon">Replace</span>
  <input class="form-control" id="replace" value="ab" />
</div>

<div class="input-group input-group-sm">
  <span class="input-group-addon">Result w/o regex</span>
  <input disabled class="form-control" id="result" />
</div>

<div class="input-group input-group-sm">
  <span class="input-group-addon">Result w/ regex</span>
  <input disabled class="form-control" id="regex" />
</div>

【讨论】:

  • 我喜欢这个简单的显式前后替换,但我编辑了这个问题以避免“重新检查”。这可能只需要 indexOf 从它停止的地方开始。如果替换与原来的相同,这也将处理无限循环。
  • @goodeye,不错的收获。我已经解决了。还修复了另一个错误,它没有将find 字符串设置为小写,因此它并不是真的不区分大小写。如果toLowerCase 产生不同的字符(例如"ß".toUpperCase() === "SS"),也可能会遇到问题,但现在也已修复。
  • 感谢您的工作。我唯一可能在正则表达式上失败(这是一个延伸)是 html 的东西 - 例如,如果你有一个 标记,并且想用一些东西替换单词 body - 但这真的超出了这个范围.我认为在某些棘手的情况下,您想自己使用正则表达式字符串,但同样可以做到。所以,我同意正则表达式适用于所有实际情况,但感谢您对此的关注 - 我确实喜欢更好地避免它!
【解决方案2】:

批准的解决方案在循环内调用toLowerCase,效率不高。

以下是改进版:

function insensitiveReplaceAll(s, f, r) {
  const lcs=s.toLowerCase(), lcf = f.toLowerCase(), flen=f.length;
  let res='', pos=0, next=lcs.indexOf(lcf, pos);
  if (next===-1) return s;
  
  do {
    res+=s.substring(pos, next)+r;
    pos=next+flen;
  } while ((next=lcs.indexOf(lcf, pos)) !== -1);
  
  return res+s.substring(pos);
}

console.log(insensitiveReplaceAll("Find aBc&deF abcX", "abc", "xy"));
console.log(insensitiveReplaceAll("hello", "abc", "xy"));

使用 jsPerf 进行测试 - https://jsperf.com/replace-case-insensitive-2/1 - 显示速度提高了 37%。

【讨论】:

    【解决方案3】:
    var s="aBc&def stuff ABCabc"
    var idx = s.toUpperCase().indexOf("ABC");
    while(idx!==-1){
      s = s.substr(0,idx)+"x"+s.substr(idx+2);
      idx = s.toUpperCase().indexOf("ABC");
    }
    

    【讨论】:

    • 这是基本思想,但 +2 应该是 +3 (实际上并没有硬编码到这个确切的例子)。此外,如果替换与原始相同,则它具有无限循环。
    【解决方案4】:
    function replace(s, q, r) {
      var result = '';
      for (var i = 0; i < s.length; i++) {
        var j = 0;
        for (; j < q.length; j++) {
          if (s[i + j].toLowerCase() != q[j].toLowerCase()) break;
        }
        if (j == q.length) {
          i += q.length - 1;
          result += r;
        } else {
          result += s[i];
        }
      }
      return result;
    }
    

    函数接受参数:

    • s - 原始字符串
    • q - 搜索查询
    • r - 替换字符串(针对每个搜索查询实例)

      1. 它通过遍历每个位置来工作。

      2. 在每个位置,它都会尝试检查是否匹配(通过.toLowerCase() 不区分大小写)。

      3. 它找到的每个匹配项都会将替换字符串插入结果中。否则,它只是将不匹配的内容复制到结果中。

    【讨论】:

    • 这很接近,谢谢。我确实将问题编辑为不“重新检查”。由于这是使用索引遍历,我认为只需将索引移到替换位置即可。
    • 我不认为这种“重新检查”是因为它总是将搜索/替换基于原始字符串,当出现匹配时将索引移动到整个匹配之外。由于替换不会修改原始字符串,因此不应发生“重新检查”。
    • 我认为它很接近 - -1 添加到 i.我还不能在 jsfiddle 中得到它。例如,replace("Find ABCc def", "abc", "ab") 应该产生 Find abc,但产生 Find ab。我将列出各种测试用例。 (另外,abc def 正在删除最后两个字符)。
    • 更新为始终遍历整个源字符串。应该修复删除字符,因为如果不匹配,它将无法复制最后几个字符。
    • 感谢您的回答 - 我选择另一个只是为了简洁,但这一个呼吁索引计数。
    【解决方案5】:

    嗯,如果性能不是问题,您可能希望遍历字符串的字符以找到您想要的字符串进行替换。像这样的东西,也许......

    for (var x = 0; x < inputString.length-3; x++) {
        if (inputString.toLowerCase.substring(x, x+2) == 'abc') {
            inputString = inputString.substring(0, x-1) + 'x' + inputString.substring(x+3);
            x = x - 2 //because your replacement is shorter you need to back up where the index is
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-29
      • 1970-01-01
      • 2011-04-25
      相关资源
      最近更新 更多