skip to Main Content

I’ve created a function in order make the first character of every word in a string in capital letters.
It works fine, just for the second letter whose index is 1 -> the character is ignored.

Can you correct it?
Is there any way to improve it?

const str = "hello stackoverflow community";


function afterSpaceUpper(str){
  
  let a = "";
  
  for(let i = 0; i<str.length-1; i++){
    
    if(i == 0 ){
      a += str[i].toUpperCase();
    } else if(str[i] === " "){
     a += str[i+1].toUpperCase();
    } else{
      a += str[i+1]
    }  
    
  }
  
  return a;
  
}

console.log(afterSpaceUpper(str));

7

Answers


  1. As mentioned in the comments, your code isn’t done in the best way, indeed, but I’ve fixed the issue, the second letter whose index is 1 isn’t ignored.

    const str = "hello stackoverflow community";
    
    function afterSpaceUpper(str) {
        let a = str[0].toUpperCase(); // Start with the first character capitalized
    
        for (let i = 1; i < str.length; i++) {
            if (str[i - 1] === " ") {
                // Check the previous character
                a += str[i].toUpperCase();
            } else {
                a += str[i];
            }
        }
    
        return a;
    }
    
    console.log(afterSpaceUpper(str));

    Start the loop from the second character (whose index is 1) and check the preceding character using str[i - 1] to see if it’s a space or not. And, always capitalize the first character.

    Login or Signup to reply.
  2. Plz have a look, the below code snippet will work optimally for all of the use cases

    const str = "hello stackoverflow community";
    
    function afterSpaceUpper(str) {
        let a = '';
    
        for (let i = 0; i < str.length; i++) {
            // CONDITION 1: str[i] is always from a-z so that we can perform operation lowercase to uppercase
          
            // CONDITION 2:  if any of the below condition is true then do transformation lowercase to uppercase (even we have also check if the character is already in uppercase then no need to perform operation )
          
            // as per you use case we only have to transform character just after space so have to check if str[i] === ' '
            // edge case if the input string start with any character a-z so for that check i == 0
            if (
                (str[i] > 'a' && str[i] < 'z') &&
                (i == 0 || str[i - 1] == ' ')
            ) {
    
                a += str[i].toUpperCase();
            } else {
                a += str[i];
            }
        }
    
        return a;
    }
    
    console.log(afterSpaceUpper(str));
    Login or Signup to reply.
  3. I’ve cleaned your function a bit. Let’s see how to fix your issue.

    1. Check if str is empty/null/undefined or not; if it is then return an empty string; otherwise continue with rest of the function.
    2. Uppercase the first char: str[0].toUpperCase();
    3. Then loop over your string. As the first character is not a space it will just be added to the new_str, but when you encounter a space add the following character as uppercase (str[i + 1].toUpperCase()).

    This fixed all your issues and also made your code cleaner and more readable.

    const str = "hello stackoverflow community";
    
    function afterSpaceUpper(str) {
      let new_str = "";
      if (!str) return "";
    
      new_str += str[0].toUpperCase();
      for (let i = 0; i < str.length - 1; i++) {
        if (str[i] === " ") {
          new_str += str[i + 1].toUpperCase();
          continue;
        }
    
        new_str += str[i + 1];
      }
    
      return new_str;
    }
    
    console.log(afterSpaceUpper(str));
    Login or Signup to reply.
  4. Of course a will have one character less than str. The loop runs from i = 0 to str.length - 2, that is str.length - 1 characters appended to the empty string a.

    The reason it is the second character is you append character at index 0 as uppercase. Then in all sub sequent iterations you append the character with index i+1. Therefore on the second iteration ( i = 1 ) you append character at index i+1 ( = 2 ), which is the third character, skipping over the second character at index 1.

    A quick fix would be starting at i = -1 and add all the i + 1 characters to a. After checking for the character at index i.

    Following is the modified code.

    const str = "hello stackoverflow community";
    
    
    function afterSpaceUpper(str){
      
      let a = "";
      
      for(let i = -1; i<str.length-1; i++){     // Start at i = -1
        
        if(i == -1 ){                           // Check first iteration
          a += str[i+1].toUpperCase();          // Capitalize first character
        } else if(str[i] === " "){
         a += str[i+1].toUpperCase();
        } else{
          a += str[i+1]
        }  
        
      }
      
      return a;
      
    }
    
    console.log(afterSpaceUpper(str));
    Login or Signup to reply.
  5. Try to avoid looping through characters when possible, and use indexOf() to navigate through a string:

    const str = "hello stackoverflow community";
    
    function afterSpaceUpper(str){
    
      if(!str.length){
        return '';
      }  
    
      let a = '', prev = 0, idx;
      do {
        a += str[prev++].toUpperCase()
        // find the next space, avoid wasted looping
        idx = str.indexOf(' ', prev);
        // add the whole word without looping with the closing space if exists
        a += str.slice(prev, idx === -1 ? str.length : idx + 1);
        // find the space from this position the next cycle
        prev = idx + 1; 
      } while (idx >= 0);
      return a;
      
    }
    [str, '', ' ', 'a', 'a b'].forEach(str => console.log(JSON.stringify(afterSpaceUpper(str))));

    Also possible with a regexp:

    const str = "hello stackoverflow community";
    
    console.log(str.replace(/(?<=^|s)./g, s => s.toUpperCase()));

    And a benchmark:

    enter image description here

    <script benchmark data-count="1000000">
    
    const str = "hello stackoverflow community";
    
    // @benchmark Darryl Noakes
    function afterSpaceUpper4(str) {
      return str
        .split(" ")
        .map((part) => (part[0]?.toUpperCase() ?? "") + part.slice(1))
        .join(" ");
    }
    
    // @run
    afterSpaceUpper4(str);
    
    
    // @benchmark original (fixed by Sally)
    
    function afterSpaceUpper3(str) {
        let a = str[0].toUpperCase(); // Start with the first character capitalized
    
        for (let i = 1; i < str.length; i++) {
            if (str[i - 1] === " ") {
                // Check the previous character
                a += str[i].toUpperCase();
            } else {
                a += str[i];
            }
        }
    
        return a;
    }
    
    // @run
    afterSpaceUpper3(str);
    
    // @benchmark Wasit Shafi
    function afterSpaceUpper2(str) {
        let a = '';
    
        for (let i = 0; i < str.length; i++) {
            if (
                ((str[i] > 'a' && str[i] < 'z') || (str[i] > 'A' && str[i] < 'Z')) &&
                (i == 0 || str[i - 1] == ' ')
            ) {
                a += str[i].toUpperCase();
            } else {
                a += str[i];
            }
        }
    
        return a;
    }
    
    // @run
    afterSpaceUpper2(str);
    
    // @benchmark Christian regexp
    const regexp2 = /(b|s)w/g;
    
    // @run
    str.replace(regexp2, (x) => x.toUpperCase());
    
    // @benchmark Alexander regexp
    const regexp = /^|s./g;
    
    // @run
    str.replace(regexp, s => s.toUpperCase())
    
    // @benchmark Alexander
    function afterSpaceUpper(str){
      if(!str.length){
        return '';
      }  
      let a = '', prev = 0, idx;
      do {
        a += str[prev++].toUpperCase();
        idx = str.indexOf(' ', prev);
        a += str.slice(prev, idx === -1 ? str.length : idx + 1);
        prev = idx + 1;
      } while (idx >= 0);
      return a;
      
    }
    // @run
    afterSpaceUpper(str);
    
    </script>
    <script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>
    Login or Signup to reply.
  6. This is how I made.

    First, split the string.

    var rep = str.split(' ');
    

    Then do the loop.

    for (var i = 0; i < rep.length; i++) {
       rep[i] = rep[i].charAt(0).toUpperCase() + rep[i].slice(1);
    }
    

    Add new variable then join them and replace the spaces to new line.

    var cap = rep.join(' ').replaceAll(' ','<br>');
    

    Final:

    const str = "hello stackoverflow community";
    
    function afterSpaceUpper(str){
    
    var rep = str.split(' ');
    for (var i = 0; i < rep.length; i++) {
        rep[i] = rep[i].charAt(0).toUpperCase() + rep[i].slice(1);
     }
    var cap = rep.join(' ').replaceAll(' ','<br>');
    
    return cap;
    }
    
    console.log(afterSpaceUpper(str))
    
    
    Login or Signup to reply.
  7. Fixing the Bugs

    The issue you are encountering is only the first that shows itself, and off-by-one error. I’ll go through fixing it step by step.

    So, this is section of code we’re interested in:

    for (let i = 0; i < str.length - 1; i++) {
      if (i == 0) {
        a += str[i].toUpperCase();
      } else if (str[i] === " ") {
        a += str[i + 1].toUpperCase();
      } else {
        a += str[i + 1];
      }
    }
    

    Given the string "hello world", it returns "Hllo World".

    Let’s run through the algorithm ourselves:

    1. i is 0, so we add the first character of the string (in uppercase). a is now "H".
    2. i is now 1, so we skip the first branch. str[i] is "e", so we skip the second branch. However, you now have a += str[i + 1]; because of that + 1, this second letter is skipped. a is now "Hl".
    3. i is now 2, so we skip the first branch. str[i] is "l", so we skip the second branch. a += str[i + 1] adds the next letter, the second "l". a is now "Hll".

    As Christian Vincenzo Traina summarized, the characters appended are: i = 0 -> str[0], i = 1 -> str[2], i = 2 -> str[3].

    As you can probably see, there is an off-by-one error. The offset only causes an issue for the second letter; after that, all the letters are offset and it is fine from there on.

    So, let’s remove that + 1 and see what we get: "HelloWworl".
    Okay, the space is being replaced by the uppercased letter, and the final letter of the string is missing.

    The missing letter is because the loop stops when i < str.length - 1 is false. Thus, when i reaches the index of the last letter, it is equal to str.length and the condition is false. The reason this wasn’t a problem before is because of the off-by-one issue.
    We can fix it by simply changing the condition to i < str.length.

    The space/duplication is because the space-handling branch simply adds the character after the space it’s found. It should instead add the space, add the character after, and then increment i to account for already handling the next character. Like so:

    a += " ";
    a += str[i + 1].toUpperCase();
    i += 1;
    

    It worked before because the previous iteration had added the space already, due to the off-by-one issue.

    The output is now correct: "Hello World". Tada!


    The above still has a bug: if the last character is a space, it will try to read beyond the end of the string, causing a TypeError.
    The simple fix now would be to check if that next character exists, such as by using a += str[i + 1]?.toUpperCase() (optional chaining).

    My preferred method is to detect a space before the current letter:

    if (i == 0) {
      a += str[i].toUpperCase();
    } else if (str[i - 1] === " ") {
      a += str[i].toUpperCase();
    } else {
      a += str[i];
    }
    

    Or, alternatively:

    if (i == 0) {
      a += str[i].toUpperCase();
    } else {
      a += str[i - 1] === " " ? str[i].toUpperCase() : str[i];
    }
    

    The i == 0 branch can be merged with the other space-handling one, as they do the same thing:

    if (i == 0 || str[i - 1] === " ") {
      a += str[i].toUpperCase();
    } else {
      a += str[i];
    }
    

    It can further be "simplified" (re amount of code, not understanding) by the fact (assumption, really) that the string will not have negative indices, so the i == 0 check can be removed by flipping the other comparison:

    // If i == 0, then str[i - 1] == undefined, which is not equal to " ".
    if (str[i - 1] !== " ") {
      a += str[i];
    } else {
      a += str[i].toUpperCase();
    }
    

    or

    a += str[i - 1] !== " " ? str[i] : str[i].toUpperCase()
    

    This last "improvement" is nullified by the change in the next section (it completely removes the need for it).

    An Improvement

    Using an improvement suggested by Sally loves Lightning, you can start the loop on the second character, which could be considered to simplify things:

    function afterSpaceUpper(str) {
      if (!str.length) return "";
    
      let a = str[0].toUpperCase();
    
      for (let i = 1; i < str.length; i++) {
        if (str[i - 1] === " ") {
          a += str[i].toUpperCase();
        } else {
          a += str[i];
        }
    
        // Or, alternatively:
        // a += str[i - 1] === " " ? str[i].toUpperCase() : str[i];
      }
    
      return a;
    }
    

    The length check is to handle empty strings, which this change would otherwise crash on.

    The result is the same as the one given by Sally loves Lightning (except for the handling of empty strings):

    const str = "hello stackoverflow community";
    
    function afterSpaceUpper(str) {
      if (!str.length) return "";
    
      let a = str[0].toUpperCase();
    
      for (let i = 1; i < str.length; i++) {
        if (str[i - 1] === " ") {
          a += str[i].toUpperCase();
        } else {
          a += str[i];
        }
    
        // Or, alternatively:
        // a += str[i - 1] === " " ? str[i].toUpperCase() : str[i];
      }
    
      return a;
    }
    
    console.log(afterSpaceUpper(str));

    Different Approaches

    All this is very "imperative". We could use functional programming:

    function afterSpaceUpper(str) {
      return str
        .split(" ")
        .map((part) => (part[0]?.toUpperCase() ?? "") + part.slice(1))
        .join(" ");
    }
    
    console.log(afterSpaceUpper("hello stackoverflow community"));

    This splits on spaces, maps over each part to uppercase the first letter, and then joins them back together with spaces.
    The uppercasing part handles empty parts (where there were multiple spaces, or the string started or ended on a space) by defaulting to an empty string. The slice will return an empty string if there are less than two characters in the part.


    And then there’s regular expressions (as first suggested by Christian Vincenzo Traina)).

    This is the one I recommend: /(?:^|s)s*./g.
    It matches the start of the string or a space, any following spaces, and then a non-space character (by virtue of the spaces being already matched); it’s kind of a fake look-behind assertion that works in this case (uppercase spaces are just spaces).

    function afterSpaceUpper(str) {
      return str.replace(/(?:^|s)s*./g, (x) => x.toUpperCase());
    }
    
    console.log(afterSpaceUpper("hello stackoverflow community")});

    This is necessary to work correctly in edge cases, like leading spaces or multiple spaces.

    Word boundaries, as suggested by Christian Vincenzo Traina, match after characters besides spaces. This may or may not be unacceptable: "let’s" -> "Let’S", "hi-lo" -> "Hi-Lo".
    The following matches any non-space character after a boundary: /b[^s]/g.

    A look-behind assertion (as suggested by Alexander Nenashev) works and is the cleanest of all, but is not widely supported.
    The following matches any character that follows the start of the string or a space: /(?<=^|s)./g.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search