FreeCodeCamp 中级算法题 - 句中查找替换

句中查找替换 (Search and Replace)

题目链接

问题解释

  • 这个 function 接收三个字符串参数。第一个参数 str 为原字符串,第二个参数 before 为需要替换的部分,第三个参数 after 为替换后的内容。返回值为按规则替换后的字符串
  • 举个例子,如果第一个参数是 "His name is Tom",第二个参数是 "Tom",第三个参数是 "john",那么返回值应为 "His name is John"
  • 需要注意题目的另一条要求,我们需要保持被替换部分的大小写格式。可能这就是本题出现在中级算法的原因吧

基本解法 - 分割成数组再合并

思路提示

  • 题目描述中,建议使用数组的 splicejoin 方法。尽管不是很必要,但这个写法可能是最容易上手的
  • 虽然第一个参数是一个句子,但句子中并不包含标点符号,因此我们可以直接通过空格来 split
  • 然后要做的,就是找到需要替换的单词的索引,并把它替换成我们想要的结果。题目中建议用 splice,其实直接通过赋值去修改这个数组也是可行的
  • 至于替换的目标字符串,首先我们需要根据原句中那个单词的大小写格式来转换第三个参数。尽管不确切,但我们可以暂时假定它只存在两种情况:全小写或首字母大写
  • 看答案之前,请自己先尝试一下。这道题的写法比较多,看看你能写出来几种

参考链接

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function myReplace(str, before, after) {
if (str.length === 0 || before.length === 0) {
return;
}

var strArr = str.split(" ");
// 找出第二个参数在数组中的索引
var srcIndex = strArr.indexOf(before);

// 判断首字母是否为大写
if (strArr[srcIndex][0] === strArr[srcIndex][0].toUpperCase()) {
after = after[0].toUpperCase() + after.slice(1);
}

strArr.splice(srcIndex, 1, after);

return strArr.join(" ");
}

解释

  • 养成一个好习惯,上来先判断边界。这道题目虽然不判断也没关系,但在实际开发的过程中,边界值检查很重要
  • 以上代码只是其中一种写法,其中可以替换的部分很多。比如:
    • 判断首字母大写的方式有很多,可以用正则,也可以用 charCodeAt 方法。上面的判断方式是,对于一个字符,如果转换大写后和原来的值相等,那么这个字符本身就是大写的
    • after 重新赋值那一行,也可以用 substrsubstring 方法来替代。这三个方法的比较,请参考Confirm the Ending 那道题目中的解释
    • splice 那一行,第二个参数为要向后删除的数量,第三个参数为可选参数,表示要添加进来的元素。也可以写成 strArr[srcIndex] = after
  • 多说一句,请记住,splicepush 是为数不多的,不返回操作后的数组的方法

另一种写法 - 使用字符串方法

思路提示

  • 个人认为,分割成数组是不必要的,因为我们完全可以通过字符串方法来实现
  • 在字符串中寻找某一个片段,我们可以直接使用字符串的 indexOf 方法。但在这道题目中,既然我们找到之后还要替换,那么完全可以使用 replace 方法
  • 既然使用了 replace,那么就需要用正则去匹配传入的 before 了。如果你不知道该怎么做,请先去 MDN 看看 RegExp() 构造器是如何使用的
  • 至于判断首字符是否为大写,你可能已经想到了,用正则也可以

参考链接

代码

1
2
3
4
5
6
7
8
9
10
11
12
function myReplace(str, before, after) {
if (str.length === 0 || before.length === 0) {
return;
}

return str.replace(RegExp(before), function(matched) {
if (/^[A-Z]/.test(matched)) {
return after[0].toUpperCase() + after.slice(1);
}
return after;
});
}

解释

  • 可能平时见的比较多的用法,是给 replace 的第二个参数传入一个字符串。但其实,传入一个回调函数也是没问题的。这个回调函数的第一个参数就是之前正则匹配到的部分
  • 函数的返回值是目标字符,也就是需要替换成的字符。因为,这个函数的返回值其实就是 replace 的第二个参数
  • 这里用到了逻辑短路的写法,其实这里执行的方式与在 return after 外面加上 else 是一样的。因为,如果 if 条件满足,那么就会直接执行里面的 return,直接得出返回值,而而不会执行 if 外面的代码

优化 - 更加严谨的解法

思路提示

  • 题目中的原话是”保持原单词的大小写”。因此并没有说大写字符一定会出现在第一位
  • 尽管这道题目的测试中也没有出现大写字符在其他位置的情况,但根据题目描述,更严谨的解释是”保持原单词中大小写字符的索引不变”
  • 那么这就引出了一个新的问题,如果原单词与替换的目标单词长度不同怎么办?我们可以这样分情况讨论:
    • 如果原单词比目标单词短,那么对应的索引保持大小写不变,其他部分采用目标字符的大小写格式
    • 如果原单词比目标单词长,那么依然是对应的索引保持大小写不变,忽略超出的部分
  • 有两种写法可以实现这个功能:
    • 利用字符串的 search 方法,找出所有的大写或小写的位置索引。然后把目标字符串先都转换成小写或大写,再根据找出的索引来调整。但正则效率本身不高,因此不是很推荐这个做法
    • 个人更推荐的是先分割字符串,然后用数组的 map 方法生成一个 Boolean 数组,再根据这个数组去调整目标字符串

参考链接

代码

ES6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function myReplace(str, before, after) {
if (str.length === 0 || before.length === 0) {
return;
}

// 大写为 true,小写为 false
var booleanArr = before.split("").map(e => e.toUpperCase() === e);
var afterArr = after.split("");

var formattedAfter = afterArr.map((str, index) => {
if (index < booleanArr.length) {
if (booleanArr[index]) {
return str.toUpperCase();
}
return str.toLowerCase();
}
return str;
}).join("");

return str.replace(RegExp(before), formattedAfter);
}

解释

  • 需要注意的是,转大小写的判断一定要在 index 小于 booleanArr 的前提下。如果大于等于,那么就表示传入的 after 参数比 before 参数长
  • map 方法返回的是数组,因此最后我们要把它 join 起来,这样才能得到字符串
  • 如果你有建议,或者更好的处理方式,请在下方评论留言
文章目录
  1. 1. 句中查找替换 (Search and Replace)
    1. 1.1. 题目链接
    2. 1.2. 问题解释
  2. 2. 基本解法 - 分割成数组再合并
    1. 2.1. 思路提示
    2. 2.2. 参考链接
    3. 2.3. 代码
    4. 2.4. 解释
  3. 3. 另一种写法 - 使用字符串方法
    1. 3.1. 思路提示
    2. 3.2. 参考链接
    3. 3.3. 代码
    4. 3.4. 解释
  4. 4. 优化 - 更加严谨的解法
    1. 4.1. 思路提示
    2. 4.2. 参考链接
    3. 4.3. 代码
      1. 4.3.1. ES6
    4. 4.4. 解释
,