
# ===================
# pigen.py
# ===================

# ====================================================
# Calculating Pi using Liebniz Formula and Generator
# ====================================================

# An infinite series can be useful even if you will never end up generating an infinite number of values.

# Example:
# We will calculate Pi using an infinite series using Liebniz formula:

# https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80
# In mathematics, the Leibniz formula for Pi, states that:
# 1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 ... = Pi/4

# We will calculate the value of Pi using an infinite generator

# ==========
# Challenge
# ==========

# Create a generator to return an infinite sequence of odd numbers, starting at 1
# Print the first 100 numbers, to check that the generator is working correctly
# NOTE that 100 is just for testing. We will need more than 100 numbers  but we don't know how many
# Thats why we are creating our own generator instead of just using a range.

# First we create the generator called oddnumbers

def oddnumbers():
    n = 1   # initialize it to 1
    while True:
        yield n  # First yield is 1
        n += 2   # Will keep adding 2 to generate odd numbers only


# Test code to test the generator:
# ================================
# Then we call our generator and assign it to variable odds.

odds = oddnumbers()

# Optional test
# This shows you that odds is a <generator object oddnumbers at 0x008BDED0>
# print(odds)

# we will now print this using range 100 and print next number in the generator object odds
# results show 1, 3, 5, 7 etc up to 199. All odd numbers
# NOTE: we need to give range because oddsgenerator is an infinite generator and would continue running if we don't give it a stop

for i in range(100):
    print(next(odds))






# ========================================================
# Calculating Pi
# ========================================================


# How to use above odd number generator to calculate Pi
#
# In this case, we don't need the test code above, so we delete it.
# We are going to add the _=input(enter) sections to understand how the program works.
# Then remove the _=input(enter) in the next section after we understood how the program works


def oddnumbers():
    n = 1   # initialize it to 1
    while True:
        yield n  # First yield is 1
        n += 2   # Will keep adding 2 to generate odd numbers only

# Now we write function (pi_series) to calculate Pi using odd numbers generated by oddnumbers function
# NOTE that under the while loop, when using conventional code, we may want to keep track of whether number is added or subtracted.
# But this is unnecessary because a generator will continue from where it left after yielding a value.
# So we can do the addition part, yield, then do the subtraction part, and yield again

def pi_series():
    odds = oddnumbers()  # Calls oddsnumbers functions to generate odd numbers and assign them to odds
    approximation = 0    # This initializes the first number to 0
    while True:

        _=input("First approximation and Initial next(odds):")
        print("Initial approximation = {}".format(approximation))
        # print("Initial next(odds) = {}".format(next(odds)))  # NOTE: we comment this out because it was calling next(odds) and going to next odd
        print("="*20)

        approximation += (4 / next(odds))  # new approximation is 0 + 4/1 = 4.0
        yield approximation                # Yields 4.0

        _=input("First updated approximation yielded after Plus:")
        print("First updated approximation = {}".format(approximation))
        print("="*20)

        approximation -= (4 / next(odds))   # Second approximation = 4 - 4/3 = 2.666
        yield approximation                 # Yields 2.666

        _=input("Second approximation and Second next(odds):")
        print("Second approximation = {}".format(approximation))
        # print("Second next(odds) = {}".format(next(odds)))  # NOTE: we comment this out because it was calling next(odds) and going to next odd
        print("="*20)

# We now call def pi_series to calculate pi

approx_pi = pi_series()

# We print it using a for loop and give it a range

for x in range(2):   # NOTE: The bigger range you give, the more accurate the approximation comes closer to 3.14
    # print(next(approx_pi))
    print("pi_series results = {}".format(next(approx_pi)))
    print("="*20)




# ======================================================
# Calculate Pi
# ======================================================

# We remove the _input(enter) code here

def oddnumbers():
    n = 1   # initialize it to 1
    while True:
        yield n  # First yield is 1
        n += 2   # Will keep adding 2 to generate odd numbers only

# Now we write function (pi_series) to calculate Pi using odd numbers generated by oddnumbers function

def pi_series():
    odds = oddnumbers()  # Calls oddsnumbers functions to generate odd numbers and assign them to odds
    approximation = 0    # This initializes the first number to 0
    while True:
        approximation += (4 / next(odds))
        yield approximation
        approximation -= (4 / next(odds))
        yield approximation

# We now call def pi_series to calculate pi

approx_pi = pi_series()

# We print it using a for loop and give it a range

for x in range(10):   # NOTE: The bigger range you give, the more accurate the approximation comes closer to 3.14
    print(next(approx_pi))
    # print("pi_series results = {}".format(next(approx_pi)))



# ==================================================
# Importance of using Infinite Generator
# ==================================================

# As we can see in above code, we are now getting a more accurate value of Pi as we increase the range
# The value of using infinite generator is when you don't know what range you want to achieve your goal
# For example, in Google indexing, they send robots to index websites, but they initially don't know how
# many websites are there to index, so the robots run using infinite generator until they reach all the websites available.
# When they start reaching websites that are already index, then the generator terminates

