Names
What should your code do? Neither your code (by good names) nor your documentation inside the code (docstrings) nor your question does state the task in detail. So I cannot tell if your code is delivering the right results (but I don't think it does). So fix your names, there is no need to save characters. The bigger the scope the better the names have to be. Also add some docstring with a more detailed description of the task the function tries to solve.
Correctness of the results
>>> _ = eg_fr(7, 133)
7/133 = 1/19
>>> _ = eg_fr(7, 132)
7/132 = 1/19
That surprises at least me. At least the print statement lies to me. there is no equality in the second expression
Functions returning values vs. I/O
>>> eg_fr(7, 133)
7/133 = 1/19
['1/19']
Functions usually should either return values and have no other side effects like print
or they should do tasks with side effect and return nothing (None)
or success/error codes only. Your function does both. Either call the function print_egypti...
and remove the return value or do remove the print from inside the function.
I strongly suggest to do printing outside the algorithm to have a testable function that is not cluttering the output.
Printing
A single test is no test.
If you did
if __name__ == '__main__':
eg_fr(7, 133)
eg_fr(7, 132)
you would have noticed not only the interesting results but also that you missed a line feed.
Looping
In Python you never do
for i in range(len(something)):
do(something[i])
You always do
for element in something:
do(element)
in your case
for element in ls_str[:-1]:
print('', element, '+ ', end='')
that is less error prone.
String concatenation
You do concatenate the string output via printing in a loop. Use join()
here. Instead of
for element in ls_str[:-1]:
print('', element, '+ ', end='')
print(ls_str[-1])
you do
print(' + '.join(ls_str))
Testability
As mentioned before we split algorithm from I/O to get a nice and testable function
def egyptian_fractions(nominator, denominator):
# [...]
def print_egyptian_fractions(nominator, denominator):
print('{}/{} = '.format(nominator, denominator), end='')
print(' + '.join(egyptian_fractions(nominator, denominator)))
Go for purity
We squeeze out all stuff from the algorithm that does not belong there. That is the string representation and also the nominator which is always 1
. The final algorithm shall return only a list of denominators.
The remaining algorithm is
def egyptian_fractions_(nominator, denominator):
denominators = []
fraction = nominator / denominator
total = 0
for i in range(1, 1000):
while 1/i + total <= fraction:
denominators.append(i)
total += 1/i
if total >= fraction:
break
return denominators
The fitting print function is
def print_egyptian_fractions(nominator, denominator):
ls_str = ['1/' + str(e) for e in egyptian_fractions(nominator, denominator)]
print('{}/{} = '.format(nominator, denominator), end='')
print(' + '.join(ls_str))
Algorithm
Depending on your task, I cannot know for sure, your algorithm is erroneous and inefficient.
- You do prepare a lengthy fractions for a later test where you could calculate straightforward.
- you do prepare string representations where you could leave this to the output functions
- You limited yourself to a smallest fraction of
1/1000
. Why?
- You use float numbers for calculation and testing inside your algorithm and will get precision errors
I should mention there is the module fractions
that is providing fractional math. As this is an exercise we will try to do it by hand.
First we try to lay out the algorithm
We try to find the biggest fraction f that is smaller or equal to the given fraction g
We keep the denominator from the fraction f
3a. we subtract fraction f from the given fraction g to get the remainder
So far this is what you did, now the change
3b. we try to find a fractional expression for the remainder (nominator, denominator)
- we repeat from step 1
For step 1 you did a simple loop testing to find the biggest fraction. We do it a little more advanced, we divide the given denominator by the given nominator to find the new egyptian denominator. We use integer division for that step to work without precision loss. If the remainder of this division is 0 we are done, if not we have to increment the new denominator.
For step 3 we do some basic math to do the subtraction with the help of a common denominator to avoid precision loss and stick to a nominator/denominator fraction format.
What do we do for step 4? the task is the very same - solve the same problem for a new pair of nominator/denominator. We do a simple recursion and call the same function again appending that result to that from the current recursion depth.
def egyptian_fractions(nominator, denominator):
if nominator == 1:
return [denominator] # exactly fitting fraction (was normalized)
else:
d = denominator // nominator
r = denominator % nominator
if r == 0:
return [d] # exactly fitting fraction (wasn't normalized)
else:
d += 1 # increment to get the biggest fitting fraction
# keep the result and add the result of the subproblem
return [d] + egyptian_fractions(nominator*d-denominator, denominator*d)