2
\$\begingroup\$

What I want is to do is to extract min/max range of salary from a text which contains either hourly or annual salary.

import re

# either of the following inputs should work
input1 = "$80,000 - $90,000 per annum"
input2 = "$20 - $24.99 per hour"

salary_text = re.findall("[\$0-9,\. ]*-[\$0-9,\. ]*", input1)

if salary_text and salary_text[0]:
    range_list = re.split("-", salary_text[0])

    if range_list and len(range_list) == 2:
        low = range_list[0].strip(' $').replace(',', '')
        high = range_list[1].strip(' $').replace(',', '')

````
\$\endgroup\$
  • 1
    \$\begingroup\$ you could create list with inputs all_examples = [input1, input2] and run for input in all_examples: - this way you can test code with all inputs without changing code. Eventually you could create list with input and expected outputs all_examples = [(input1, "80000", "90000"), (input2, "20", "24.99")] and automatically check if results are correct for input, expected_low, expected_hight in all_examples: ... low == expected_low ... high == expected_hight ... \$\endgroup\$ – furas Jun 15 at 1:47
3
\$\begingroup\$

The , commas are a nice twist. I feel you are stripping them a bit late, as they don't really contribute to the desired solution. Better to lose them from the get go.

Calling .findall seems to be overkill for your problem specification -- likely .search would suffice.

salary_text = re.findall("[\$0-9,\. ]*-[\$0-9,\. ]*", input1)

The dollar signs, similarly, do not contribute to the solution, your regex could probably just ignore them if your inputs are fairly sane. Or even scan for lines starting with $ dollar, and then the regex ignores them.

range_list = re.split("-", salary_text[0])

There is no need for this .split -- the regex could have done this for you already. Here is what I recommend:

def find_range(text):
    if text.startswith('$'):
        m = re.search(r'([\d\.]+) *- *\$?([\d\.]+)', text.replace(",", ""))
        if m:
            lo, hi = m.groups()
            return float(lo), float(hi)
    return None, None


print(find_range('$80,000 - $90,000 per annum'))
print(find_range('$20 - $24.99 per hour'))

The raw string regex r'[\d\.]+' picks out one or more numeric characters, which can include decimal point. And putting ( ) parens around a regex makes it a capturing group -- we have two such groups here. Finally, *- *\$? lets us skip a single - dash with optional whitespace and at most one optional $ dollar sign.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks a lot, this is an excellent solution. \$\endgroup\$ – Hooman Bahreini Jun 16 at 21:54
1
\$\begingroup\$

Good job

And there is already a good answer here!

I'm only commenting on your regular expression, even though I'm not so sure how your input ranges may look like. But, it would probably miss some edge cases. I'm assuming that these are all acceptable:

$80,000,000,000.00 - $90,000,000,000.00 per annum
$80,000,000 - $90,000,000 per annum
$80,000 - $90,000 per annum
$20 - $24.99 per hour
 $20 - $24.99 per hour
$20 - $24.99 per hour
 $20.00 - $24.99 per hour

and these are unacceptable:

 $20.00 - $24.99 per day
 $111,120.00 - $11,124.99 per week
 $111,222,120.00 - $111,111,124.99 per month

You can see your own expression in this link:

Demo

  • It would pass some cases that may not be desired, I guess.
  • You also do not need to escape . and $ inside a character class:

Demo

Code

import re

def find_range(text: str) -> dict:
    expression = r'^\s*\$([0-9]{1,3}(?:,[0-9]{1,3})*(?:\.[0-9]{1,2})?)\s*-\s*\$([0-9]{1,3}(?:,[0-9]{1,3})*(?:\.[0-9]{1,2})?)\s*per\s+(?:annum|hour)\s*$'
    return re.findall(expression, text)


input_a = '$80,000 - $90,000 per annum'
input_b = '$20 - $24.99 per hour'
print(find_range(input_a))


If you wish to simplify/update/explore the expression, it's been explained on the top right panel of regex101.com. You can watch the matching steps or modify them in this debugger link, if you'd be interested. The debugger demonstrates that how a RegEx engine might step by step consume some sample input strings and would perform the matching process.


RegEx Circuit

jex.im visualizes regular expressions:

enter image description here

Demo

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ Thanks for this, especially those corner cases are very good points. \$\endgroup\$ – Hooman Bahreini Jun 16 at 11:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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