How to count occurrences of a letter in a sentence?

Asked

Viewed 3,540 times

6

I’m trying to do an exercise whose goal is to know how many times a certain letter appears in the sentence, I was trying to do so:

var frase = "o homem é o lobo do homem";
var letra = "o";

for (var i = 0; i < frase.length; i++) {
  if (letra == frase) {}
}

I’m a beginner in Javascript, my doubt is how to relate the for with the if in case he has the lyrics.

4 answers

7

Make another variable to serve as "counter variable". In the case of the example below, I called quantidade, that each frase[i] if it equals letra will add +1, until you reach the final result.

Why [i] in square brackets?

Each letter in a string has an index and starting from the first letter that index is 0.

In the example, the string "o homem é o lobo do homem", the first o has the index 0, the index 1 is a , the index 2 is the h and so on.

That is to say frase[0] would be = o.

var frase = "o homem é o lobo do homem";
var letra = "o";
var quantidade = 0

for (var i = 0; i < frase.length; i++) {
  if (frase[i] == letra) {
    quantidade++
  }
}

document.write(quantidade)

5

Just to complement the other answers, there are some situations you should take care of, since there are surprises that only the Unicode brings to you :-)

Take the test below:

function comFilter(frase, letraProcurada) {
    return [...frase].filter(letra => letra === letraProcurada).length;
}

function comForEach(frase, letraProcurada) {
    var total = 0;
    [...frase].forEach(letra => {
        if (letra === letraProcurada)
            total++;
    });
    return total;
}

function comSplit(frase, letraProcurada) {
    return frase.split(letraProcurada).length - 1;
}

function comRegex(frase, letraProcurada) {
    var re = new RegExp(letraProcurada, "g");
    return [...frase.matchAll(re)].length;
}

function comFor(frase, letraProcurada) {
    var quantidade = 0;
    for (var i = 0; i < frase.length; i++) {
        if (frase[i] == letraProcurada) {
            quantidade++
        }
    }
    return quantidade;
}

function testar(frase, letraProcurada) {
    console.log(`procurando "${letraProcurada}" em "${frase}"`);
    for (const func of [comFilter, comForEach, comSplit, comRegex, comFor]) {
        console.log(`- ${func.name} = ${func(frase, letraProcurada)}`);
    }
}

testar('até é noé', 'é');
testar('até é noé', 'é');

Basically I took the solutions of the other answers and tested twice each - apparently with the same string, but note that the results are different (in the second test, all counts were zero).

This is because the second string is in NFD form. If you want to know more details about what this is, read here and here, but basically, letters accented as "is" can be represented in two ways: as the character itself "is" or as two characters: the "e" (without accent) and the " (the accent) - the first form is called NFC, and the second, NFD (read the links already indicated to better understand).

But visually you can’t tell the difference, because they’re both rendered the same way. And then there’s trouble when it comes to counting counts, because we loops, in the regex and in split, the second string - which is in NFD - considers that the "e" and the accent are separate characters, and therefore none of them is equal to the é, resulting in zero.

To work in these cases, an alternative is to normalize the strings to NFC:

function ocorrencias(frase, letraProcurada) {
    var qtd = 0;
    letraProcurada = letraProcurada.normalize('NFC');
    for (var letra of frase.normalize('NFC')) {
        if (letra === letraProcurada)
            qtd++;
    }
    return qtd;
}

And I also use the for...of, that already iterates by the string characters correctly.

In this way, it even works with letters from other alphabets, and also with emojis:

function comFilter(frase, letraProcurada) {
    return [...frase].filter(letra => letra === letraProcurada).length;
}

function comForEach(frase, letraProcurada) {
    var total = 0;
    [...frase].forEach(letra => {
        if (letra === letraProcurada)
            total++;
    });
    return total;
}

function comSplit(frase, letraProcurada) {
    return frase.split(letraProcurada).length - 1;
}

function comRegex(frase, letraProcurada) {
    var re = new RegExp(letraProcurada, "g");
    return [...frase.matchAll(re)].length;
}

function comFor(frase, letraProcurada) {
    var quantidade = 0;
    for (var i = 0; i < frase.length; i++) {
        if (frase[i] == letraProcurada) {
            quantidade++
        }
    }
    return quantidade;
}

function ocorrencias(frase, letraProcurada) {
    var qtd = 0;
    letraProcurada = letraProcurada.normalize('NFC');
    for (var letra of frase.normalize('NFC')) {
        if (letra === letraProcurada)
            qtd++;
    }
    return qtd;
}

function testar(frase, letraProcurada) {
    console.log(`procurando "${letraProcurada}" em "${frase}"`);
    for (const func of [comFilter, comForEach, comSplit, comRegex, comFor, ocorrencias]) {
        console.log(`- ${func.name} = ${func(frase, letraProcurada)}`);
    }
}

testar('até é noé', 'é'); // NFC
testar('até é noé', 'é'); // NFD
testar('abc  def  xyz', '');
testar('abc  def  xyz', '');

Note: with emojis will not work if they are a emoji ZWJ Quence, but we are already running too far from the scope of the question...

  • 1

    @Augustovasques If there’s time left I’ll do it later (but I have a hunch: think that my solution will be slower, since you need to normalize the string first).

  • 2

    You could also link this other question: https://answall.com/q/443744/69296 :D

  • 1

    @Augustovasques Tá lá: https://jsbench.me/9xkit4zsbp/1 (the simple was faster, regex was the worst - normalize was the second worst)

  • This link is valuable. Thank you.

4

An alternative solution is to transform the string in a array to use methods proper to the array as filter, reduce or forEach.

Basically you can do it using this way: [...frase].
The operator Spread (...) takes an object as a string and brackets ([]) turns into a array (read more here).

With this we can use the method filter, that will iterate for each "element" of array, that is, each letter, but with a syntax simpler than making a for or foreach, then we can "filter" only the letter we want, and count how many were found using length:

var frase = "o homem é o lobo do homem"
var letraProcurada = "o";
var total = [...frase].filter(letra => letra === letraProcurada).length;

console.log("total=" + total);

In practice the filter does something like [...frase].forEach(..... To illustrate, another example using forEach:

var frase = "o homem é o lobo do homem"
var total = 0;
var letraProcurada = "o";

[...frase].forEach(letra => { 
   if (letra === letraProcurada) total ++; 
});

console.log(total);

4

Native method can be used String.prototype.split() to count the occurrence of one string in another.

The method split([separator[, limit]]) divides a String in a array of Strings whose separator is the parameter separator which may be a String or a regular expression.
When found, the separator is removed from the string and the substrings are returned in an array.
If the separator does not occur or is omitted, the array will contain an element consisting of the entire string.
Which implies that the array length returned by split() will always be the number of occurrences of the tab plus one:

let frase = prompt("Digite uma frase.").normalize("NFC");
let letra = prompt("Digite letra ou texto para contar as ocorrências.").normalize("NFC");

   
console.log(frase.split(letra).length - 1);

Another possibility is create a regular expression with a matching pattern as separator and get a iterator containing all correspondence with the method String.prototype.matchAll(), then spread it in an array and only get the size of that array:

let frase = prompt("Digite uma frase.").normalize("NFC");
let letra = prompt("Digite letra ou texto para contar as ocorrências.").normalize("NFC");


re = new RegExp(letra, "g");

console.log([...frase.matchAll(re)].length);

EDIT:

In both fragments were added codes that put the input strings in the Canonical Composition Normalization Format. According to user orientation Hkotsubo that warns that the visual equivalence of UNICODE strings may harbor a discrepancy in codepoints of its characters which would lead any untreated comparison to an erroneous count of occurrences.
To correct this possible discrepancy the method was used String.prototype.normalize().

Browser other questions tagged

You are not signed in. Login or sign up in order to post.