python, power function using one loop - python

I tried to solve a problem, writing a power by function that does the same job as the operator ** (by python for example)
after I solve it, I got another assignment:
I'm allowed to use only one loop and only one if\else.
I would love for some insight
I'm a beginer and have no clue how to go further.
my code was:
...
def power(x, y):
s = x
if y > 0:
for i in range (1, y):
s = s * x
elif (y < 0):
for i in range (y, -1):
s = s * x
s = 1 / s
else:
s = 1
return s
print(power(3, 5))
print(power(3, -5))
print(power(3, 0))

Are you allowed to use the abs function?
from typing import Union
def power(x: Union[float, int], y: int) -> Union[float, int]:
s: Union[float, int] = 1
for _ in range(abs(y)):
s *= x
if y < 0:
s = 1 / s
return s
assert power(3, 5) == 243
assert 0.0040 < power(3, -5) < 0.0042
assert power(3, 0) == 1

A way I would do it is creating a function that accepts a number and an exponent.
Then I would create a list with exp amount of that number. Multiply everything in the list together to get the result:
def power(num, exp):
prod = 1
powers = [num] * exp
for n in powers:
prod *= n
return prod

Related

A Normal Distribution Calculator

so im trying to make a program to solve various normal distribution questions with pure python (no modules other than math) to 4 decimal places only for A Levels, and there is this problem that occurs in the function get_z_less_than_a_equal(0.75):. Apparently, without the assert statement in the except clause, the variables all get messed up, and change. The error, i'm catching is the recursion error. Anyways, if there is an easier and more efficient way to do things, it'd be appreciated.
import math
mean = 0
standard_dev = 1
percentage_points = {0.5000: 0.0000, 0.4000: 0.2533, 0.3000: 0.5244, 0.2000: 0.8416, 0.1000: 1.2816, 0.0500: 1.6440, 0.0250: 1.9600, 0.0100: 2.3263, 0.0050: 2.5758, 0.0010: 3.0902, 0.0005: 3.2905}
def get_z_less_than(x):
"""
P(Z < x)
"""
return round(0.5 * (1 + math.erf((x - mean)/math.sqrt(2 * standard_dev**2))), 4)
def get_z_greater_than(x):
"""
P(Z > x)
"""
return round(1 - get_z_less_than(x), 4)
def get_z_in_range(lower_bound, upper_bound):
"""
P(lower_bound < Z < upper_bound)
"""
return round(get_z_less_than(upper_bound) - get_z_less_than(lower_bound), 4)
def get_z_less_than_a_equal(x):
"""
P(Z < a) = x
acquires a, given x
"""
# first trial: brute forcing
for i in range(401):
a = i/100
p = get_z_less_than(a)
if x == p:
return a
elif p > x:
break
# second trial: using symmetry
try:
res = -get_z_less_than_a_equal(1 - x)
except:
# third trial: using estimation
assert a, "error"
prev = get_z_less_than(a-0.01)
p = get_z_less_than(a)
if abs(x - prev) > abs(x - p):
res = a
else:
res = a - 0.01
return res
def get_z_greater_than_a_equal(x):
"""
P(Z > a) = x
"""
if x in percentage_points:
return percentage_points[x]
else:
return get_z_less_than_a_equal(1-x)
print(get_z_in_range(-1.20, 1.40))
print(get_z_less_than_a_equal(0.7517))
print(get_z_greater_than_a_equal(0.1000))
print(get_z_greater_than_a_equal(0.0322))
print(get_z_less_than_a_equal(0.1075))
print(get_z_less_than_a_equal(0.75))
Since python3.8, the statistics module in the standard library has a NormalDist class, so we could use that to implement our functions "with pure python" or at least for testing:
import math
from statistics import NormalDist
normal_dist = NormalDist(mu=0, sigma=1)
for i in range(-2000, 2000):
test_val = i / 1000
assert get_z_less_than(test_val) == round(normal_dist.cdf(test_val), 4)
Doesn't throw an error, so that part probably works fine
Your get_z_less_than_a_equal seems to be the equivalent of NormalDist.inv_cdf
There are very efficient ways to compute it accurately using the inverse of the error function (see Wikipedia and Python implementation), but we don't have that in the standard library
Since you only care about the first few digits and get_z_less_than is monotonic, we can use a simple bisection method to find our solution
Newton's method would be much faster, and not too hard to implement since we know that the derivative of the cdf is just the pdf, but still probably more complex than what we need
def get_z_less_than_a_equal(x):
"""
P(Z < a) = x
acquires a, given x
"""
if x <= 0.0 or x >= 1.0:
raise ValueError("x must be >0.0 and <1.0")
min_res, max_res = -10, 10
while max_res - min_res > 1e-7:
mid = (max_res + min_res) / 2
if get_z_less_than(mid) < x:
min_res = mid
else:
max_res = mid
return round((max_res + min_res) / 2, 4)
Let's test this:
for i in range(1, 2000):
test_val = i / 2000
left_val = get_z_less_than_a_equal(test_val)
right_val = round(normal_dist.inv_cdf(test_val), 4)
assert left_val == right_val, f"{left_val} != {right_val}"
# AssertionError: -3.3201 != -3.2905
We see that we are losing some precision, that's because the error introduced by get_z_less_than (which rounds to 4 digits) gets propagated and amplified when we use it to estimate its inverse (see Wikipedia - error propagation for details)
So let's add a "digits" parameter to get_z_less_than and change our functions slightly:
def get_z_less_than(x, digits=4):
"""
P(Z < x)
"""
res = 0.5 * (1 + math.erf((x - mean) / math.sqrt(2 * standard_dev ** 2)))
return round(res, digits)
def get_z_less_than_a_equal(x, digits=4):
"""
P(Z < a) = x
acquires a, given x
"""
if x <= 0.0 or x >= 1.0:
raise ValueError("x must be >0.0 and <1.0")
min_res, max_res = -10, 10
while max_res - min_res > 10 ** -(digits * 2):
mid = (max_res + min_res) / 2
if get_z_less_than(mid, digits * 2) < x:
min_res = mid
else:
max_res = mid
return round((max_res + min_res) / 2, digits)
And now we can try the same test again and see it passes

New factorial function without 0

So, the standard factorial function in python is defined as:
def factorial(x):
if x == 0:
return 1
else:
return x * factorial(x-1)
Since n! := n * (n-1) * ... * 1, we can write n! as (n+1)! / (n+1). Thus 0! = 1 and we wouldn't need that if x == 0. I tried to write that in python to but I didn't work. Can you guys help me?
Since this is a recursive function (return x * factorial(x-1)) you must have an end condition (if x == 0:).
It is true that n! == (n+1)! / (n+1) and you could change your recursive call to :
def factorial(x):
return factorial(x+1) / (x+1)
But that again would have no end condition -> endless recursion (you will calling the next (n+1)! and than the (n+2)! and so on forever (or until you'll get an exception)).
BTW, you can have the condition stop your execution at 1:
if x == 1:
return 1
You wouldn't want to use recursive function for things that are not limited, hence I suggest doing a little bit importing from the standard library
from functools import reduce
import operator
def fact(x):
if not isinstance(x, int) or x <= 0:
raise Exception("math error")
else:
return reduce(operator.mul, range(1, x + 1), 1)
print(fact("string"))
print(fact(-5))
print(fact(0))
print(fact(5))
Just realized that there is no need for a hustle like that:
def fact2(x):
if not isinstance(x, int) or x <= 0:
Exception("math error")
else:
y = 1
while x > 1:
y *= x
x -= 1
return y

Python function returning first value twice

I've written this function to calculate sin(x) using Taylor series to any specified degree of accuracy, 'N terms', my problem is the results aren't being returned as expected and I can't figure out why, any help would be appreciated.
What is am expecting is:
1 6.28318530718
2 -35.0585169332
3 46.5467323429
4 -30.1591274102
5 11.8995665347
6 -3.19507604213
7 0.624876542716
8 -0.0932457590621
9 0.0109834031461
What I am getting is:
1 None
2 6.28318530718
3 -35.0585169332
4 46.5467323429
5 -30.1591274102
6 11.8995665347
7 -3.19507604213
8 0.624876542716
9 -0.0932457590621
Thanks in advance.
def factorial(x):
if x <= 1:
return 1
else:
return x * factorial(x-1)
def sinNterms(x, N):
x = float(x)
while N >1:
result = x
for i in range(2, N):
power = ((2 * i)-1)
sign = 1
if i % 2 == 0:
sign = -1
else:
sign = 1
result = result + (((x ** power)*sign) / factorial(power))
return result
pi = 3.141592653589793
for i in range(1,10):
print i, sinNterms(2*pi, i)
I see that you are putting the return under the for which will break it out of the while loop. You should explain if this is what you mean to do. However, given the for i in range(1,10): means that you will ignore the first entry and return None when the input argument i is 1. Is this really what you wanted? Also, since you always exit after the calculation, you should not do a while N > 1 but use if N > 1 to avoid infinite recursion.
The reason why your results are off is because you are using range incorrectly. range(2, N) gives you a list of numbers from 2 to N-1. Thus range(2, 2) gives you an empty list.
You should calculate the range(2, N+1)
def sinNterms(x, N):
x = float(x)
while N >1:
result = x
for i in range(2, N):
Your comment explains that you have the lines of code in the wrong order. You should have
def sinNterms(x, N):
x = float(x)
result = x
# replace the while with an if since you do not need a loop
# Otherwise you would get an infinite recursion
if N > 1:
for i in range(2, N+1):
power = ((2 * i)-1)
sign = 1
if i % 2 == 0:
sign = -1
# The else is not needed as this is the default
# else:
# sign = 1
# use += operator for the calculation
result += (((x ** power)*sign) / factorial(power))
# Now return the value with the indentation under the if N > 1
return result
Note that in order to handle things set factorial to return a float not an int.
An alternative method that saves some calculations is
def sinNterms(x, N):
x = float(x)
lim = 1e-12
result = 0
sign = 1
# This range gives the odd numbers, saves calculation.
for i in range(1, 2*(N+1), 2):
# use += operator for the calculation
temp = ((x ** i)*sign) / factorial(i)
if fabs(temp) < lim:
break
result += temp
sign *= -1
return result

While loop and tuples

I want to define a function that returns the sum of all the integers between, and including, the two given numbers but I am having trouble with the last line of code, which is down below. For example, the user inputs two integers such as (2,6) and the function will add everything together, 2+3+4+5+6=20. I cannot figure out how to make my function start at input(x) and end at input(y). Also, I want to use the while loop.
def gauss(x, y):
"""returns the sum of all the integers between, and including, the two given numbers
int, int -> int"""
x = int
y = int
counter = 1
while counter <= x:
return (x + counter: len(y))
You can do this using sum as below:
In [2]: def sigma(start, end):
...: return sum(xrange(start, end + 1))
...:
In [3]: sigma(2, 6)
Out[3]: 20
In case you want to use a while loop, you can do:
In [4]: def sigma(start, end):
...: total = 0
...: while start <= end:
...: total += start
...: start += 1
...: return total
...:
In [5]: sigma(2, 6)
Out[5]: 20
def gauss(x, y):
"""returns the sum of all the integers between, and including, the two given numbers
int, int -> int"""
acc = 0 # accumulator
while x <= y:
acc += x
x += 1
return acc
Aside: A better way is to not use sum orrange or loops at all
def gauss(x, y):
return (y * (y + 1) - x * (x - 1)) // 2

Get logarithm without math log python

I need to generate the result of the log.
I know that:
Then I made my code:
def log(x, base):
log_b = 2
while x != int(round(base ** log_b)):
log_b += 0.01
print(log_b)
return int(round(log_b))
But it works very slowly. Can I use other method?
One other thing you might want to consider is using the Taylor series of the natural logarithm:
Once you've approximated the natural log using a number of terms from this series, it is easy to change base:
EDIT: Here's another useful identity:
Using this, we could write something along the lines of
def ln(x):
n = 1000.0
return n * ((x ** (1/n)) - 1)
Testing it out, we have:
print ln(math.e), math.log(math.e)
print ln(0.5), math.log(0.5)
print ln(100.0), math.log(100.0)
Output:
1.00050016671 1.0
-0.692907009547 -0.69314718056
4.6157902784 4.60517018599
This shows our value compared to the math.log value (separated by a space) and, as you can see, we're pretty accurate. You'll probably start to lose some accuracy as you get very large (e.g. ln(10000) will be about 0.4 greater than it should), but you can always increase n if you need to.
I used recursion:
def myLog(x, b):
if x < b:
return 0
return 1 + myLog(x/b, b)
You can use binary search for that.
You can get more information on binary search on Wikipedia:
Binary search;
Doubling search.
# search for log_base(x) in range [mn, mx] using binary search
def log_in_range(x, base, mn, mx):
if (mn <= mx):
med = (mn + mx) / 2.0
y = base ** med
if abs(y - x) < 0.00001: # or if math.isclose(x, y): https://docs.python.org/3/library/math.html#math.isclose
return med
elif x > y:
return log_in_range(x, base, med, mx)
elif x < y:
return log_in_range(x, base, mn, med)
return 0
# determine range using doubling search, then call log_in_range
def log(x, base):
if base <= 0 or base == 1 or x <= 0:
raise ValueError('math domain error')
elif 0 < base < 1:
return -log(x, 1/base)
elif 1 <= x and 1 < base:
mx = 1
y = base
while y < x:
y *= y
mx *= 2
return log_in_range(x, base, 0, mx)
elif 0 <= x < 1 and 1 < base:
mn = -1
y = 1/base
while y > x:
y = y ** 0.5
mn *= 2
return log_in_range(x, base, mn, 0)
import math
try :
number_and_base = input() ##input the values for number and base
##assigning those values for the variables
number = int(number_and_base.split()[0])
base = int(number_and_base.split()[1])
##exception handling
except ValueError :
print ("Invalid input...!")
##program
else:
n1 = 1 ##taking an initial value to iterate
while(number >= int(round(base**(n1),0))) : ##finding the most similer value to the number given, varying the vlaue of the power
n1 += 0.000001 ##increasing the initial value bit by bit
n2 = n1-0.0001
if abs(number-base**(n2)) < abs(base**(n1)-number) :
n = n2
else :
n = n1
print(math.floor(n)) ##output value
Comparison:-
This is how your log works:-
def your_log(x, base):
log_b = 2
while x != int(round(base ** log_b)):
log_b += 0.01
#print log_b
return int(round(log_b))
print your_log(16, 2)
# %timeit %run your_log.py
# 1000 loops, best of 3: 579 us per loop
This is my proposed improvement:-
def my_log(x, base):
count = -1
while x > 0:
x /= base
count += 1
if x == 0:
return count
print my_log(16, 2)
# %timeit %run my_log.py
# 1000 loops, best of 3: 321 us per loop
which is faster, using the %timeit magic function in iPython to time the execution for comparison.
It will be long process since it goes in a loop. Therefore,
def log(x,base):
result = ln(x)/ln(base)
return result
def ln(x):
val = x
return 99999999*(x**(1/99999999)-1)
log(8,3)
Values are nearly equal but not exact.

Categories