-2

lets say the input is

let word = 'I Lo1ve Co4ding'

the output would be I Loove Coooooding

so repeating x letters after putting a number after

Still don't understand how it works and how to replace it mid string.

PM 77-1
  • 12,933
  • 21
  • 68
  • 111
  • find the index of the number. Find previous character and repeat it. Can be done with a replace() and function. – epascarello Jan 20 '23 at 17:49
  • 2
    Note that you cannot *change* the string; you'll have to make a new string. – Pointy Jan 20 '23 at 17:50
  • 3
    *"and how to replace it mid string"* - Don't. Create a new string by looping over the characters in the existing one and performing logic when one of those characters is a number. (Maybe creating an array of characters along the way and then combining them into one resulting string afterward.) – David Jan 20 '23 at 17:50
  • Use the available [`String`](//developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String#instance_methods) and [`RegExp`](//developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/RegExp#instance_methods) methods. – Sebastian Simon Jan 20 '23 at 17:57
  • Does this answer your question? [Repeat a string in JavaScript a number of times](https://stackoverflow.com/questions/1877475/repeat-a-string-in-javascript-a-number-of-times) – Heretic Monkey Jan 20 '23 at 18:34

2 Answers2

4

You can use the callback function argument in String.prototype.replace, and call String.prototype.repeat on the first matching group (letter) and pass the second matching group (number) plus 1.

const expandStr = (str) =>
  str.replace(/([a-z])(\d+)/gi, (g, g1, g2) => g1.repeat(+g2 + 1));

console.log(expandStr('I Lo1ve Co4ding'));

Caveat

As suggested in the comments, you may use the following:

/(\p{L})(\p{N}+)/gu

In place of:

/([a-z])(\d+)/gi

Explanation:

  • \p{L} – matches a single code point in the category "letter"
  • \p{N} – matches any kind of numeric character in any script
  • \p{S} – symbols e.g. emoji

const expandStr = (str) =>
  str.replace(/(\p{L}|\p{S})(\p{N}+)/gu, (g, g1, g2) => g1.repeat(+g2 + 1));

console.log(expandStr('I Lo1ve Co4ding'));
console.log(expandStr('I give ️ I saw 1!')); // Works with emoji

Please refer to "Unicode Categories" to learn more.

Alternative pattern: /([^(\p{N}|\s)])(\p{N}+)/gu

Tokenizing

Here is a more traditional example that incorporates loops. It does not use regular expressions, but follows a tokenizer (parser) approach.

Note: Keep in mind that this naïve example does not account for Unicode.

const
  NUMBER_RANGE_START         = '0' .charCodeAt(0), //  48
  NUMBER_RANGE_END           = '9' .charCodeAt(0), //  57
  LETTER_UPPER_RANGE_START   = 'A' .charCodeAt(0), //  65
  LETTER_UPPER_RANGE_END     = 'Z' .charCodeAt(0), //  90
  LETTER_LOWER_RANGE_START   = 'a' .charCodeAt(0), //  97
  LETTER_LOWER_RANGE_END     = 'z' .charCodeAt(0), // 122
  WHITESPACE_CARRIAGE_RETURN = '\r'.charCodeAt(0), //  13
  WHITESPACE_LINE_FEED       = '\n'.charCodeAt(0), //  10
  WHITESPACE_SPACE           = ' ' .charCodeAt(0), //  32
  WHITESPACE_TAB             = '\t'.charCodeAt(0); //  9


const codeInRange = (code, start, end) => code >= start && code <= end;

const charInRange = (char, start, end) => codeInRange(char.charCodeAt(0), start, end);

const isNumber = (char) =>
  charInRange(char, NUMBER_RANGE_START, NUMBER_RANGE_END);

const isUpperLetter = (char) =>
  charInRange(char, LETTER_UPPER_RANGE_START, LETTER_UPPER_RANGE_END);

const isLowerLetter = (char) =>
  charInRange(char, LETTER_LOWER_RANGE_START, LETTER_LOWER_RANGE_END);

const isLetter = (char) => isLowerLetter(char) || isUpperLetter(char);

const isWhiteSpace = (char) => {
  switch (char.charCodeAt(0)) {
    case WHITESPACE_CARRIAGE_RETURN:
    case WHITESPACE_LINE_FEED:
    case WHITESPACE_SPACE:
    case WHITESPACE_TAB:
      return true;
    default:
      return false;
  }
};

const expandStr = (str) => {
  const result = [];
  let index, char, prevChar, count, step;
  for (index = 0; index < str.length; index++) {
    char = str[index];
    if (
      isNumber(char) &&
      (
        isLetter(prevChar) ||
        (
          !isWhiteSpace(prevChar) &&
          !isNumber(prevChar)
        )
      )
    ) {
      count = parseInt(char, 10);
      for (step = 0; step < count; step++) {
        result.push(prevChar);
      }
    } else {
      result.push(char);
    }
    prevChar = char;
  }
  return result.join('');
};

console.log(expandStr('I Lo1ve Co4ding')); // I Loove Coooooding
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
  • Thank you. I want to ask where does the d+ and the gi came from. Is it just for grouping? – Rizky Amanatama Jan 20 '23 at 18:07
  • @RizkyAmanatama the `\d` portion of the regex is a short-hand for `[0-9]` (any number). The `gi` portion are expression flags. The `g` means global (match all occurrences) and the `i` means that the expression is case-insensitive. See: [_Advanced searching with flags_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags) – Mr. Polywhirl Jan 20 '23 at 18:09
  • 2
    @RizkyAmanatama See [Reference - What does this regex mean?](/q/22937618/4642212), the [guide on MDN](//developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions), and the [regex tag wiki](/tags/regex/info) and use regex debuggers like [RegEx101](//regex101.com). – Sebastian Simon Jan 20 '23 at 18:10
  • 1
    Consider some alternatives: `/(.)([0-9]+)/gsu` for any symbol, not just letters from `a` to `z`; `/(\p{L})([0-9]+)/gu` for only letters, but not restricted to the ones from `a` to `z`. – Sebastian Simon Jan 20 '23 at 18:14
0

A little bit longer way as from Mr.Polywhirl. But I think foreach loops make for good readability and you will see how it works.

const word = 'I Lo1ve Co4ding'

function rewrite(w) {
  const arr = [...w];
  let n = [];
  arr.forEach((s,i) => {
    if(! isNaN(s) && s != ' ') {
      n.push(arr[i-1].repeat(s));
    } else {
      n.push(s);  
    }
  });
  return n.join('');
}
console.log( rewrite(word) );
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Maik Lowrey
  • 15,957
  • 6
  • 40
  • 79