

# Often we will need to zip two or more lists together before passing it to
# the processing engine (algorithm)
#
# The zip() function transforms multiple lists into a
# single list of tuples of corresponding elements:

list1 = ['a', 'b', 'c']
list2 = [1, 2, 3]

L = zip(list1, list2) 		# is is a zip-object [('a', 1), ('b', 2), ('c', 3)]

print(list(L))			# Convert to a list for printing 


# If the lists are different lengths, zip stops as soon as ONE of the list ends

list1 = ['a', 'b']
list2 = [1, 2, 3]

L = zip(list1, list2)           # is is a zip-object [('a', 1), ('b', 2)]

print(list(L))                  # Convert to a list for printing

list1 = ['a', 'b', 'c']
list2 = [1, 2]

L = zip(list1, list2)           # is is a zip-object [('a', 1), ('b', 2)]

print(list(L))                  # Convert to a list for printing


# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# Unzipping
#
# You can also “unzip” a list using a strange trick:
#
#  (1) *pair  unpacks each argument (e.g.: ('a', 1)  into  'a' 1
#      and use the individual arguments to the zip() function
#  (2) So zip() will use: 
#        arg1 = 'a' 'b'  'c'    (which is a tuple)
#        arg2 =  1   2    3     (which is a tuple)
#
#      I.e.: zip( ('a','b','c') , (1,  2,   3) )
#
#  Output:  ( ('a','b','c'), (1, 2, 3)
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

pairs = [('a', 1), ('b', 2), ('c', 3)]
letters, numbers = zip(*pairs)

print(letters)
print(numbers)


# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# NOTE:
#
#   multiple arguments to a function is the SAME as ONE TUPLE argument !!!
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

# Example:

def add(a, b):        # You "see" 2 arguments, it's ACTUALLY ONE TUPLE
    return a + b

x = add(1, 2) 
print(x)

y = (1,2)
# x = add(y)		# Type error
x = add(*y)
print(x)

y = [1,2]
# x = add(y)		# Type error
x = add(*y)
print(x)

