<jupyter_start><jupyter_text>Chapter 9: IteratorsHere we present all code from this chapter, so that you can reproduce results by your own.We start by importing all general modules<jupyter_code>from scipy import *
import itertools<jupyter_output><empty_output><jupyter_text>Generators Example: Odd numbersA generator for odd numbers smaller than $n$ could be defined like this:<jupyter_code>def odd_numbers(n):
    "generator for odd numbers less than n"
    for k in range(n):
        if k % 2 == 1:
            yield k<jupyter_output><empty_output><jupyter_text>Then you can use it as:<jupyter_code>g = odd_numbers(10)
for k in g:
    print(k,end=' ') # do something with k<jupyter_output>1 3 5 7 9<jupyter_text>or even as:<jupyter_code>for k in odd_numbers(10):
    print(k,end=' ') # do something with k<jupyter_output>1 3 5 7 9<jupyter_text>Iterators are DisposableNote that an iterable object is able to create new iterators as many time as necessary.Let us examine the case of a list:<jupyter_code>L = ['a', 'b', 'c']
iterator = iter(L)
list(iterator) # ['a', 'b', 'c']
list(iterator) # [] empty list, because the iterator is exhausted

new_iterator = iter(L) # new iterator, ready to be used
list(new_iterator) # ['a', 'b', 'c']<jupyter_output><empty_output><jupyter_text>Iterator Tools<jupyter_code>A = ['a', 'b', 'c']
for iteration, x in enumerate(A):
    print(iteration, x)

A = [0, 1, 2]
for elt in reversed(A):
    print(elt)
for iteration in itertools.count():
    if iteration > 5:
        break # without this, the loop goes on forever
    print("integer {}".format(iteration))<jupyter_output>integer 0
integer 1
integer 2
integer 3
integer 4
integer 5<jupyter_text>Let us find some odd numbers by combining `islice` with an infinite generator. First we modify the generator for odd numbers so that it becomes an infinite generator:<jupyter_code>def odd_numbers():
    k=-1
    while True:
        k+=1
        if k%2==1:
            yield k<jupyter_output><empty_output><jupyter_text>Then, we use it with `islice` to get a list of some odd numbers:<jupyter_code>list(itertools.islice(odd_numbers(),10,30,8))<jupyter_output><empty_output><jupyter_text>Generators of Recursive Sequences Fibonacci Numbers<jupyter_code>def fibonacci(u0, u1):
    """
    Infinite generator of the Fibonacci sequence.
    """
    yield u0
    yield u1
    while True:
        u0, u1 = u1, u0+u1
        yield u1
list(itertools.islice(fibonacci(0, 1), 15))<jupyter_output><empty_output><jupyter_text>Example: Arithmetic Geometric Mean<jupyter_code>def arithmetic_geometric_mean(a, b):
    """
    Generator for the arithmetic and geometric mean
    a, b initial values
    """ 
    while True:	# infinite loop
        a, b = (a+b)/2, sqrt(a*b)
        yield a, b
from itertools import islice
def elliptic_integral(k, tolerance=1e-5, maxiter=100):
    """
    Compute an elliptic integral of the first kind.
    """
    a_0, b_0 = 1., sqrt(1-k**2)
    for a, b in islice(arithmetic_geometric_mean(a_0, b_0), maxiter):
        if abs(a-b) < tolerance:
            return pi/(2*a)
    else:
        raise Exception("Algorithm did not converge")
def pendulum_period(L, theta, g=9.81):
    return 4*sqrt(L/g)*elliptic_integral(sin(theta/2))
L=1.5
theta=pi/4
print('Period of pendulum with length {} and initial angle theta {} degree is'.format(L,theta*180/(2*pi)),
                                                                                 pendulum_period(L,theta))<jupyter_output>Period of pendulum with length 1.5 and initial angle theta 22.5 degree is 2.5551310824<jupyter_text>Convergence Acceleration<jupyter_code>def Euler_accelerate(sequence):
    """
    Accelerate the iterator in the variable `sequence`.
    """
    s0 = next(sequence) # Si
    s1 = next(sequence) # Si+1
    s2 = next(sequence) # Si+2
    while True:
        yield s0 - ((s1 - s0)**2)/(s2 - 2*s1 + s0)
        s0, s1, s2 = s1, s2, next(sequence)
def pi_series():
    sum = 0.
    j = 1
    for i in itertools.cycle([1, -1]):
        yield sum
        sum += i/j
        j += 2
N=10
list(itertools.islice(Euler_accelerate(pi_series()), N))<jupyter_output><empty_output><jupyter_text>Storing generated values when an exception is raised<jupyter_code>import itertools
def power_sequence(u0):
    u = u0
    while True:
        yield u
        u = u**2
generator = power_sequence(2.)
L = []
for iteration in range(20):
    try:
        L.append(next(generator))
    except Exception:
        break
print(L)<jupyter_output>[2.0, 4.0, 16.0, 256.0, 65536.0, 4294967296.0, 1.8446744073709552e+19, 3.402823669209385e+38, 1.157920892373162e+77, 1.3407807929942597e+154]<jupyter_text>When Iterators Behave as Lists Generator expressions<jupyter_code>g = (n for n in range(1000) if not n % 100)
print(list(g))
sum(n for n in range(1000) if not n % 100)<jupyter_output><empty_output><jupyter_text>$\zeta$-function<jupyter_code>def generate_zeta(s):
    for n in itertools.count(1):
        yield 1/n**s

def zeta(N, s):
    return sum(x for x in itertools.islice(generate_zeta(s), N))

zeta(1000,2)<jupyter_output><empty_output><jupyter_text>Iterator Objects ODE store<jupyter_code>class OdeStore:
    """Class to store results of ode computations"""
    def __init__(self, data):
        "data is a list of the form [[t0, u0], [t1, u1],...]"
        self.data = data
    def __iter__(self):
        "By default, we iterate on the values u0, u1,..."
        for t, u in self.data:
            yield u

store = OdeStore([[0, 1], [0.1, 1.1], [0.2, 1.3]])
for u in store:
    print(u)
list(store)<jupyter_output>1
1.1
1.3
{'fraction_non_alphanumeric': 0.11915427342770113, 'fraction_numerical': 0.048020068088156245, 'mean_word_length': 3.6056105610561056, 'pattern_counts': {'":': 0, '<': 56, '<?xml version=': 0, '>': 56, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '319116', 'license': 'MIT', 'n_tokens_mistral': 2152, 'n_tokens_neox': 1976, 'n_words': 566, 'path': '9-iterators/9-Iterators.ipynb', 'repo_name': 'karant17/Scientific-Computing-with-Python-3', 'size': '5581'}