0

Character Occurrences Count

Hello,

I'm wondering if there's a way to count how many times a given character is in a string. 

i.e.: I have the field 'mytext' which contains a string (let's say "rockandrollwillneverdie") and I want to find out how many times the letter "e" appears in the string (so the result should be "3".

36replies Oldest first
  • Oldest first
  • Newest first
  • Active threads
  • Popular
    • Lars
    • Lars
    • 4 mths ago
    • Reported - view

    Hi Gianluca,

    with length() you get the length of a string and use it for iteration. With substring() you can get a character from a string. Then you compare and Bob's your uncle:

    let mystring := "rockandrollwillneverdie";
    let found := 0
    for i from 0 to length(mystring) do
       if substring(mystring, i, i+1) = "e" then
          found := found + 1;
       end;
    end;
    alert(found)
    
    Like
    • Gianluca
    • Gianluca
    • 4 mths ago
    • Reported - view

    thank you Lars,

    it works perfectly, but this way i get the result in an alert pop-up. Can I get te same result as a number and use it in further calculations?

    Like
      • Lars
      • Lars
      • 4 mths ago
      • Reported - view

      Gianluca of course, instead of

      alert(found)

      you can use anything you want:

      found

      as last line just returns the value and displays it.

      myfield := found

      assigns the result to a field of your table

      let whateverCalculation := found * 3 + sqrt(9)

      would also be possible. After the last "end" you can do whatever you like with "found"

      Like 1
    • Gianluca
    • Gianluca
    • 4 mths ago
    • Reported - view

    Lars  Thank you so much! This was the missing piece of a longer script that i needed to bring into ninox from a filemaker custom function i used! Now I think i'll be able to finish it!

    Like 1
    • Lars
    • Lars
    • 4 mths ago
    • Reported - view

    And I forgot: if you need this more than once in your script, you can define it as a function:

    function countLetters(myString : text, myLetter : text) do
        let found := 0
        for i from 0 to length(myString) do
           if substring(myString, i, i+1) = myLetter then
              found := found + 1;
           end;
        end;
        found;
    end;
    
    let e := countLetters("rockandrollneverdies", "e");
    let d := countLetters("rockandrollneverdies", "d")
    

    The last two lines are just examples on how to use it. You need to define the function at the very beginning of your script.

    Like 1
      • Gianluca
      • Gianluca
      • 4 mths ago
      • Reported - view

      Lars Wonderful: now that's even better. If i define that in global field it will work through all the database, correct? 

      Alain Fontaine using regex was my first thought, but i am definitely a level lower than noob when it comes to using them :). Thank you. It's another great solution.

      John Halls what always amazes me is how many different solutions there are to a problem.

      Like
      • Lars
      • Lars
      • 4 mths ago
      • Reported - view

      Gianluca Yes, if you define it in the global options, it will be available everywhere in this database.

      However, I'd recommend Alain Fontaine's solution. You can use it as the function body in exactly the same way. Usually, regexes are efficiently programmed and faster than script code. I have some knowledge of regex but I didn't even think about using it for that - this is really awesome.

      Like 1
  • Another way to solve such a problem would be to harness the power of regular expressions, replacing the loop by:

    length(replacex(myString, "[^" + myLetter + "]", "g", ""))
    Like 3
      • John Halls
      • John_Halls
      • 4 mths ago
      • Reported - view

      Alain Fontaine I must learn some regex. I came up with

      let mystring := "rockandrollwillneverdie";
      let found := count(split("*"+mystring+"*","e"))-1;
      alert(found)
      

      Using the split function to replace the loop

      Like 1
    • John Halls Unfortunately, your solution fails if two occurences of the letter of interest are immediate neighbors. The reason is that this produces a null element in the array, and that the "count" function does not count null elements.

      More considerations about the regular expression solution:

      1- it can count occurences of several letters at once; for example, if you need to compute the number of occurences of "a", "e", "i", "o" and "u", just pass "aeiou" as the second parameter.

      2- in its simple form, it cannot look after "]" or "/"; a more elaborate form is needed if those characters must be counted:

      length(replacex(myString, "[^" + replacex(myLetter, "([\\\]])", "g", "\$1") + "]", "g", ""))
      Like
      • John Halls
      • John_Halls
      • 4 mths ago
      • Reported - view

      Alain Fontaine Thanks Alain. I caught the ones at the start and end but forgot about double letters. It's pushing the boundary of split's reason for being anyway but I was interested in finding a non-loop solution.

      Like
    • Gianluca
    • Gianluca
    • 4 mths ago
    • Reported - view

    Ok. Thanks to your suggestions i've finalized the script (which is about 211 lines long.

    I embedded Alain Fontaine  script in the function Lars  have created  and all works perfectly. Now I just wonder if there are ways to "clean" it and make it shorter or more tidy.

    For the brave ones who dare to read it i post it here.

    function countLetters(myString : text,myLetter : text) do
        length(replacex(myString, "[^" + myLetter + "]", "g", ""))
    end;
    let xPCog := replace(Cognome, " ", "");
    let xConCog := replacex(upper(xPCog), "A|E|I|O|U", "g", "");
    let xVocCog := replacex(upper(xPCog), "B|C|D|F|G|H|J|K|L|M|N|P|Q|R|S|T|V|W|X|Y|Z", "g", "");
    let xCog := xConCog + xVocCog;
    let xLunCog := length(xCog);
    let xPNom := replace(Nome, " ", "");
    let xConNom := replacex(upper(xPNom), "A|E|I|O|U", "g", "");
    let xVocNom := replacex(upper(xPNom), "B|C|D|F|G|H|J|K|L|M|N|P|Q|R|S|T|V|W|X|Y|Z", "g", "");
    let xNom := xConNom + xVocNom;
    let xLunNom := length(xNom);
    let cfparz := switch xLunCog do
        case 1:
            text(xConCog) + "XX"
        case 2:
            text(xConCog) + "X"
        case 3:
            text(xConCog)
        default:
            substring(text(xCog), 0, 3)
        end + switch xLunNom do
        case 1:
            text(xConNom) + "XX"
        case 2:
            text(xConNom) + "X"
        case 3:
            text(xNom)
        default:
            if length(text(xConNom)) > 3 then
                substring(text(xNom), 0, 1) + substring(text(xNom), 2, 4)
            else
                substring(text(xNom), 0, 3)
            end
        end + substring(text(year(DataNascita)), 2, 4) + switch text(month(DataNascita)) do
        case "1":
            "A"
        case "2":
            "B"
        case "3":
            "C"
        case "4":
            "D"
        case "5":
            "E"
        case "6":
            "H"
        case "7":
            "L"
        case "8":
            "M"
        case "9":
            "P"
        case "10":
            "R"
        case "11":
            "S"
        case "12":
            "T"
        default:
            ""
        end + switch Sesso do
        case 1:
            if length(text(day(DataNascita))) = 1 then
                "0" + text(day(DataNascita))
            else
                text(day(DataNascita))
            end
        case 2:
            text(day(DataNascita) + 40)
        default:
            ""
        end + (
            let xComune := text(ComuneNascita);
            (select Comuni where Comune = xComune).CodiceComune
        );
    let xCarDis := substr(cfparz, 0, 1) + substr(cfparz, 2, 1) + substr(cfparz, 4, 1) + substr(cfparz, 6, 1) + substr(cfparz, 8, 1) + substr(cfparz, 10, 1) + substr(cfparz, 12, 1) + substr(cfparz, 14, 1);
    let xCarPar := substr(cfparz, 1, 1) + substr(cfparz, 3, 1) + substr(cfparz, 5, 1) + substr(cfparz, 7, 1) + substr(cfparz, 9, 1) + substr(cfparz, 11, 1) + substr(cfparz, 13, 1);
    let a := countLetters(xCarPar, "A");
    let b := countLetters(xCarPar, "B");
    let c := countLetters(xCarPar, "C");
    let d := countLetters(xCarPar, "D");
    let e := countLetters(xCarPar, "E");
    let f := countLetters(xCarPar, "F");
    let g := countLetters(xCarPar, "G");
    let h := countLetters(xCarPar, "H");
    let i := countLetters(xCarPar, "I");
    let j := countLetters(xCarPar, "J");
    let k := countLetters(xCarPar, "K");
    let l := countLetters(xCarPar, "L");
    let m := countLetters(xCarPar, "M");
    let n := countLetters(xCarPar, "N");
    let o := countLetters(xCarPar, "O");
    let p := countLetters(xCarPar, "P");
    let q := countLetters(xCarPar, "Q");
    let r := countLetters(xCarPar, "R");
    let s := countLetters(xCarPar, "S");
    let t := countLetters(xCarPar, "T");
    let u := countLetters(xCarPar, "U");
    let v := countLetters(xCarPar, "V");
    let w := countLetters(xCarPar, "W");
    let x := countLetters(xCarPar, "X");
    let y := countLetters(xCarPar, "Y");
    let z := countLetters(xCarPar, "Z");
    let x0 := countLetters(xCarPar, "0");
    let x1 := countLetters(xCarPar, "1");
    let x2 := countLetters(xCarPar, "2");
    let x3 := countLetters(xCarPar, "3");
    let x4 := countLetters(xCarPar, "4");
    let x5 := countLetters(xCarPar, "5");
    let x6 := countLetters(xCarPar, "6");
    let x7 := countLetters(xCarPar, "7");
    let x8 := countLetters(xCarPar, "8");
    let x9 := countLetters(xCarPar, "9");
    let sumP := a * 0 + b * 1 + c * 2 + d * 3 + e * 4 + f * 5 + g * 6 + h * 7 + i * 8 + j * 9 + k * 10 + l * 11 + m * 12 + n * 13 + o * 14 + p * 15 + q * 16 + r * 17 + s * 18 + t * 19 + u * 20 + v * 21 + w * 22 + x * 23 + y * 24 + z * 25 + x0 * 0 + x1 * 1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 * 5 + x6 * 6 + x7 * 7 + x8 * 8 + x9 * 9;
    let aa := countLetters(xCarDis, "A");
    let bb := countLetters(xCarDis, "B");
    let cc := countLetters(xCarDis, "C");
    let dd := countLetters(xCarDis, "D");
    let ee := countLetters(xCarDis, "E");
    let ff := countLetters(xCarDis, "F");
    let gg := countLetters(xCarDis, "G");
    let hh := countLetters(xCarDis, "H");
    let ii := countLetters(xCarDis, "I");
    let jj := countLetters(xCarDis, "J");
    let kk := countLetters(xCarDis, "K");
    let ll := countLetters(xCarDis, "L");
    let mm := countLetters(xCarDis, "M");
    let nn := countLetters(xCarDis, "N");
    let oo := countLetters(xCarDis, "O");
    let pp := countLetters(xCarDis, "P");
    let qq := countLetters(xCarDis, "Q");
    let rr := countLetters(xCarDis, "R");
    let ss := countLetters(xCarDis, "S");
    let tt := countLetters(xCarDis, "T");
    let uu := countLetters(xCarDis, "U");
    let vv := countLetters(xCarDis, "V");
    let ww := countLetters(xCarDis, "W");
    let xx := countLetters(xCarDis, "X");
    let yy := countLetters(xCarDis, "Y");
    let zz := countLetters(xCarDis, "Z");
    let xx0 := countLetters(xCarDis, "0");
    let xx1 := countLetters(xCarDis, "1");
    let xx2 := countLetters(xCarDis, "2");
    let xx3 := countLetters(xCarDis, "3");
    let xx4 := countLetters(xCarDis, "4");
    let xx5 := countLetters(xCarDis, "5");
    let xx6 := countLetters(xCarDis, "6");
    let xx7 := countLetters(xCarDis, "7");
    let xx8 := countLetters(xCarDis, "8");
    let xx9 := countLetters(xCarDis, "9");
    let sumD := aa * 1 + bb * 0 + cc * 5 + dd * 7 + ee * 9 + ff * 13 + gg * 15 + hh * 17 + ii * 19 + jj * 21 + kk * 2 + ll * 4 + mm * 18 + nn * 20 + oo * 11 + pp * 3 + qq * 6 + rr * 8 + ss * 12 + tt * 14 + uu * 16 + vv * 10 + ww * 22 + xx * 25 + yy * 24 + zz * 23 + xx0 * 1 + xx1 * 0 + xx2 * 5 + xx3 * 7 + xx4 * 9 + xx5 * 13 + xx6 * 15 + xx7 * 17 + xx8 * 19 + xx9 * 21;
    let resto := ((sumP + sumD) / 26 - floor((sumP + sumD) / 26)) * 26;
    let finalC := switch text(round(resto)) do
        case 0:
            "A"
        case 1:
            "B"
        case 2:
            "C"
        case 3:
            "D"
        case 4:
            "E"
        case 5:
            "F"
        case 6:
            "G"
        case 7:
            "H"
        case 8:
            "I"
        case 9:
            "J"
        case 10:
            "K"
        case 11:
            "L"
        case 12:
            "M"
        case 13:
            "N"
        case 14:
            "O"
        case 15:
            "P"
        case 16:
            "Q"
        case 17:
            "R"
        case 18:
            "S"
        case 19:
            "T"
        case 20:
            "U"
        case 21:
            "V"
        case 22:
            "W"
        case 23:
            "X"
        case 24:
            "Y"
        case 25:
            "Z"
        default:
            ""
        end;
    cfparz + finalC
    
    Like
  • I did not try to understand it all, but I can see, for example, that the computation of "finalC" can be reduced to:

    let finalC := item("ABCDEFGHIJKLMNOPQRSTUVWXYZ", round(resto));
    

    The  same certainly applies to the definition of a letter to represent the month of "DataNascita".

    Like 1
      • Gianluca
      • Gianluca
      • 4 mths ago
      • Reported - view

      Alain Fontaine   I've corrected following your advice and it's already much better :)

      Merci beaucoup!

      Like
      • Lars
      • Lars
      • 4 mths ago
      • Reported - view

      Alain Fontaine Now I'm curious ... I thought that 'item' can only be applied to arrays and JSON. You are also doing this on a string. How is this possible or am I missing something?

      Like
    • Lars Indeed, item() also works on strings.

      Like 2
      • Lars
      • Lars
      • 4 mths ago
      • Reported - view

      RoSoft_Steven Rooryck  Thanks a lot - it isn't mentioned in the manual. That makes it a little bit nicer to extract a character than with substring(text, i, i+1).

      Like
  • Still digging into your script… This:

    if length(text(day(DataNascita))) = 1 then
                "0" + text(day(DataNascita))
            else
                text(day(DataNascita))
            end
    

    seems to give the same result as:

    format(day(DataNascita), "00")
    

    I tried to understand the first part of the script, but it would be easier to know the intent. Is it supposed to always produce a six letter code?

    Like
      • Gianluca
      • Gianluca
      • 4 mths ago
      • Reported - view

      Alain Fontaine yes, indeed.

      It's part of the algorithm used to calculate italian tax code, which implies the following rules:

      3 letters for the last name: must be the first 3 consonants. If there are not enough consonants, then u start using vowels, and if still some letters are missing, you place "X". Spaces have not to be considered (i.e Mac Donalds woule be macdonalds and letters extracted would be MCD)

      3 letters for the first name: same rules as above, but the consonants you need to extract are 1st, 3rd and 4th. If consonants are ≤ 3, then the rules are the same as for Last Name.

      Btw...I've noticed a mistake in this part of the script

      let cfparz := switch xLunCog do
          case 1:
              text(xConCog) + "XX"
          case 2:
              text(xConCog) + "X"
          case 3:
              text(xConCog)
          default:
              substring(text(xCog), 0, 3)
          end 

      where xConCog should be xCog. 

      Like
    • Lars
    • Lars
    • 4 mths ago
    • Reported - view

    With what I just learned, you can use

    item("ABCDEHLMPRST", number(month(DataNascita))-1)
    

    to replace

    switch text(month(DataNascita)) do
        case "1":
            "A"
        case "2":
            "B"
        case "3":
            "C"
        case "4":
            "D"
        case "5":
            "E"
        case "6":
            "H"
        case "7":
            "L"
        case "8":
            "M"
        case "9":
            "P"
        case "10":
            "R"
        case "11":
            "S"
        case "12":
            "T"
        default:
            ""
        end
    
    Like 1
      • Gianluca
      • Gianluca
      • 4 mths ago
      • Reported - view

      Lars yes....I did the same with a different trick.

      Since i need A to start counting from 1 and not from 0, i simply wrote

      item("0ABCDEHLMPRST", number(month(DataNascita))
      Like
    • Lars
    • Lars
    • 4 mths ago
    • Reported - view

    Next proposal - replace

    let cfparz := switch xLunCog do
        case 1:
            text(xConCog) + "XX"
        case 2:
            text(xConCog) + "X"
        case 3:
            text(xConCog)
        default:
            substring(text(xCog), 0, 3)
        end

    by

    rpad(text(xConCog), 3, "X")

     which just fills up xConCog with "X"es up to a total length of 3.

    I didn't fully understand the principle or the intent, maybe you can directly use 

    rpad(substring(text(xCog), 0, 3), 3, "X")

    which might (didn't check) make xLunCog obsolete.

    Same for xConNom, of course

    Like 1
      • Gianluca
      • Gianluca
      • 4 mths ago
      • Reported - view

      Alain Fontaine and Lars

      God bless the rain 😁

      thank you all for the suggestions. Now the code look so much better!

      Like
    • Lars
    • Lars
    • 4 mths ago
    • Reported - view

    One more: the whole 

    let a := countLetters(xCarPar, "A");
    let b := countLetters(xCarPar, "B");
    let c := countLetters(xCarPar, "C");
    let d := countLetters(xCarPar, "D");
    let e := countLetters(xCarPar, "E");
    let f := countLetters(xCarPar, "F");
    let g := countLetters(xCarPar, "G");
    let h := countLetters(xCarPar, "H");
    let i := countLetters(xCarPar, "I");
    let j := countLetters(xCarPar, "J");
    let k := countLetters(xCarPar, "K");
    let l := countLetters(xCarPar, "L");
    let m := countLetters(xCarPar, "M");
    let n := countLetters(xCarPar, "N");
    let o := countLetters(xCarPar, "O");
    let p := countLetters(xCarPar, "P");
    let q := countLetters(xCarPar, "Q");
    let r := countLetters(xCarPar, "R");
    let s := countLetters(xCarPar, "S");
    let t := countLetters(xCarPar, "T");
    let u := countLetters(xCarPar, "U");
    let v := countLetters(xCarPar, "V");
    let w := countLetters(xCarPar, "W");
    let x := countLetters(xCarPar, "X");
    let y := countLetters(xCarPar, "Y");
    let z := countLetters(xCarPar, "Z");
    let sumP := a * 0 + b * 1 + c * 2 + d * 3 + e * 4 + f * 5 + g * 6 + h * 7 + i * 8 + j * 9 + k * 10 + l * 11 + m * 12 + n * 13 + o * 14 + p * 15 + q * 16 + r * 17 + s * 18 + t * 19 + u * 20 + v * 21 + w * 22 + x * 23 + y * 24 + z * 25
    

    can be replaced by 

    let sumP := 0;
    for i in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" do
        sumP := sumP + countLetters(xCarPar, i) * index("ABCDEFGHIJKLMNOPQRSTUVWXYZ", i)
    end;
    

    and then you do something similar for the numbers

    Like 1
  • And a proposal for the complete computation of the checksum (boy, I had some spare time today, and it rains):

    let xCarDis := substr(cfparz, 0, 1) + substr(cfparz, 2, 1) + substr(cfparz, 4, 1) + substr(cfparz, 6, 1) + substr(cfparz, 8, 1) + substr(cfparz, 10, 1) + substr(cfparz, 12, 1) + substr(cfparz, 14, 1);
    let xCarPar := substr(cfparz, 1, 1) + substr(cfparz, 3, 1) + substr(cfparz, 5, 1) + substr(cfparz, 7, 1) + substr(cfparz, 9, 1) + substr(cfparz, 11, 1) + substr(cfparz, 13, 1);
    let xCarList := "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    let xParFact := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    let xDisFact := [1, 0, 5, 7, 9, 13, 15, 17, 19, 21, 2, 4, 18, 20, 11, 3, 6, 8, 12, 14, 16, 10, 22, 25, 24, 23, 1, 0, 5, 7, 9, 13, 15, 17, 19, 21];
    let sumPD := sum(for i in range(0, length(xCarList)) do
                countLetters(xCarPar, item(xCarList, i)) * item(xParFact, i) + countLetters(xCarDis, item(xCarList, i)) * item(xDisFact, i)
            end);
    let resto := (sumPD / 26 - floor(sumPD / 26)) * 26;
    let finalC := item("ABCDEFGHIJKLMNOPQRSTUVWXYZ", round(resto));
    Like 4
Like Follow
  • Status Answered
  • 4 mths agoLast active
  • 36Replies
  • 144Views
  • 6 Following