<jupyter_start><jupyter_text>Resources - link: [itertools — Iterator Functions — PyMOTW 3](https://pymotw.com/3/itertools/index.html)  Why we use it- Iterator-based code offers **better memory consumption** characteristics than code than using lists.<jupyter_code>from itertools import *<jupyter_output><empty_output><jupyter_text>Merging and Splitting Iterators - chain<jupyter_code>a = [1, 2, 3]
b = ['a', 'b', 'c']
# most common one 
for i in chain(a, b):
    print(i, end=' ')
# for the sake of 'evaluated lazily'
def make_iter_to_chain():
    yield a
    yield b
    
for i in chain.from_iterable(make_iter_to_chain()):
    print(i, end=' ')<jupyter_output>1 2 3 a b c<jupyter_text>- zip<jupyter_code>zip.__name__           # built-in (whatever)

list(zip(a,b))         # "list" the generator 

for i in zip(a,b):     # produces values 'one at a time'
    print(i, end=' ')  # e.g. a[0], b[0]  or  a[3], b[3]
r1 = range(4)  # 0,1,2,3
r2 = range(2)  # 0,1

# zip is exhausted by the shortest iterator
list(zip(r1, r2))
l1 = range(4)
l2 = range(2)

# just "zip" it!  
list(zip_longest(r1, r2))                 # default: fill with None 
list(zip_longest(r1, r2, fillvalue=666))  # name it!<jupyter_output><empty_output><jupyter_text>- islice<jupyter_code># iterable, *args 

list(islice('ABCDEF', 1, 3))        # just like index
list(islice('ABCDEF', 2))           # "2"      for two items
list(islice('ABCDEF', 2, None))     # "2,None" for start from two to end 
list(islice('ABCDEF', 0, None, 2))  # "0,None" as [:], and there's a step "2" !

# range is fun!
list(islice(range(1000), 10))
list(islice(range(0, 201), 0, None, 50))<jupyter_output><empty_output><jupyter_text>- tee<jupyter_code># tuple of n independent iterators 

r = islice(range(5), 5)

# repeat (sort of)
i1, i2, i3, i4 = tee(r, 4)  # default: 2

list(i1), list(i2), 
list(i3), list(i4)
# the new iterators (i1,i2) share their input (r)
# so ...

r = islice(range(5), 5)
i1, i2 = tee(r)        

print('r: ',end=' ')
for i in r:
    print(i, end=' ') 
    if i > 1:           # [0,1,2] was consumed in here.
        break           
        
print()                 

# for now, the 'r' only got [3,4]  (for tee as well) 
print('i1: ',list(i1))  
print('i2: ',list(i2))<jupyter_output>r:  0 1 2 
i1:  [3, 4]
i2:  [3, 4]<jupyter_text>Converting Inputs - map<jupyter_code>def times_two(x):
    return x * 2 

def multiply(x, y):
    return x, y, x * y 
for i in map(times_two, range(5)):
    print(i)
    
[ i * 2 for i in range(5) ] 
# normal
for i in map(multiply, range(5), range(10)):
    print("{:d} * {:d} = {:d}".format(*i))

# [0,1,2,3,4] vs [0,1,2]
for i in map(multiply, range(5), range(2)):  # range(2) wins!!
    print(i)<jupyter_output>0 * 0 = 0
1 * 1 = 1
2 * 2 = 4
3 * 3 = 9
4 * 4 = 16
(0, 0, 0)
(1, 1, 1)<jupyter_text>- starmap<jupyter_code>values = [(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]

# feed the lambda func with 'i' 
#   i       --  (0,5) 
#   lambda  --  0,5 -> (0, 5, 0*5)

for i in starmap(lambda x,y: (x, y, x*y),values):
    print("{} * {} = {}".format(*i))<jupyter_output>0 * 5 = 0
1 * 6 = 6
2 * 7 = 14
3 * 8 = 24
4 * 9 = 36<jupyter_text>Producing New Values - count<jupyter_code># more readiability I think :)

for i in zip(count(-5), ['a', 'b', 'c']):
    print(i)
    
print() 
    
for i in enumerate(['a', 'b', 'c'],start=-5):
    print(i)
from fractions import Fraction as F 

start = F(1, 5)
step  = F(1, 5)

alphab = list('abcde')

for i in zip(count(start, step), alphab):
    print("{} : {}".format(*i))<jupyter_output>1/5 : a
2/5 : b
3/5 : c
4/5 : d
1 : e<jupyter_text>- cycle<jupyter_code># "range" is God, 
#   the "cycled()" iterators follow!  

# repeat the goddamn iterators!
for i in zip(range(6), cycle(list('abc'))):
    print(i)
# range die, all iterators die 
for i in zip(range(3), cycle(list('abcdeh'))):
    print(i)<jupyter_output>(0, 'a')
(1, 'b')
(2, 'c')<jupyter_text>- repeat<jupyter_code>for i in repeat('oooover-and-oooover', 3):
    print(i)
for cnt, rept in zip(count(), repeat('bang bang!', 5)):
    print(cnt, rept)
# feed lambda with 'repeat(2)' and 'range(5)'
#   repeat(2)  --  let the other arg decide (indefinite num itself)
#   range(5)   --  [0,1,2,3,4]
#   lambda     --  map 2 with [0,1,2,3,4] while doing multiply 

for i in map(lambda x, y: (x, y, x * y), repeat(2), range(5)):
    print("{} {} {}".format(*i))<jupyter_output>2 0 0
2 1 2
2 2 4
2 3 6
2 4 8<jupyter_text>Filtering - dropwhile<jupyter_code>def drop_it(x):
    print("Testing:", x)
    return x < 100

# WARNING:
#   it does NOT check every item of the input 
#   after the cond is false the first time, 
#     all the remaining items are returned (what the ..)

for i in dropwhile(drop_it, [20, 30, 150, 66]):  # tips: 66 shouldn't be here
    print(">> Yielding:",i)<jupyter_output>Testing: 20
Testing: 30
Testing: 150
>> Yielding: 150
>> Yielding: 66<jupyter_text>- takewhile<jupyter_code>def take_it(x):
    print("Testing:", x)
    return x < 100 

# same mechanics as 'dropwhile'
#   got ture/false then stop processing 

for i in takewhile(take_it, [20, 30, 150, 10]):  # tips: 10 should be here
    print(">> Yielding:",i)<jupyter_output>Testing: 20
>> Yielding: 20
Testing: 30
>> Yielding: 30
Testing: 150<jupyter_text>- filter<jupyter_code># Yes! 
# the 'filter' does check Every items in the list (compare to xxwhile)

def check_item(x):
    print("\nTesting:", x) 
    return x >= 0 

for i in filter(check_item, [-1, 0, 1, 2, -2]):  # >=0 is True (Testing)
    print("Yielding:", i)<jupyter_output>Testing: -1

Testing: 0
Yielding: 0

Testing: 1
Yielding: 1

Testing: 2
Yielding: 2

Testing: -2<jupyter_text>- filterfalse<jupyter_code>def check_item(x):
    print("\nTesting:",x)
    return x >= 0

for i in filterfalse(check_item, [-1, 0, 1, 2, -2]): # >=0 is False (Testing)
    print("Yielding:", i)<jupyter_output>Testing: -1
Yielding: -1

Testing: 0

Testing: 1

Testing: 2

Testing: -2
Yielding: -2<jupyter_text>- compress<jupyter_code># 'compress' uses the values in another iterable 
#   to indicate where to accept/ignore the values 

# accept/ignore 
every_third = cycle([False, False, True])

data = range(0, 10)

# 0, 1, 2  ==>  FFT => 2 
# 3, 4, 5  ==>  FFT => 5 
# 6, 7, 8  ==>  FFT => 8
# 9,       ==>  F\\
for i in compress(data, every_third):
    print(i, end=' ')<jupyter_output>2 5 8<jupyter_text>Grouping Data<jupyter_code>import functools
from itertools import *
import operator
from pprint import pprint
@functools.total_ordering
class Point:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return '({}, {})'.format(self.x, self.y)

    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

    def __gt__(self, other):
        return (self.x, self.y) == (other.x, other.y)
# create a dataset

data = list(map(
    Point,
    cycle(islice(count(), 3)),  # == 'range(3)' (But being cycled)
    islice(count(), 7)          # == 'range(7)'
))

print('Data:')
pprint(data, width=35)
# group the unsorted data based on X values 

print('Grouped, unsorted:')

for k,g in groupby(data, operator.attrgetter('x')): 
    print(k, list(g))
# sort the data

data.sort()

print('Sorted:')
pprint(data, width=35)
# group the sorted data based on X values 

print('Grouped, sorted:')
for k, g in groupby(data, operator.attrgetter('x')):
    print(k, list(g))<jupyter_output>Grouped, sorted:
0 [(0, 6)]
2 [(2, 5)]
1 [(1, 4)]
0 [(0, 3)]
2 [(2, 2)]
1 [(1, 1)]
0 [(0, 0)]<jupyter_text>Combining Inputs - accumulate<jupyter_code>list(
    accumulate(range(0,5))  # 0, 0+1, 0+1+2, 0+1+2+3, 0+1+2+3+4 
)

list(
    accumulate('abcde')     # a ab abc abcd abcde
)
# u can customize your own (default is 'add')

def f(a, b):
    return [a,b]  # hella fun!

# a 
# a - b 
# a b - c 
# a b c - d
# a b c d - e
list(accumulate('abcde', f))<jupyter_output><empty_output><jupyter_text>- product<jupyter_code># short intro XD

list(product('123','67'))
[(x,y) for x in '123' for y in '67']
from pprint import pprint 

FACE_CARDS = ('Jack', 'Queen', 'King', 'Ace')
SUITS = ('- Heart', '-- Spade', '--- Club', '---- Diamond')

# Card   first   (2~11, JQKA)
# Type   second  (HSCD)
DECK = list(product(
    chain(range(2, 11), FACE_CARDS),  # a list contains [ 2~11, Q, K, A ]
    SUITS
))

DECK[0:5]  # it's a list after all 


for card in DECK:
    print('{:>6} {}'.format(*card), end='  ')  # e.g. card: (2, '- Heart')  
    if card[1] == SUITS[-1]:                   # card[1] get the type (\n)
        # fancy! But I haven't understand what the 'if' means 
        print() 
SUITS = ('-H', '-S', '-C', '-D')  # Type 
FACE_CARDS = ('J', 'Q', 'K', 'A') # Card

# Type   first   (HSCD)
# Card   second  (2~11, JQKA)
DECK = list(product(
    SUITS,
    chain(range(2, 11), FACE_CARDS),  # a list contains [ 2~11, Q, K, A ]
))

# Ordering 

for card in DECK:
    print('{:>2} {}'.format(card[1], card[0]), end=' ')
    if card[1] == FACE_CARDS[-1]:
        print()
''' Ah ah, no cards!! '''

def show(iterable):
    for i, item in enumerate(iterable, 1):  # start with 1 (index)
        print(item, end=' ')
        if (i % 3) == 0:
            print()
        
    
    print()
    

show(
    list(product(
        range(3),  # put the 'range()' into the 'show()' as arg (iter->item)
        repeat=1   # it's an param of the 'product' 
    ))
)

show(
    list(product(
        range(3),
        repeat=3
    ))
)<jupyter_output><empty_output><jupyter_text>- permutations<jupyter_code>def show(iterable):
    '''
    This function is actually optional.
    
    It's just for showing shorter results,
        since the original result is a bunch of tuples. (and newline XD)
    '''
    
    first = None 
    
    for idx, item in enumerate(iterable, 1):
        
        if first != item[0]:                 # not fully understand these...
            if first is not None:
                print('line-end')
                
            first = item[0]
            
        print(''.join(item), 'hi',end=' ')   # tuple -> single string 
        
    print()
show(
    permutations('abcd')  # each has 2**(n-1) items (6 for this example)
)

# just for testing :>
list(permutations('abcd'))[0:4]
show(
    permutations('abcd', r=1)
)

print()

show(
    permutations('abcd', r=2)
)

print()

show(
    permutations('abcd', r=len('abcd'))
)<jupyter_output>a hi line-end
b hi line-end
c hi line-end
d hi 

ab hi ac hi ad hi line-end
ba hi bc hi bd hi line-end
ca hi cb hi cd hi line-end
da hi db hi dc hi 

abcd hi abdc hi acbd hi acdb hi adbc hi adcb hi line-end
bacd hi badc hi bcad hi bcda hi bdac hi bdca hi line-end
cabd hi cadb hi cbad hi cbda hi cdab hi cdba hi line-end
dabc hi dacb hi dbac hi dbca hi dcab hi dcba hi<jupyter_text>- combinations<jupyter_code>def show(iterable):
    
    first = None 
    
    for idx, item in enumerate(iterable, 1):
        
        if first != item[0]:
            if first is not None:
                print()
            first = item[0]
        
        print(''.join(item), end=' ')
        
    print()
"Unique paris:"

show(
    combinations('abcd', r=2)  # the param 'r' is required 
)

show(
    combinations('abcd', r=3)
)
""" Including individual input elements """

show(
    combinations_with_replacement('abcd', r=2)
)

show(
    combinations_with_replacement('abcd', r=3)
)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16897189674918509, 'fraction_numerical': 0.03832261474759933, 'mean_word_length': 3.0169851380042463, 'pattern_counts': {'":': 0, '<': 69, '<?xml version=': 0, '>': 97, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '20', 'dir': 'jupyter-structured-clean-dedup', 'id': '440627', 'license': 'MIT', 'n_tokens_mistral': 4845, 'n_tokens_neox': 4523, 'n_words': 1327, 'path': 'functional_programming_modules/itertools_pymotw.ipynb', 'repo_name': 'codingEzio/code_python_standard_library', 'size': '11351'}