2
\$\begingroup\$

I would like to know if there is a better regex to match all worded numbers from 1 to 99.
With "better" I mean shorter with same performance, OR faster.

I came up with this. I can't do better.
const regex = /\b(?<!\-)(?:(?:twen(?=ty)|thir|four(?=te)|for(?=ty)|fif|six|seven|eigh|nine)t(?:een|y)(?:(?<=ty)\-(?:one|two|three|four|five|six|seven|eight|nine))?|(?:one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve))/g;

const testString = see_regex101_link
const isMatch = regex.test(testString);
console.log(isMatch);

You can test it here: https://regex101.com/r/CYBK5S/1 Javascript if possible.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I expect that maintaining a set of 99 strings would be best in terms of execution speed, readability and time required for testing. In Ruby (think pseudo-code), suffixes = ["one", "two", ..., "nine"], prefixes = ["twenty", "thirty", ..., "ninety"], teens = ["ten", "eleven", ..., "nineteen"], cartesian_product = prefixes.product(['-'], suffices).map(&:join) #=> ["twenty-one", "twenty-two", ..., "twenty-nine", "thirty-one", ..., "ninety-nine"], then create a constant that holds a set of 99 strings: ONE_TO_ONE_HUNDRED = Set[suffices + teens + prefixes + cartesian_product]. \$\endgroup\$ Commented Sep 23 at 22:27
  • \$\begingroup\$ I expect REs to get "compiled" into small automatons, with many expressions resulting in the same automaton. \$\endgroup\$
    – greybeard
    Commented Sep 26 at 8:08

2 Answers 2

3
\$\begingroup\$

Honestly, I got a bit curious of your question and started trying some things.

I have found that your pattern takes on average 0.215ms, which was hard to beat.

However, for some reason the following pattern averages in 0.15ms:

const pattern = /\b(?:twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety)(?:-(?:one|two|three|four|five|six|seven|eight|nine))?|(?:ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen)|(?:one|two|three|four|five|six|seven|eight|nine)\b/g

So this pattern might not be shorter but quicker.

\$\endgroup\$
3
  • \$\begingroup\$ "For some reason": I wonder if the nearly-alphabetical sorting of the words helped the regex engine build a pseudo-trie. E.g. there's a lot of t-words, followed by f-words, and s-words, and so on. \$\endgroup\$
    – Rogue
    Commented Sep 24 at 12:50
  • \$\begingroup\$ I just tested it. Using the benchmark feature both mine and yours have a minimum of 32ms match time. Multiply the string magnitude if your machine is too fast. Despite yours having -20% steps than mine on PCRE2 flavour, i don't see a difference even with jsfiddle. \$\endgroup\$
    – Bobby234
    Commented Sep 24 at 21:18
  • 1
    \$\begingroup\$ To detail it, yours is faster on PCRE2, but not with Javascript (test on jsfiddle might be useful) \$\endgroup\$
    – Bobby234
    Commented Sep 25 at 6:10
1
\$\begingroup\$

After more testing, I just optimized Jdm(user) solution: /\b(?:(?:twen|thir|for|fif|six|seven|eigh|nine)ty(?:-(?:one|two|three|four|five|six|seven|eight|nine))?|(?:thir|four|fif|six|seven|eigh|nine)teen|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\b/

The best regex with Javascript and PCRE2 (didn't test others) when the string has all worded numbers from 1 to 99 equally. Also the most readable.
https://regex101.com/r/JZGYSD/1

You can optimize it based on context:

  1. if most of the numbers are > 20, the regex above is better.
  2. if they < 20, move 1-9 to first
    /\b(?:(?<![efhnrx]ty-)(?:one|two|three|four|five|six|seven|eight|nine)|(?:twen|thir|for|fif|six|seven|eigh|nine)ty(?:\-(?:one|two|three|four|five|six|seven|eight|nine))?|(?:thir|four|fif|six|seven|eigh|nine)teen|ten|eleven|twelve)\b/
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.