一道前端面试题

输入字符串'a(3)b(1)cde(2)f(0)',得到输出结果:'aaabcdee'。

其中括弧内的数字是括弧前面字母的重复次数;如果字母后面没有括弧,就原样输出(相当于重复1次);括弧内数字为0则不输出。

这道题其实非常简单,但是有多种解题思路,涉及的基本知识点也很多。下面我会按照我觉得由低到高的方式一一列举出来。

第一步当然是把字符串放到变量 str 里面,下文我们都会用这个变量:

let str = 'a(3)b(1)cde(2)f(0)';

1. 最基本的方法

最基本的方法就是遍历这个字符串。

如何遍历字符串?

字符串其实是有下标的,所以可以直接用for循环:

for (let i = 0; i < str.length; i++) {
    // 这里 str[i] 便可以拿到每个字符
}

抽象暂存数据

这个题目最重要的一步,是要在输出所需字符串之前,抽象出规则,暂存数据以备我们拼接成输出字符串。

这里我们把所需数据放到一个数组tempArr里面,遍历字符串时,遇到字母或数字就将其放到这个数组:

let tempArr = [];

tempArr 中,我们期望每一个元素的格式是这样的:

{
    charactor: 'a',
    repeat: 3
}

这样我们抽象出了一份数据,数据中包含了输出的字母 charactor 和需要重复的次数 repeat。

如何判断字母或数字?

这个字符串中,我们只要判断 str[i] 不等于 () 就好了,当然,为了安全,我们还是要校验字母和数字,大家一定可以想到用正则表达式,那如果不用正则呢?

校验字母就需要把26个字母和10个数字拼成字符,然后用 indexOf 来判断。

判断是不是数字,也可以用 isNaN,在给定字符串中,isNaN(str[i]) === false 则代表 i 位是数字。

当然,为了逻辑严谨,我们还要考虑括号前的字符可能不仅仅是字母怎么办。如果括弧内不是数字还要做容方案。

使用正则判断字母和数字使用 [a-z]\d,不再赘述。

开始遍历

上面我们已经写了一个for循环,为了让代码更为“优雅”,我们也可以将字符串分割为数组,然后使用 forEach

str.split('').forEach((el, i) => {
    // 声明一个本轮循环的变量
    let temp = {};
    if(/[a-z]/.test(str[i])) {
        // 如果是字母
        // 将字母放入抽象数据
        temp.charactor = str[i];
        if (str[i+1] === '(') {
            // 判断字母后面的字符是什么
            // 如果字母后面是左括弧,那重复次数就是该字母后2位
            temp.repeat = parseInt(str[i+2]);
        } else {
            // 字母后面不是左括弧,那就是省掉次数的字母(或最后一位是字母),如给定字符串中的 c、d,这时候其实 repeat 就是 1
            temp.repeat = 1;
        }
        // 把本轮抽象出来的数据push到上文已声明的数组
        tempArr.push(temp);
    }
    // 如果不是字母(括弧或数字)不做任何处理继续往后遍历
});

通过上面的处理,我们就可以得到这样的一个数据:

[
    { charactor: 'a', repeat: 3 }
    { charactor: 'b', repeat: 1 }
    { charactor: 'c', repeat: 1 }
    { charactor: 'd', repeat: 1 }
    { charactor: 'e', repeat: 2 }
    { charactor: 'f', repeat: 0 }
]

我们只需要循环上面的数组,就可以得到需要输出的字符串了。

一些脑洞

多余的括弧

我们其实可以发现,给定字符串中的左右括弧,完全是干扰项。我们可以第一步就把这些括弧去掉,把给定字符串由 a(3)b(1)cde(2)f(0) 变为 a3b1cde2f0,这会让给定字符串看起来更清晰,但是用处不大。

使用map()

我们抽象出一个数据结构之后,再拼装为所需要输出的字符串。这一步没有很复杂,所以我们也可以放在循环里面,然后使用 map 替换 forEach 来处理,最后通过 join() 转为输出字符串:

let newStr = str.split('').map((el, i) => {
    let tempStr = '';
    if(/[a-z]/.test(el)) {
        if (str[i+1] === '(') {
            for (let j = 0; j < parseInt(str[i+2]); j++) {
                // 根据数字循环字母
                tempStr += el;
            }
        } else {
            tempStr = el;
        }
    }
    return tempStr;
}).join('');
console.log(newStr);

2. 更妙的方法

这个题目更好的方式是用正则表达式,从一个字符串到另一个字符串,我们只需要用到字符串的 replace 方法。

这其中最关键的点,是如何用正则匹配出一组一组的数据,在给定字符串中,可以划分为:'a(3)','b(2)','c','d'……这样的几组,需要根据这些写出一个匹配的正则表达式:/(\w)\((\d)\)/

把正则表达式作为第一个参数给 replace,第二个参数使用一个处理函数返回需要的内容即可。这样我们只需要1行代码来解决这个问题,大家可以体会下:

'a(3)b(1)cde(2)f(0)'.replace(/(\w)\((\d)\)/g, (match, $1, $2) => Array(+$2).fill($1).join(''));

这一行中包括了:正则 replace、数组长度、字符串转数字、fill()、join() 等知识点。

您的赞助将会支持作者创作及本站运维

发表评论


TOP