-3

I've this piece of code that will count the number of occurrences found in a given string.

But it will only count the number of unique special characters in the string.

How can I change this behavior ?

It should give me 3 and not 1 as I have 3 spaces.

Thanks.

var string = 'hello, i am blue.';
var specialChar = [' ', '!'];

let count = 0
specialChar.forEach(word => {
  string.includes(word) && count++
});

console.log(count);
poipoi
  • 29
  • 5
  • You should try something and ask a question if it doesn't work. The question is unclear as it stands. Also `string.includes(word) && count++` is usually avoided since`If (string.includes) {count++}` is much clearer – Ruan Mendes Jan 27 '23 at 20:16
  • I added what I'm expected. – poipoi Jan 27 '23 at 20:18
  • 1
    Put on your thinking cap and dive into regex : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions – This Guy Jan 27 '23 at 20:22
  • Does this answer your question? [Count the number of occurrences of a character in a string in Javascript](https://stackoverflow.com/questions/881085/count-the-number-of-occurrences-of-a-character-in-a-string-in-javascript) – pilchard Jan 27 '23 at 22:05
  • also from the docs for `String#indexOf()`: [Using indexOf() to count occurrences of a letter in a string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf#using_indexof_to_count_occurrences_of_a_letter_in_a_string) – pilchard Jan 27 '23 at 22:09

3 Answers3

4

What you are doing is iterating over specialChar, which yields two iterations: the first iteration will check if ' ' is included in the string which is true and thus increments count, and the second iteration will check if '!' is included in the string which is not the case hence you get 1.

What you should actually do is iterate through the string and check if each character is included in the specialChar array. Here is how you can do that with the minimum changes made (the code can be improved and made clearer).

Note: .split("") splits the string to an array of its characters.

var string = 'hello, i am blue.';
var specialChar = [' ', '!'];

let count = 0
string.split("").forEach(char => {
  specialChar.includes(char) && count++
});

console.log(count);
Ry-
  • 218,210
  • 55
  • 464
  • 476
  • 1
    Avoid the use of `String.split("")`. Try with `"abcd".split("")` and you'll understand why. Better to use Spread syntax `...` or `for..of` since those *will* retain the code points – Roko C. Buljan Jan 27 '23 at 20:52
  • 1
    Yes [...string] is better, I tried to stay as close to the original question as much as possible – Mohcine BAADI Jan 27 '23 at 20:59
1

One way to count characters in a string is to split the string by the character and then count the parts and subtract one.

var string = 'hello! i am blue!';
var specialChar = [' ', '!'];

let count = 0
specialChar.forEach(char => {
  count += string.split(char).length - 1
});

console.log(count);

Or using RegExp being sure to escape anything that is considered a special character.

function escapeRegex(v) {
  return v.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}

function countChars(str, chars) {
  const escapedChars = escapeRegex(chars.join(''));
  const regex = new RegExp(`[${escapedChars}]`, "g");
  return str.match(regex)?.length || 0;
}

console.log(countChars('hello! i am blue!', [' ', '!']));

The fastest version turns out to be one that counts the char in a word using indexOf

function countCharsIndexOf(str, char) {
    let num = 0;
    let pos = str.indexOf(char);
    while (pos > -1) {
        pos = str.indexOf(char, pos + 1);
        num++;
    }
    return num;
}

function countAllCharsIndexOf(str, chars) {
    return chars.reduce(
      (acc, char) => acc + countCharsIndexOf(str, char),
      0
    );  
}


console.log(countAllCharsIndexOf('hello! i am blue!', [' ', '!']));

enter image description here

Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • @RokoC.Buljan I didn't use `split("")`? What am I missing? `"o".split("o")` works fine. I know that faster != better but my point is that these are pretty easy to read and are also faster. – Ruan Mendes Jan 30 '23 at 22:04
  • (And even if you did use `.split("")`, it works fine for single-code-unit `specialChar` regardless of the input string.) – Ry- Jan 30 '23 at 22:07
  • Woops my bad. I missed the space in `' '` :\ – Roko C. Buljan Jan 30 '23 at 22:14
1

Since you're using an array of matches [" ", "!"] you need as an output - and Object with the counts, i.e: {" ": 5, "!": 2}.

Here's two examples, one using String.prototype.match(), and the other using Spread Syntax ... on a String

Using Match

and Array.prototype.reduce() to reduce your initial Array to an Object result

const string = 'hello, i am blue. And this is an Exclamation! Actually, two!';
const specialChar = [' ', '!'];

const regEscape = v => v.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');

const count = specialChar.reduce((ob, ch) => {
  ob[ch] = string.match(new RegExp(regEscape(ch), "g")).length;
  return ob; 
}, {}); // << This {} is the `ob` accumulator object

console.log(count);

Using String spread ...

to convert the string to an array of Unicode code-points sequences / symbols

const string = 'hello, i am blue. And this is an Exclamation! Actually, two!';
const specialChar = [' ', '!'];

const count = [...string].reduce((ob, ch) => {
  if (!specialChar.includes(ch)) return ob;
  ob[ch] ??= 0;
  ob[ch] += 1;
  return ob; 
}, {}); // << This {} is the `ob` accumulator object

console.log(count);
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • An object with the counts didn’t seem to be a requirement. `for…of` is better than string spread. That’s not the most efficient way to use regex. – Ry- Jan 27 '23 at 20:38
  • @Ry I never said it supported any requirement, just an obvious suggestion, given OP uses an array of characters. – Roko C. Buljan Jan 27 '23 at 20:40
  • @Ry- could you please justify your statement that String`...` spread is not appropriate versus the use of `for..of`? Is that related to engine internals implementations/performance like in i.e: V8? And is that relatable to the simple requirement? Thank you in advance for any clarification. – Roko C. Buljan Jan 27 '23 at 21:03
  • Making an array out of a string in order to iterate over it < iterating over the string, in terms of directness/clarity. Also, yes, [probably faster](https://jsben.ch/5yFtM). – Ry- Jan 27 '23 at 21:25
  • @Ry- Your tests show both approaches are comparable. They could get compiled to the same thing. I do agree `for of` is a lot clearer. However, the approaches I suggested (`RegExp` and `string.split()`) [are a lot faster](https://jsben.ch/QxmpZ). – Ruan Mendes Jan 30 '23 at 19:46
  • @RuanMendes: I know regex is faster, I tested it and the accepted answer [at the time](https://jsben.ch/SLV18) (you added it 50 minutes ago, btw). (`split` on the character to find varies by engine, performs better when special characters are few and sparse, and is beaten by `indexOf` in a loop which shares that characteristic.) – Ry- Jan 30 '23 at 20:00
  • @Ry- Added a version with `indexOf` to my answer and added it to the benchmark. https://jsben.ch/yqlH4 It's more twice as fast as `string.split(char)` to count the characters. – Ruan Mendes Jan 30 '23 at 22:35