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
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.Start the loop from the second character (whose index is
1
) and check the preceding character usingstr[i - 1]
to see if it’s a space or not. And, always capitalize the first character.Plz have a look, the below code snippet will work optimally for all of the use cases
I’ve cleaned your function a bit. Let’s see how to fix your issue.
str
is empty/null
/undefined
or not; if it is then return an empty string; otherwise continue with rest of the function.str[0].toUpperCase();
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.
Of course
a
will have one character less than str. The loop runs fromi = 0
tostr.length - 2
, that isstr.length - 1
characters appended to the empty stringa
.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 indexi+1
( =2
), which is the third character, skipping over the second character at index1
.A quick fix would be starting at
i = -1
and add all thei + 1
characters toa
. After checking for the character at indexi
.Following is the modified code.
Try to avoid looping through characters when possible, and use
indexOf()
to navigate through a string:Also possible with a regexp:
And a benchmark:
This is how I made.
First, split the string.
Then do the loop.
Add new variable then join them and replace the spaces to new line.
Final:
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:
Given the string
"hello world"
, it returns"Hllo World"
.Let’s run through the algorithm ourselves:
i
is 0, so we add the first character of the string (in uppercase).a
is now"H"
.i
is now 1, so we skip the first branch.str[i]
is"e"
, so we skip the second branch. However, you now havea += str[i + 1]
; because of that+ 1
, this second letter is skipped.a
is now"Hl"
.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, wheni
reaches the index of the last letter, it is equal tostr.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: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:
Or, alternatively:
The
i == 0
branch can be merged with the other space-handling one, as they do the same thing: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:or
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:
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):
Different Approaches
All this is very "imperative". We could use functional programming:
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).
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
.