Python Forum
Script optimisation and style help
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Script optimisation and style help
#1
As a long-time Perl coder, I've picked up loads of optimisations and style tips that make scripts easier to understand, shorter, and more efficient.

I occasionally dabble in Python and typically end up brute-forcing some code together using the basic Python capabilities, often thinking how I'd write the script in Perl and then working out the Python equivalent methods. I recently wrote a script to reverse-engineer sample sizes from published percentages, and I suspect it's particularly bad since I directly translated some JavaScript into Python for one of the key functions.

If anyone is able to offer advice on how to improve the script and make it more optimised and Pythonic, I'd be very grateful! I'm working with Python 3.7 simply because it's recent, and not because of any particular attachment to Python 3 over Python 2.

# Determine minimum likely values based on provided percentages

import math

digit_accuracy = 2
results = {}

#percentages = [ 4.71, 20.00, 42.35, 32.94 ]
percentages = [ 8.57, 5.71, 2.86, 20.00, 14.29, 17.14, 20.00, 5.71, 5.71 ]

# Calculate Greatest Common Divisor of two numbers
# Based on code from https://www.programiz.com/python-programming/examples/lcm
def gcd(x, y):
    while(y):
        x, y = y, x % y
    return x

# Calculate Least Common Multiple of two numbers
# Based on code from https://www.programiz.com/python-programming/examples/lcm
def lcm(x, y):
    return ((x * y) // gcd(x, y))

# Use continued fraction method to calculate simplest fraction that represents provided decimal at given precision (decimal places)
# Based on the JavaScript version at http://jonisalonen.com/2012/converting-decimal-numbers-to-ratios/
def estimate_fraction(decimal_value, decimal_precision):
    #print("Estimating fractions for", decimal_value)
    desired_result = round(decimal_value * 100, decimal_precision)
    h1 = 1
    h2 = 0
    k1 = 0
    k2 = 1
    b = decimal_value
    while True:
        a = math.floor(b)
        aux = h1
        h1 = (a * h1) + h2
        h2 = aux
        aux = k1
        k1 = (a * k1) + k2
        k2 = aux

        rounded_result = round(((h1 * 100) / k1), decimal_precision)
        if (rounded_result == desired_result):
            break

        b = 1 / (b - a)

    return [h1, k1]

# ------------------- MAIN -------------------

def main():
    lowest_denominator = 0
    for item in percentages:
        # Find a fraction to estimate the current percentage to the given number of decimal places accuracy
        [numerator, denominator] = estimate_fraction((item / 100), digit_accuracy)
        results[item] = [numerator, denominator]
        # Find the lowest common multiple for the fraction denominators up to this point
        if (lowest_denominator == 0):
            lowest_denominator = denominator
        else:
            lowest_denominator = lcm(denominator, lowest_denominator)

    running_total = 0
    for x in percentages:
        [numerator, denominator] = results[x]
        # Convert all fractions to a common denominator (which should be the sample size)
        if (denominator != lowest_denominator):
            multiplier = lowest_denominator / denominator
            numerator *= multiplier
            denominator *= multiplier
        running_total += numerator

        # Display summary of the calculated values for this entry
        calculated_percentage = round(((numerator * 100) / denominator), digit_accuracy)
        print(int(numerator), " / ", int(denominator), " = ", calculated_percentage, "% (Requested: ", x, "%)", sep="")

    # Print some summary information to help determine if the estimated sample size is sensible
    print("\nTotal", int(running_total), "items represented of", lowest_denominator)
    print("Percentages sum to ", round(sum(percentages), digit_accuracy), "%", sep="")
    

# ---------- ENTRY POINT ----------

if __name__ == '__main__':
    main()
Reply
#2
I just have style comments. First, your comments before the functions should be done as triple quoted strings right after the def line. That way they become __docstring__ attributes of the function. That is what is displayed when you do help(function) in the interpreter. Second, learn string formatting, either the format method of strings, or the even newer f-string syntax (Python 3.6+).
Craig "Ichabod" O'Brien - xenomind.com
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures
Reply
#3
Also no need of brackets, e.g. in following cases
  • while(y)
  • if (rounded_result == desired_result):
  • [numerator, denominator] = estimate_fraction....
  • if (lowest_denominator == 0):
  • [numerator, denominator] = results...
  • if (denominator != lowest_denominator):
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply
#4
(Feb-18-2019, 04:08 PM)ichabod801 Wrote: I just have style comments. First, your comments before the functions should be done as triple quoted strings right after the def line. That way they become __docstring__ attributes of the function. That is what is displayed when you do help(function) in the interpreter. Second, learn string formatting, either the format method of strings, or the even newer f-string syntax (Python 3.6+).
Fantastic, thank you!

(Feb-18-2019, 04:28 PM)buran Wrote: Also no need of brackets, e.g. in following cases
  • while(y)
  • if (rounded_result == desired_result):
  • [numerator, denominator] = estimate_fraction....
  • if (lowest_denominator == 0):
  • [numerator, denominator] = results...
  • if (denominator != lowest_denominator):
Those are definitely a Perl-ism Big Grin . Thank you for the comment!
Reply
#5
by the way, if you don't want to implement gcd(), there is math.gcd()
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020