• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 560
  • Last Modified:

how to use Python lists to create dynamic calculations

Greetings,
I am trying to use python to create a lookup table.  The lookup table is in a csv right now.
example:
Category1, .15xPrice + 2.00
Category2, .18xPrice + 2.00

The lookup locates the Category, and the second column is the algorithm that is executed against a given value assigned to the the variable Price.

I do not know how to execute this.

For example:
PRice = 29.95
Category = Category1

THe output would be (29.95 * .15) + 2.00 or 6.4925
How do I accomplish this?

Thanks
0
Evan Cutler
Asked:
Evan Cutler
  • 3
  • 3
3 Solutions
 
peprCommented:
Firstly, I suggest to use the standard csv module to read the content of the file. You can do manual split in this case, but using the csv module is not difficult and it is capable to handle more complex input in future.

Secondly, the formula has to be interpreted (that is clear). Python has the eval() function that could be used after fixing the formula syntax; however, it may be dangerous if the formula could contain malicious code. Anyway, it is possible to wrap the calculation to a function to have the calculator abstraction (starting with dangerous eval(), replacing it later by something else -- it can also fix the syntax, or better to say to convert the formula syntax to Python syntax).

Thirdly, you need to map the placeholder inside the formula  (here PRice) the value . Notice also that the 'x' in the formula is difficult to be distinguished from the following Price name technically. So, you have to introduce some rules. The mapping should probably be done through a dictionary as it is easy to insert the valua via a modified placeholder.

Now, the hardwired hack exactly for the case. It can be enhanced later:
#!python3

import csv

def calc(value, formula):
    print('\n------------------------ calc {}, {!r}'.format(value, formula))
    d = {'Price': value}
    print('Placeholder mapping:', d)
    
    # Modifying the placeholders in the formula.
    for k in d:
        formula = formula.replace(k, '{' + k + '}')
    print('Fixed placeholders:', repr(formula))
        
    # Fixing the x operator.
    formula = formula.replace('x', '*')
    print('Fixed operator:', repr(formula))
    
    # Now insert the values to the formula.
    expression = formula.format_map(d)
    print('Expression:', repr(expression))
    print('---------------------------------')
    
    # Do the evaluation and return the result.
    return eval(expression)
    
    

if __name__ == '__main__':    
    fname = 'data.csv'
    price = 29.95
    with open(fname, newline='') as f:
        reader = csv.reader(f)
        for row in reader:
            ##print(row)
            category = row[0]
            result = calc(price, row[1])
            print('{}: {}'.format(category, result))

Open in new window

It prints on my console:
c:\__Python\EvanCutler\Q_28507029>a.py

------------------------ calc 29.95, ' .15xPrice + 2.00'
Placeholder mapping: {'Price': 29.95}
Fixed placeholders: ' .15x{Price} + 2.00'
Fixed operator: ' .15*{Price} + 2.00'
Expression: ' .15*29.95 + 2.00'
---------------------------------
Category1: 6.4925

------------------------ calc 29.95, ' .18xPrice + 2.00'
Placeholder mapping: {'Price': 29.95}
Fixed placeholders: ' .18x{Price} + 2.00'
Fixed operator: ' .18*{Price} + 2.00'
Expression: ' .18*29.95 + 2.00'
---------------------------------
Category2: 7.391

Open in new window

There are some thing not explained (used intentionally for provoking you to ask ;) -- ask here in this question:
{!r}
repr()
if __name__ == '__main__':
with
newline=''
0
 
Nas-BanovCommented:
With the only request to use "*" for multiplication in formulas and not "x":
# read formula file
lookup = { }
for line in open('myFile.cvs', 'r'):
    category, formula = line.strip().split(',')
    lookup[category] = formula
# result is as-if
#      lookup = {'Category1': '.15*Price + 2.00', 'Category2': ' .18*Price + 2.00'}

# given 
Price = 29.95
Category = 'Category1'

# do the math
print eval(lookup[Category])

Open in new window

0
 
peprCommented:
+1 to Nas-Banov's even simpler (but a bit more dangerous) approach where eval() takes the Price variable directly from the local variables, and for the lookup dictionary filled in advance. However, the file (when open) should always be closed. This is what the with construct does implicitly.

The example with calc() can be rewritten using the approach. It fixes the formulas using another function when loading the lookup table:
#!python3

import csv

def calc(Price, formula):
    return eval(formula)


def fixed_formula(formula):
    print('\n---------------- fixed_formula({!r})'.format(formula))
    formula2 = formula.strip(). replace('x', '*')
    print('Fixed formula:', repr(formula2))
    return formula2


if __name__ == '__main__':

    # Load the lookup table, fix the formulas.
    fname = 'data.csv'
    lookup = {}
    with open(fname, newline='') as f:
        reader = csv.reader(f)
        for row in reader:
            lookup[row[0]] = fixed_formula(row[1])

    print('=' * 50)
    # Calculate the values for the price and all categories.
    price = 29.95
    for category in lookup:
        result = calc(price, lookup[category])
        print('{}: {}'.format(category, result))

Open in new window

It prints:
c:\__Python\EvanCutler\Q_28507029>b.py

---------------- fixed_formula(' .15xPrice + 2.00')
Fixed formula: '.15*Price + 2.00'

---------------- fixed_formula(' .18xPrice + 2.00')
Fixed formula: '.18*Price + 2.00'
==================================================
Category1: 6.4925
Category2: 7.391

Open in new window

0
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 
Nas-BanovCommented:
@pepr, i did not see your solution when i wrote mine - there was no answer when i started writing it; i wouldn't have done double-take if i saw yours.

Now, i tried to balance simplicity and correctness for a novice level. But your note about "more dangerous" does not apply:
Even without with, the file will be closed automatically right after the for loop (you can check, files are fine w/o with).
Using eval() sure has dangers but you are using it too and formula.replace() substitution is not making it safer
replacing letter 'x' with '*' will cause havoc if there are formulas like '.23*fee + extra'
0
 
peprCommented:
@Nas-Banov: No problem with your answer ;) I believe that Evan is novice only concerning Python, not a novice programmer.

The file is closed automatically because the program is terminated, not because it leaves the for loop. Or possibly you may be lucky and the file object is garbage collected. However, it is not a good practice. The with was designed for exactly such situations. How did you check that the file is closed by the for loop?

I did mention the problem with `x` (that it can be a part of an identifier). This is also reason why formulas should be specifically interpreted.

When the substitution of Price in the formula is done, one can add a check that the formula after the substitution does not contain any letters. In such case, the eval is quite safe.
0
 
Nas-BanovCommented:
@pepr - i like your idea about checking for remaining letters re securing eval! The file will be closed because its reference count becomes 0 after the loop and then it gets immediately disposed, destructor called etc. Python uses reference counting (plus mark&sweep for cyclic structures). Google it - but let's end discussion here.

As a practical matter, i advise @Evan Cutler to ignore our nit-picking here, just look at out first answers and make use of it.
0
 
Evan CutlerAuthor Commented:
Guys,
This was the best discussion ever!!

Thank you so much for your help.  
These solutions worked perfectly and I got my script started.
When I submitted my script to my client, he came at me and told me that the categories might fuzzy match the lookup categories.
This means I have titles that might be part of titles in both places.
But that question I'll give another 500 for.
Thanks guys.
Evan
0

Featured Post

New feature and membership benefit!

New feature! Upgrade and increase expert visibility of your issues with Priority Questions.

  • 3
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now