3

I'm trying to create some variables within string with words that follows a specific format.

For instance, my original string is:

string = '@Cost1 + (@Cost2 + @Cost3) / @Revenue1 * 1.2'

I have already created a dictionnary as follow:

mydict = {'Cost1' : 10, 'Cost2' : 5, 'Cost3' : 1, 'Revenue1' : 10}

What I would like to create is a function that will replace the @Cost1 (for instance) by: mydict.get('Cost1') so that I can consider the @ in my string as a marker for variable and look for the value in my dictionnary.

Therefore, my intent is to:

  • replace the @ by mydict.get(' --> that is very easy with the string.replace() function
  • add ') at the end of the chain of alphabet letters that follows the @words --> this one I'm having difficulty to implement

I was considering a for loop but struggling with the logic to add the "')" after the end of each word preceded by an @. Also thought about generator but not too sure

Also, I would like to add a check that every @Cost1, @Cost2 etc. is in my dictionnary, but this one I should be able to do it.

Any help is welcome about this

Thank you !

Eric

Edit:

following your comment, I would like my final output to be:

string = mydict.get('Cost1') + ( mydict.get('Cost2') + mydict.get('Cost3') ) / mydict.get('Revenue1') * 1.2'
yeye
  • 503
  • 4
  • 19
  • 1
    Could you post a sample output? – DirtyBit Mar 19 '19 at 07:44
  • 1
    I have no idea what *"add "')" at the end of the chain of alphabet letters that follows the @words"* means. What's "the end of the chain of alphabet letters that follows the @words"? – Aran-Fey Mar 19 '19 at 07:45

2 Answers2

4

I think you need to capture a regular expression @+letters/digits/underscore, which is "@(\w+) (matches @ then 1 or more letter/digit/underscore and create a group with those) fed to a replacement function

import re
mydict = {'Cost1' : 10, 'Cost2' : 5, 'Cost3' : 1, 'Revenue1' : 10}
string = '@Cost1 + (@Cost2 + @Cost3) / @Revenue1 * 1.2'

expression = re.sub("@(\w+)",lambda m : str(mydict.get(m.group(1),0)),string)

result:

'10 + (5 + 1) / 10 * 1.2'

This method is highly efficient because it uses the dictionary lookup instead of looping to replace and create as many strings as there are variables.

note that any unknown variable yields 0. If you don't want that use mydict[m.group(1)] to trigger key error instead.

The lambda isn't compulsory either. You can write a real function instead as long as it takes a match object as input and returns a string as output:

def repfunc(m)
    return str(mydict.get(m.group(1),0))

re.sub("@(\w+)",repfunc,string)

(that's why we convert to string after getting the value)

Once you have that expression, you can apply an evaluator (eval is the simplest but the less safe, ast.literal_eval won't work because it doesn't support operations, but there are others, for instance the simpleeval third-party module (which can be fed a list of variables which would simplify the answer even more), or other examples here: Evaluating a mathematical expression in a string)

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • Merci Jean-François ! That dit it. One question, I want to catch error properly: How do I do it ? When I add as you say mydict[m.group(1)], it gives me key error but I don't know which key is it. How I can get it ? Also, is it possible to detail what is the behaviour of the : "@(\w+)" in the sub and also the lambda m : str(mydict.get(m.group(1),0)) Thanks Edit: Sorry I made a mistake, I can see the key erroring. If you can give me the explanation of the lambda function, very appreciated – yeye Mar 19 '19 at 08:23
  • 2
    I have added a non-lambda version. – Jean-François Fabre Mar 19 '19 at 13:50
  • How would you do within a pd.series ? I'm trying to do the same (assuming string is row in my pd.serie). I'm trying to use pd.Series.str.replace but I don't manage to do it ? Then I would also like to evaluate the result in the second time – yeye Jun 15 '19 at 23:16
1
string = '@Cost1 + (@Cost2 + @Cost3) / @Revenue1 * 1.2'
mydict = {'Cost1' : 10, 'Cost2' : 5, 'Cost3' : 1, 'Revenue1' : 10}
string = string.replace('@', '')
for i in mydict:
    string = string.replace(i, str(mydict[i]))

print(string)
print(eval(string))

output:

10 + (5 + 1) / 10 * 1.2

10.72
Nihal
  • 5,262
  • 7
  • 23
  • 41