<jupyter_start><jupyter_text>Sorting Iterables<jupyter_code>import random
random.seed(0)
for _ in range(10):
    print(random.randint(1,10))
class RandomInts:
    def __init__(self, length,*,seed=0,lower=0,upper=10):
        self.length = length
        self.seed = seed
        self.lower = lower
        self.upper = upper
    
    def __len__(self):
        return self.length
    def __iter__(self):
        return self.RandomIterator(self.length,seed = self.seed,lower = self.lower,upper = self.upper)
    
    class RandomIterator:
        def __init__(self,length,*,seed,lower,upper):
            self.length = length
            self.lower = lower
            self.upper = upper
            self.num_requests = 0
            random.seed(seed)
            
        def __iter__(self):
            return self
        
        def __next__(self):
            if self.num_requests >= self.length:
                raise StopIteration
            else:
                result = random.randint(self.lower,self.upper)
                self.num_requests += 1
                return result
randoms = RandomInts(10)
for num in randoms:
    print(num)
for num in randoms:
    print(num)
randoms = RandomInts(10,seed=None)
for num in randoms:
    print(num)
for num in randoms:
    print(num)
randoms = RandomInts(10)
list(randoms)
sorted(randoms,reverse=True)<jupyter_output><empty_output><jupyter_text>Iter() Function ![title](imgs/69.png)![title](imgs/70.png)![title](imgs/71.png)![title](imgs/72.png)![title](imgs/73.png)![title](imgs/74.png)![title](imgs/75.png)<jupyter_code>l = [1,2,3,4,5]
l_iter = iter(l)
type(l_iter)
next(l_iter)
next(l_iter)
class Squares:
    def __init__(self,length):
        self._length = length
        
    def __len__(self):
        return self._length
    
    def __getitem__(self,i):
        if i >=self._length:
            raise IndexError
        else:
            return i**2
sq = Squares(5)
for i in sq:
    print(i)
sq_iter = iter(sq)
type(sq_iter)
'__iter__' in dir(sq_iter)
class SquaresIterator: # <------------ can be any sequence type
    def __init__(self,squares):
        self._squares = squares
        self._i = 0
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self._i >= len(self._squares):
            raise StopIteration
        else:
            result = self._squares[self._i]
            self._i += 1
            return result
    
sq = Squares(5)
sq_iter = SquaresIterator(sq)
next(sq_iter)
next(sq_iter)
class SimpleIter:
    def __init_(self):
        pass
    
    def __iter__(self):
        return 'Nope'
s = SimpleIter() # <-----------------------dunder iter method needs to be implemented
iter(s)
def is_iter(obj):
    try:
        iter(obj)
        return True
    except TypeError:
        return False 
is_iter(s)
is_iter(Squares(5))
obj = 100
if is_iter(obj):
    for i in obj:
        print(i)
else:
    print('Error: obj is not iterable')<jupyter_output>Error: obj is not iterable<jupyter_text>![title](imgs/79.png)```while True:    val = countdown()    if val == 0:        break    else:        print(val)``` ![title](imgs/76.png)![title](imgs/77.png)![title](imgs/78.png)  Iterating Callables<jupyter_code>def counter():
    i = 0
    
    def inc():
        nonlocal i
        i += 1
        return i
    return inc
cnt = counter()
cnt()
cnt()
class CounterIterator:
    def __init__(self,counter_callable):
        self.counter_callable = counter_callable
        
    def __iter__(self):
        return self
    
    def __next__(self):
        return self.counter_callable()
cnt = counter()
cnt_iter = CounterIterator(cnt)
for _ in range(5):
    print(next(cnt_iter))
class CounterIterator:
    def __init__(self,counter_callable, sentinel):
        self.counter_callable = counter_callable
        self.sentinel = sentinel
        
    def __iter__(self):
        return self
    
    def __next__(self):
        result =  self.counter_callable()
        if result == self.sentinel:
            raise StopIteration
        else:
            return result
       
cnt = counter()
cnt_iter = CounterIterator(cnt,5)
for c in cnt_iter:
    print(c)
next(cnt_iter)
class CounterIterator:
    def __init__(self,counter_callable, sentinel):
        self.counter_callable = counter_callable
        self.sentinel = sentinel
        self.is_consumed = False
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.is_consumed:
            raise StopIteration
        else:
            result =  self.counter_callable()
            if result == self.sentinel:
                self.is_consumed = True
                raise StopIteration
            else:
                return result
       
cnt = counter()
cnt_iter = CounterIterator(cnt,5)
for c in cnt_iter:
    print(c)
help(iter)
cnt = counter()
cnt_iter = iter(cnt,5)
for i in cnt_iter:
    print(i)
next(cnt_iter)
import random
random.seed(0)
for i in range(10):
    print(i,random.randint(0,10))
random_iter = iter(lambda:random.randint(0,10),8)
random.seed(0)
for num in random_iter:
    print(num)
def countdown(start=10):
    def run():
        nonlocal start
        start -= 1
        return start
    return run
takeoff = countdown(10)
for _ in range(15):
    print(takeoff())
takeoff = countdown(10)
takeoff_iter = iter(takeoff,-1)
for i in takeoff_iter:
    print(i)<jupyter_output>9
8
7
6
5
4
3
2
1
0<jupyter_text>Delegating Iterators<jupyter_code>from collections import namedtuple

Person = namedtuple('Person','first last')
class PersonNames:
    def __init__(self,persons):
        try:
            self._persons = [person.first.capitalize()+' '+person.last.capitalize()for person in persons]
        except (TypeError, AttributeError):
            self._persons = []
    def __iter__(self):
        return iter(self._persons)
persons = [Person('michael','palin'),Person('eric','johnson'),Person('alla','cruz')]
person_names = PersonNames(persons)
person_names._persons
for name in person_names:
    print(name)<jupyter_output><NAME>
<NAME>
<NAME>
{'fraction_non_alphanumeric': 0.11542894780174542, 'fraction_numerical': 0.015149020253581426, 'mean_word_length': 2.476817401259302, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 20, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '435054', 'license': 'MIT', 'n_tokens_mistral': 2049, 'n_tokens_neox': 1969, 'n_words': 449, 'path': 'Iterating Collections Part 2.ipynb', 'repo_name': 'nikgun1984/Python-Programming-Part-2', 'size': '6089'}