Skip to content Skip to sidebar Skip to footer

Count Characters In Strings With Or Without String.slice

I am trying to learn how this problem has been solved but I am lost at string.slice(0,1) and i++ after that. What is the need to use the slice method? The questions is: Write a f

Solution 1:

Nina already solved it using your code, a shorter option would be using String.match

functioncountChars(string, character) {
  const { length } = string.match(newRegExp(character, 'ig')) || [];
  return length;
}

console.log(countChars('Foobar', 'o'));

Solution 2:

Explanation of your code:

functioncountChars(string, character) {
    let count = 0; // set variable count to zerolet i = 0; // set variable i to zerowhile (i < string.length) { // loop while i is less than lenght of stringif (string[i] === character) { // check character at position i 
            count++; // Increment count by one
        }
        string.slice(0, 1); // returns the first character in string, but return value is not used. Can be removed
        i++; // increment variable i by one
    }
    return count; // returns count
}

console.log("Count", countChars("ABCDABC", "A"));

string.slice(0, 1) The slice method extracts a section of a string and returns it as a new string, without modifying the original string. The return value is not used in this function, so you can remove the call. I guess that the person who wrote it tried to remove the first character of the string for each iteration of the loop. That is not a good way to solve the problem, for example because it creates one string per iteration and wastes memory.

i++ The increment operator ++ increments (adds one to) its operand and returns a value. There are two ways to use this operator: as pre increment ++i (returns the value before incrementing) or post increment i++ (returns the value after incrementing). Both variants are useful. When used on a single line (like in your example) it doesn't matter. You can also write it as i = i + 1 or using the addition assignmenti += 1.

let value = 0;
console.log( 'Pre increment: before %d, result %d, after %d', value, ++value, value); // before 0, result 1, after 1
value = 0;
console.log( 'Post increment: before %d, result %d, after %d', value, value++, value); //before 0, result 0, after 1

Using the while-statement is one way to create the loop that iterates over the string, using the for-statement is another, more compact way.

Example with while:

let i = 0; // initializationwhile ( i < string.length ) { // check contition// ... some code ...
  i += 1; 
}

Example with for:

for ( let i = 0; i < string.length; i += 1 ) {
  // ... some code ... 
}

The pros with for are: the initialization, condition and final expression is defined in the header. The scope of the i variable can with let be restricted to be defined only inside the for-loop.

Your example, but with a for-loop:

functioncountChars(string, character) {
        let count = 0; // set variable count to zerofor (let i = 0; i < string.length; i += 1) {
            if (string[i] === character) { // check character at position i 
                count++; // Increment count by one
            }
        }
        return count; // returns count
    }

console.log("Count", countChars("ABCDABC", "A"));

The code is buggy!

There is a bug with this solution, and it has to do with how characters are defined. In Javascript each position of the string is a 16-bit integer. It works for most text, but fails for emojis and other characters that aren't in the Unicode Basic Multilingual Plane since they are defined with two positions in the string (surrogate pairs).

A solution that works is to use the split-method, that splits a string object into an array of strings, using a specified separator string to determine where to make each split. Note that if the separator string isn't found, or if the input string is empty, an array of one string will be returned. If there is one match, the returned array will have two strings. So the split-method returns a value that is one more than what we want, but that is easy to fix by just subtracting one.

functioncountChars(string, character) {
  return string.split(character).length - 1;
}

console.log("Count", countChars("ABCD😃ABC😃", "😃"));

Optimizations

While “Premature optimization is the root of all evil”, there are some things that can be written in a clean and optimized way while you write the code. For example, if you are writing to a file, it is very inefficient to open the file, write something, and then close the file inside the loop:

for( i = 0; i < array.length; i + i = 1) {
  fs.openFile("log.txt").write(array[i]).close();
}

It should be no surprise that it is more efficient to first open the file, write inside the loop, and then close the file:

fh = fs.openFile("log.txt");
for( i = 0; i < array.length; i + i = 1) {
  fh.write(array[i]);
}
fh.close();

The reason I mention this, is because when I write a for-statement, I usually initialize both the variable and the length in the initialization part of the for-statement, since the length of the string doesn't change (and using the property string.length is always more expensive than using a local variable).

for (let i = 0, length = string.length; i < length; i += 1) {
  // ... code ...
}

Another thing: string[i] and string.charAt(i) creates a new string in every iteration of the loop. To compare a single character inside a loop, it is much faster to compare integers instead of strings, by using string.charCodeAt(i). And instead of string.charCodeAt(i) you can use string.codePointAt(i) to make it safe to use with all Unicode characters like emoji, not only the characters in the BMP.

As I said above, the method that you used isn't safe to use with Unicode. For example, if you search for an emoji (😃), you will not get the correct result.

The following two methods are safe to use with all Unicode codepoints. Both of them can handle a needle with multiple characters.

functioncount_split(haystack, needle) {
  return haystack.split(needle).length - 1;
}

This function uses a regular expression as the needle, and that can give unexpected results, since some characters have special meaning. For example, if you search for a dot (.) it will match every character. On the other hand, you can do an advanced search for '[ab]' that will match all 'a' and 'b' characters.

functioncount_match(haystack, needle) {
  const match = haystack.match(newRegExp(needle, 'g')) || [];
  return match.length;
}

The following method is safe to use with any Unicode, and the needle must be a single codepoint.

functioncountChars(haystack, needle) {
  let count = 0;
  const codePoint = needle.codePointAt(0);
  if (
    !((needle.length === 1) || (needle.length === 2 && codePoint > 0xffff))
  ) {
    // NOTE: Using charPointAt(0) returns the codePoint of the first character.//       Since the characters in the string is stored as 16-bit integers,//       characters outside of the Unicode BMP is stored as surrogate pairs//       in two positions. Thats why the length of the string is1 for//       a BMP codepoint and 2 for a codepoint outside of the BMP.//       codePointAt handles the decoding of the surrogate pair.throwError('needle should be one codepoint');
  }
  for (let i = 0, length = haystack.length; i < length; i += 1) {
    if (haystack.codePointAt(i) === codePoint) {
      count++;
    }
  }
  return count;
}

I have created test-cases on jsperf.com to compare the speed of the Unicode safe-functions mentioned above, and also the UNSAFE-functions that where published in this thread while I was writing this answer.

Solution 3:

Without slice

functioncountChars(string, character) {
    let count = 0;
    let i = 0;
    while (i < string.length) {
        if (string[i] === character) {
            count++;
        }
        i++;
    }
    return count;
}

console.log(countChars('foobar', 'o'))

With slice, but without i.

functioncountChars(string, character) {
    let count = 0;
    while (string.length) { // check if the length is not zeroif (string[0] === character) {
            count++;
        }
        string = string.slice(1); // take the rest of the string, beginning from index 1
    }
    return count;
}

console.log(countChars('foobar', 'o'))

Solution 4:

If you're willing to look for other solutions, here is a short one.

functioncount(str, char){   

   let count = str.split('').filter(c => {
     return c.toLowerCase() === char.toLowerCase()
   });

  return count.length;
}

console.log(count('hello world', 'l'));

Note: The above solution is case-insensitive.

Solution 5:

I would do it like this

functioncountChars(string, character) {
  return string.split('').filter(e => e === character).length;
}

console.log(countChars('foobar', 'o'))

But @bambam RegEx approach is likely the most efficient.

Post a Comment for "Count Characters In Strings With Or Without String.slice"