Count of specific character (e.g. # of commas) in a Variable's content?

Is there an easy way to get a count of a specific character (e.g. # of commas) in a Variable's content?

e.g. with 2 variables set as follows:
VarX = aaa, bbb, ccc, ddd
CountWhatChar = ,

Results in:
CountComma = 3

Yup:

Character Counter.kmmacros (3.4 KB)
image

2 Likes

@gglick has given you a great solution using all native KM Actions.

As an alternate, you could use this simple JXA Script:

var sourceStr = "aaa, bbb, ccc, ddd"
var numCommas = sourceStr.split(",").length - 1
numCommas

It has the advantage of being more compact, and might be useful in solving the larger problem where you need to know how many commas are being used.

Of course, just put it in a Execute a JavaScript For Automation action.

If you (or anyone) need help with this. just post here.

4 Likes

A more KMish macro
“Regex counting” Macro

Regex counting.kmmacros (1.9 KB)

Question to regex experts ;-): OK for a single character but for an arbitrary string?

-alain

1 Like

You might try first replacing the string with a single, placeholder character that is not in the source text, then use your procedure to count.

Make sense?

If the size of the arbitrary string is constant (RegEx can specify strings that are of varying lengths) the following might be useful

RegexCounting2.kmmacros (4.0 KB)

So you need to provide the string that you want to search. And you need to provide the length of the string that you are trying to count. For comma, that would be 1.

Then use the RegEx expression appropriate to finding that string.

We could have searched for "ddd" and supplied 3 as the length and it would return the count.

Hey Steve,

Sure.

-Chris


Count a Given Character.kmmacros (4.8 KB)
image

2 Likes

For a little more generality we could use (in an Execute JSA action) a function which counts all the characters in any string:

// charCounts :: String -> Dict
const charCounts = s =>
    s.split('')
    .reduce((a, c) => Object.assign(a, {
        [c]: 1 + (a[c] || 0)
    }), {});

returning a dictionary of the form:

{
  "a": 3,
  ",": 3,
  " ": 3,
  "b": 3,
  "c": 3,
  "d": 3
}

which could be stringified with:

Object.keys(dctScores)
    .sort()
    .reduce(
        (a, k) => `${a}'${k}' -> ${dctScores[k]}` + '\n',
        ''
    );

yielding something like:

' ' -> 3
',' -> 3
'a' -> 3
'b' -> 3
'c' -> 3
'd' -> 3

i.e. in full:

Char counts.kmmacros (19 KB)

(() => {
    'use strict';

    const main = () => {
        const dctScores = charCounts(
            Application('Keyboard Maestro Engine')
            .getvariable('varX')
        );
        return Object.keys(dctScores)
            .sort()
            .reduce(
                (a, k) => `${a}'${k}' -> ${dctScores[k]}` + '\n',
                ''
            );
    };

    // GENERIC ---

    // charCounts :: String -> Dict
    const charCounts = s =>
        s.split('')
        .reduce((a, c) => Object.assign(a, {
            [c]: 1 + (a[c] || 0)
        }), {});

    return main();
})();
1 Like

@cvc8445, I think we've shown you several easy ways to accomplish your task, and perhaps a few complicated ways. :smile:

In the interest of providing a more general solution that also handles any length of an arbitrary string, I submit the following Macro with JXA script. The core of the script is still quite simple and readable:

var sourceStr  = kmeApp.getvariable("Local__SourceStr",  {instance: kmInst}) || "aaa XYZ bbb XYZ ccc XYZ ddd";
var strToCount = kmeApp.getvariable("Local__StringToCount",  {instance: kmInst}) || "XYZ";
  
  //--- COUNT THE NUMBER OF OCCURRENCES ---
  var numOccur = sourceStr.split(strToCount).length - 1

I prefer a more focused script that, while working in the general case, still returns directly the answer we are looking for, without having to do further parsing to get it. The script simply returns the integer number of occurrences to a KM Variable.

This macro and script are provided as a learning tool, and example, to show you how to use certain features of KM. You will need to adapt it, or use parts of it, to your own macro/script.

To that end, if you have any questions, comments, issues, or suggestions about this, please feel free to post.


Example Output

Base Case: Simple, 1-character String
image

Expanded Case: Multi-character String
image


MACRO:   Count Number of Occurrences of String [Example]


#### DOWNLOAD:
<a class="attachment" href="/uploads/default/original/3X/9/5/9567e13e55cdb626cada747065c8d4f743574bcf.kmmacros">Count Number of Occurrences of String [Example].kmmacros</a> (12 KB)
**Note: This Macro was uploaded in a DISABLED state. You must enable before it can be triggered.**

---

### ReleaseNotes

Author.@JMichaelTX

**PURPOSE:**

* **Count the Number of Occurrences of a SubString within a String**

**REQUIRES:**

1. **KM 8.0.2+**
  * But it can be written in KM 7.3.1+
  * It is KM8 specific just because some of the Actions have changed to make things simpler, but equivalent Actions are available in KM 7.3.1.
.
2. **macOS 10.11.6 (El Capitan)**
  * KM 8 Requires Yosemite or later, so this macro will probably run on Yosemite, but I make no guarantees.  :wink: 

**NOTICE: This macro/script is just an _Example_**

* It has had very limited testing.
* You need to test further before using in a production environment.
* It does not have extensive error checking/handling.
* It may not be complete.  It is provided as an example to show you one approach to solving a problem.

**How To Use**

1. Make sure the data you want to use is properly set in the first two Actions (shown in magneta)
   * The StringToCount can be one or more characters, including blanks.
2. Trigger this macro.

**MACRO SETUP**

* **Carefully review the Release Notes and the Macro Actions**
  * Make sure you understand what the Macro will do.  
  * You are responsible for running the Macro, not me.  ??
.
1. Assign a Trigger to this maro..
2. Move this macro to a Macro Group that is only Active when you need this Macro.
3. ENABLE this Macro.
.
* **REVIEW/CHANGE THE FOLLOWING MACRO ACTIONS:**
(all shown in the magenta color)
   * Set variable SourceStr
   * Set Variable StringToCount

TAGS: @JXA @Strings

USER SETTINGS:

* Any Action in _magenta color_ is designed to be changed by end-user

ACTION COLOR CODES

* To facilitate the reading, customizing, and maintenance of this macro,
      key Actions are colored as follows:
* GREEN   -- Key Comments designed to highlight main sections of macro
* MAGENTA -- Actions designed to be customized by user
* YELLOW  -- Primary Actions (usually the main purpose of the macro)
* ORANGE  -- Actions that permanently destroy Variables or Clipboards,
OR IF/THEN and PAUSE Actions


**USE AT YOUR OWN RISK**

* While I have given this limited testing, and to the best of my knowledge will do no harm, I cannot guarantee it.
* If you have any doubts or questions:
  * **Ask first**
  * Turn on the KM Debugger from the KM Status Menu, and step through the macro, making sure you understand what it is doing with each Action.

---

![image|564x1211](upload://63KdukEmx8UxpFdI3909UIeU6Pd.jpg)

---

### JXA Script
```javascript
'use strict';  
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

(function myMain() {    // ~~~ automatically executed when this script is executed ~~~
  
var ptyScriptName   = "Count Number of String Occurrences"
var ptyScriptVer     = "1.0"
var ptyScriptDate   = "2018-05-26"
var ptyScriptAuthor = "JMichaelTX"
/*

PURPOSE: Count the Number of Occurrences of a SubString within a String

RETURNS: | integer | Count of Occurrences

KM VARIABALES REQUIRED:
• Local__SourceStr
• Local__StringToCount

KM VARIABLES SET:
• NONE

REF:

  1. cvc8445, 2018-05-23, Count of specific character (e.g. # of commas) in a Variable's content?
    Count of specific character (e.g. # of commas) in a Variable's content?
*/
  
  // --- SET CURRENT APP VARIABLE NEEDED FOR DIALOGS & StandardAdditions.osax ---
  var app = Application.currentApplication()
  app.includeStandardAdditions = true

  //--- GET THE DATA FROM KM MACRO ---
  
  var kme = Application("Keyboard Maestro Engine");
  
  var kmInst = app.systemAttribute("KMINSTANCE");
  var kmeApp = Application("Keyboard Maestro Engine")
   
  var sourceStr  = kmeApp.getvariable("Local__SourceStr",  {instance: kmInst}) || "aaa XYZ bbb XYZ ccc XYZ ddd";
  var strToCount = kmeApp.getvariable("Local__StringToCount",  {instance: kmInst}) || "XYZ";
  
  //--- COUNT THE NUMBER OF OCCURRENCES ---
  var numOccur = sourceStr.split(strToCount).length - 1
    
  return numOccur

})();  // ~~~ function run() is automatically executed when this script is executed ~~~


```

---

Side note to @peternlewis: I finally found a good use for “smart quotes”, in my KM Display window.  :wink:
3 Likes

With my limited testing, I uncovered no problems with JMichaelTx 's contribution using JXA.

General solution is somewhat a matter of definition. :thinking:

The solution previously supplied by me using Regex within KM has some circumstances that it can deal with that the JMichaelTX solution does not. In the example below, you might consider counting the number of two consecutive digits in a string without specifying two particular digits. Here, Regex offers a solution. As I noted previously, this assumes that you know how many characters the Regex search will return. It cannot be "variable". In the example below it is 2. [You cannot simply pass/enter the length of the Regex string itself (which is 4 in this example - \d\d)].

But now this should give you the answer you are looking for.

The methodology below falls apart if you are passing a Regex string such as \d+. In that case the length of the found substrings will be variable in length. You cannot simply supply a number such as 2. So my contribution will not work.

RegexCounting3.kmmacros (4.1 KB)

Thanks for the challenge. I think we all learn from such. :smile:

Actually, JavaScript split() function is quite versitle, and can easily handle this, without the need to input the "SearchLength".

All I need to do is add one statement:
if (/^\/(.+)\/$/.test(strToCount)) { strToCount = RegExp(strToCount.match(/^\/(.+)\/$/)[1]); }

then it will detect a RegEx expression as input, and convert the text string to a RegEx expression.
So, if the user enters /XYZ\d+/ it recognizes this as a RegEx because the string starts and ends with a /. Of course, plain text still works.

Then a sourceStr like this:
aaa, XYZ1 bbb, ccc, XYZ456 ddd

returns

image


Here's the complete script. Anyone interested can just replace the script in my above Macro with this one.

JXA Script Allowing for RegEx Input

'use strict';  
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

(function myMain() {    // ~~~ automatically executed when this script is executed ~~~
  
var ptyScriptName   = "Count Number of String Occurrences"
var ptyScriptVer     = "1.1"
var ptyScriptDate   = "2018-05-26"
var ptyScriptAuthor = "JMichaelTX"
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PURPOSE:  Count the Number of Occurrences of a SubString within a String

RETURNS:  | integer | Count of Occurrences
  
KM VARIABALES REQUIRED:
  • Local__SourceStr  (can be either plain text, or RegEx put between slashes: /<RegEx>/
  • Local__StringToCount
  
KM VARIABLES SET:
  • NONE
  
REF:
  1. cvc8445, 2018-05-23, Count of specific character (e.g. # of commas) in a Variable's content?
      https://forum.keyboardmaestro.com/t/count-of-specific-character-e-g-of-commas-in-a-variables-content/10389
~~~~~~~~~~~~~~~~~~~ END HEADER COMMENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
  
  // --- SET CURRENT APP VARIABLE NEEDED FOR DIALOGS & StandardAdditions.osax ---
  var app = Application.currentApplication()
  app.includeStandardAdditions = true

  //--- GET THE DATA FROM KM MACRO ---
  
  var kme = Application("Keyboard Maestro Engine");
  
  var kmInst = app.systemAttribute("KMINSTANCE");
  var kmeApp = Application("Keyboard Maestro Engine")
   
  var sourceStr  = kmeApp.getvariable("Local__SourceStr",  {instance: kmInst}) || "aaa XYZ bbb XYZ1 ccc XYZ456 ddd";
  var strToCount = kmeApp.getvariable("Local__StringToCount",  {instance: kmInst}) || "/XYZ\\d+/";
  
  //--- Check for Input of RegEx Expression ---
  if (/^\/(.+)\/$/.test(strToCount)) {  strToCount = RegExp(strToCount.match(/^\/(.+)\/$/)[1]); }
  
  //--- COUNT THE NUMBER OF OCCURRENCES ---
  var numOccur = sourceStr.split(strToCount).length - 1
    
  return numOccur

})();  // ~~~ function run() is automatically executed when this script is executed ~~~


Touché.
Of course J…TX is willing to step outside and use super weapons like JXA.

Thanks @every_contributors for the diversity of this panel of solutions.

Without super weapons of traditional languages, in case of fixed length search string, following @rlivingston, KM gives the result in two actions without explicit loop:

Replace Counting.kmmacros (5.7 KB)


1 Like

Well, it actually took 3 Actions, but it's still a very nice solution. :+1:

Of course, my JXA solution which allows for RegEx for String to Count, only took one KM Action. :wink:

Nope IMO 2 KM Actions (the green ones). :wink:

I still prefer this 'one-liner' :wink:

(() => {
    'use strict';

    // charCount :: Char -> String -> Int 
    const charCount = (c, s) => 
        s.split('').reduce((a, x) => c !== x ? a : 1 + a, 0);
        
        
    return charCount('e', 'excessive');
    // --> 3

})();

or if you insist:

(() => {
    'use strict';

    // charCount :: Char -> String -> Int 
    const charCount = (c, s) => s.split(c).length - 1;
        
        
    return charCount('e', 'excessive');
    // --> 3

})();
3 Likes

autrement dit:

-- charCount :: Char -> String -> Int
on charCount(c, s)
    (length of splitOn(c, s)) - 1
end charCount

on run
    
    charCount("e", "excessive")
    
    --> 3
end run


-- GENERIC ---

-- splitOn :: String -> String -> [String]
on splitOn(strDelim, strMain)
    set {dlm, my text item delimiters} to {my text item delimiters, strDelim}
    set xs to text items of strMain
    set my text item delimiters to dlm
    return xs
end splitOn

Hey Folks,

Using Keyboard Maestro's Search/Replace function:

----------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2018/05/28 20:27
# dMod: 2018/05/28 20:32
# Appl: Keyboard Maestro Engine
# Task: Count Characters Using Keyboard Maestro's AppleScript Search-Replace Command
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @Keyboard_Maestro_Engine, @Count, @Characters, @Using, @Keyboard_Maestro's, @AppleScript, @Search-Replace, @Command
----------------------------------------------------------------

# Simulate getting the Data String from an existing Keyboard Maestro Varaible.
tell application "Keyboard Maestro Engine"
   
   # Sets the Keyboard Maestro variable.
   setvariable "kmDataStr" to "aaa, bbb, ccc, ddd,
eee, fff, ggg"
   
   # Gets data from the Keyboard Maestro variable.
   set dataStr to getvariable "kmDataStr"
end tell

# The meat of the script.
tell application "Keyboard Maestro Engine"
   set numberOfCharactersFound to length of (search dataStr for "[^,]" replace "" with regex without case sensitive and process tokens)
end tell

----------------------------------------------------------------

The same thing using the Satimage AppleScript Extension.

----------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2018/05/28 20:27
# dMod: 2018/05/28 20:37
# Appl: Satimage.osax
# Task: Count Characters Using the Satimage AppleScript Extension (Satimage.osax)
# Libs: None
# Osax: Satimage.osax --> http://tinyurl.com/satimage-osaxen
# Tags: @Applescript, @Script, @Satimage.osax, @Count, @Characters, @Using, @Search, @Replace, @Change, @Command
----------------------------------------------------------------

# Simulate getting the Data String from an existing Keyboard Maestro Variable.
tell application "Keyboard Maestro Engine"
   
   # Sets the Keyboard Maestro variable.
   setvariable "kmDataStr" to "aaa, bbb, ccc, ddd,
eee, fff, ggg"
   
   # Gets data from the Keyboard Maestro variable.
   set dataStr to getvariable "kmDataStr"
end tell

# The meat of the script – a bit simpler than the Keyboard Maestro version.
set numberOfCharactersFound to length of (find text "," in dataStr with all occurrences and string result)

----------------------------------------------------------------

It's a bit simpler than the Keyboard Maestro version and is yet another example of why I keep using the Satimage.osax to get work done.

-Chris

Hey Folks,

Last one (from me anyhow).

I thought this might be easy to do with Python, and I was right.

This serves as an example of counting literal strings with Python, and how to get Keyboard Maestro variables into the Python environment.

-Chris


Count Literal Strings with Python.kmmacros (4.9 KB)

@ccstone Thank you for a good macro, i was just looking for such a thing.

I might have a local issue, and i know it's a long time since you wrote this. However, i am crossing my fingers in the hopes :crossed_fingers:

I am not able to save the value from the shell script to a variable, which is important, as i am trying to build a macro on the base of the.

I hope i have'nt messed up the macro to badly :sweat_smile:
Insert Extra Tab.kmmacros (3.3 KB)