How to Extract a Non-Number String and Two Number Strings From a Variable

Hello,

Please excuse my broken English but I will try my best with Google Translation.

I've got a variable like "abc123-456" and I want to extract the numbers but separately like this "123" "456" and these numbers can be one digit to 4 digit numbers like "1-1234" "12-1" "1234-10" "1234-1234".

So I want results such that if I have "text1233-111" set in my any variable I want to insert text or type like "text" "1233" "111".

I never done programming before and Keyboard Maestro is my first actual programming app. Any help would be greatly appreciated!

Thank you.

Hey Chris,

Welcome to the forum!  :sunglasses:

This task is easy enough to do by searching with a regular expression – but it takes a fair bit of learning before regex comes easy.

-Chris


Extract Text and Two Numbers from a Variable v1.00.kmmacros (5.7 KB)

Macro-Image

Keyboard Maestro Export

In a scripting language like Python (general purpose) or JavaScript (web + macOS/iOS scripting), we can typically "group" a sequence of characters into separate groups, according to whether they are numeric characters (digits) or not.

In Python, for example:

groupby("text1233-111", str.isdigit)

which we could generalize a bit:

for n in [
    int(''.join(characters)) for (isnumeric, characters)
    in groupby(test, str.isdigit)
    if isnumeric
]: print(n)

and use in a Keyboard Maestro "Execute a Shell Script" action (example below).

Given that regular expressions do, as Chris puts it, "take a fair bit of learning", opinion will differ over whether the time and effort of learning is better rewarded by "regex" or by a fuller and more flexible scripting language.

My personal advice would probably be to learn the basic concepts of a general scripting language, before you dive into the less flexible and less powerful sub-language of regular expressions.


Separately grouping numeric and non-numeric characters

Separately grouping numeric and non-numeric characters.kmmacros (2.2 KB)


Or simply separating the numeric and non-numeric tokens out into separate lines:

Separate lines from numeric and non-numeric sequences.kmmacros (2.2 KB)

and once you have experimented a little with a full scripting language, you can also learn to use a few simple regular expressions within it.

Here, for example using the regex \D+ (one or more non-digit characters) to split the string, and then filter out any "empty" strings that remain.

This time, using JavaScript:

Splitting on non-numeric characters.kmmacros (2.1 KB)

Or stripping back a bit:

Application("Keyboard Maestro Engine")
    .getvariable("textSample")
    .split(/\D+/u)
    .join("\n")
    .trim();

And using a For Each ... action over the separate number lines:

For Each after splitting on non-numeric characters.kmmacros (4.3 KB)

Hello Chris!

Thank you so much for your kind reply. I did a search on regex, but most of them are in English and my programming language skills are very low, so it was difficult to understand utilize so I needed an example. And you gave me perfect example! Thank you, thank you. Have a good day!

-Chris

1 Like

Hello ComplexPoint!

Thank you for your such a detailed kindness reply!

To be honest it does not look so easy to me but I will look up all of your examples and try to study properly thank you for the personal advice and I will try to learn basic concepts to!

Thank you, thank you!

-Chris

1 Like

To be honest, unless you happen to find them interesting in themselves, there are much better things to do in life than going down the rabbit hole of formal languages.

Usually better to get someone else to do it, if you can :slight_smile:

(See under David Ricardo and trade theory – always best to spend the time where you can make the most of it)

1 Like

Great examples so far. FWIW, here is a possible Swift approach:

Separate lines from numeric and non-numeric sequences.kmmacros (6.8 KB)

Swift Code:

import Foundation

// main :: IO ()
func main() -> () {
    let s = ProcessInfo.processInfo.environment["KMVAR_testData"]!
    let f = unlines • map(str) • filter(all(isAlphaNum)) • groupBy(on(eq)(isDigit)) • chars
    return print(f(s))
}
// FUNCTIONS --
// unlocked2412 library
// Swift Precedence & Operators --------------------------------
precedencegroup ApplicativePrecedence {
    associativity: left
    higherThan: BitwiseShiftPrecedence
}

infix operator <*> : ApplicativePrecedence

precedencegroup CompositionPrecedence {
	associativity: right
	
	// This is a higher precedence than the exponentiative operators `<<` and `>>`.
	higherThan: BitwiseShiftPrecedence, ApplicativePrecedence
}

// Swift Prelude -----------------------------------------------
// apFn :: (a -> b -> c) -> (a -> b) -> (a -> c)
func <*><A, B, C>(_ f: @escaping (A) -> (B) -> C, _ g: @escaping (A) -> B) -> ((A) -> C) {
    return { x in f(x)(g(x)) }
}

// all :: (a -> Bool) -> [a] -> Bool
func all <A>(_ p: @escaping (A) -> Bool) -> ([A]) -> Bool {
    return {xs in xs.allSatisfy(p)}
}

// chars :: String -> [Char]
func chars(_ s: String) -> [Character] {
    return Array(s)
}

// cons :: a -> [a] -> [a]
func cons<A>(_ x: A) -> ([A]) -> [A] {
    return { xs in [x] + xs }
}

// eq (==) :: Eq a => a -> a -> Bool
func eq<A: Equatable>(_ a: A) -> (A) -> Bool {
    return { b in a == b }
}

func filter<L: LazySequenceProtocol>(_ p: @escaping (L.Elements.Element) -> Bool) -> (L) -> LazyFilterSequence<L.Elements> {
    return { xs in xs.filter(p) }
}

func filter<S: Sequence>(_ p: @escaping (S.Element) -> Bool) -> (S) -> [S.Element] {
    return { xs in xs.filter(p) }
}

// groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
func groupBy<A: Equatable>(_ eq: @escaping (A) -> (A) -> Bool) -> ([A]) -> [[A]] {
    {xs in
        {
            switch xs {
                case []: return []
                default: return {
                    let x = head(xs)
                    let xs1 = tail(xs)
                    let (ys, zs) = span(eq(x))(xs1)
                    return cons(
                        cons(x)(ys)
                    )(groupBy(eq)(zs))
                }()
            }
        }()
    }
}

// head :: [a] -> a
func head<A>(_ xs: [A]) -> A {
    return xs.first!
}

// isAlphaNum :: Char -> Bool
func isAlphaNum(_ c: Character) -> Bool {
    return c.isLetter || c.isNumber
}

// isDigit :: Char -> Bool
func isDigit(_ c: Character) -> Bool {
    return c.isNumber
}

func map<T, L: LazySequenceProtocol>(_ p: @escaping (L.Elements.Element) -> T) -> (L) -> LazyMapSequence<L.Elements, T> {
    return { xs in xs.map(p) }
}

// map :: (a -> b) -> [a] -> [b]
func map<T, S: Sequence>(_ f: @escaping (S.Element) -> T) -> (S) -> [T] {
    return { xs in xs.map(f) }
}

// on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
func on<A, B, C>(_ f: @escaping (B) -> (B) -> C) -> (@escaping (A) -> B) -> (A) -> (A) -> C {
    return {
        g in {
            a in {
                b in f(g(a))(g(b))
            }
        }
    }
}

//span                    :: (a -> Bool) -> [a] -> ([a],[a])
//span _ xs@[]            =  (xs, xs)
//span p xs@(x:xs')
//| p x          =  let (ys,zs) = span p xs' in (x:ys,zs)
//| otherwise    =  ([],xs)
//span                    :: (a -> Bool) -> [a] -> ([a],[a])
func span<A: Equatable>(_ p: @escaping (A) -> Bool) -> ([A]) -> ([A], [A]) {
    return { xs in
        {
            switch xs {
            case []:
                return (xs, xs)
            default:
                return {
                    let x = head(xs)
                    let xs1 = tail(xs)
                    return p(x) ? (
                        {
                            let (ys, zs) = span(p)(xs1)
                            return (cons(x)(ys), zs)
                        }()
                    ) : ([], xs)
                }()
            }
        }()
    }
}

// str :: [Char] -> String
func str(_ xs: [Character]) -> String {
    return String(xs)
}

// tail :: [a] -> [a]
func tail<S: Sequence>(_ xs: S) -> [S.Element] {
    return Array(xs.dropFirst(1))
}

// unlines :: [String] -> String
func unlines(_ xs: [String]) -> String {
    return xs.joined(separator: "\n")
}

infix operator • : CompositionPrecedence

// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
func • <A, B, C>(f: @escaping (B) -> C, g: @escaping (A) -> B) -> (A) -> C {
    return { x in f(g(x)) }
}

main()
1 Like

And a Haskell solution:

Separate lines from numeric and non-numeric sequences.kmmacros (2.0 KB)

Haskell Code:

import Data.List
import Data.Char
import Data.Function

main :: IO ()
main = interact f

f :: String -> String
f = unlines . filter (all isAlphaNum) . groupBy (on (==) isDigit)
2 Likes