# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# # Chapter 12 - Tuples

# -------------------------------

# A tuple is a group of one or more values that are treated as a whole. This chapter explains how to recognize and use tuples.

# ---

# ## Using tuples

# A tuple is a group of one or more values, separated by commas. Normally, tuples are written with parentheses around them, but the parentheses are not actually necessary (except in circumstances where otherwise confusion would arise). For example:

t1 = ("apple", "orange")
print( type( t1 ) )
t2 = "banana", "cherry"
print( type( t2 ))

# You can mix data types within tuples. You can even put tuples in tuples.

t1 = ("apple", 3, 1.4)
t2 = ("apple", 3, 1.4, ("banana", 5))

# To find out how many elements a tuple contains, you can use the `len()` function.

t1 = ("apple", "orange")
t2 = ("apple", 3, 1.4)
t3 = ("apple", 3, 1.4, ("banana", 5))
print( len( t1 ) )
print( len( t2 ) )
print( len( t3 ) )

# Note that in this example, the length of `t3` is 4, and not 5. The last element of `t3` is the tuple `("banana", 5)`, which counts as one element.
#
# You can use a `for` loop to access individual elements of a tuple in sequence.

t1 = ("apple", 3, 1.4, ("banana", 5))
for element in t1:
    print( element )

# You can also use the `max()` and `min()` functions to get the maximum respectively the minimum from a tuple of numbers. You can sum the elements of a tuple using the `sum()` function.

t1 = (327, 419, 101, 667, 925, 225)
print( max( t1 ) )
print( min( t1 ) )
print( sum( t1 ) )

# You can test whether an element is part of a tuple by using the `in` operator.

t1 = ("apple", "banana", "cherry")
print( "banana" in t1 )
print( "orange" in t1 )

# ### Tuple assignments

# As you have seen, you can create a tuple by assigning comma-separated values to a variable. Parentheses around it are optional. What if you want to create a tuple with only one element?

t1 = ("apple")
print( type( t1 ) )

# As you can see, putting parentheses around the element does not work, as parentheses are optional. Python introduced a little trick to create a tuple with only one element, and that is that you indicate that it is a tuple by placing a comma after the value. This is rather unintuitive and I would even say "degenerate", but historically this was the solution that an early version of Python introduced, and for compatibility reasons it was not changed.

t1 = ("apple",)
print( type( t1 ) )
print( len( t1 ) )

# Python allows you to place a tuple left of the assignment operator. This is an exception to the rule that only one variable can be placed left of an assignment. The values at the right side are copied one-by-one to the left side, left to right.

t1, t2 = "apple", "banana"
print( t1 )
print( t2 )

# You can place parentheses around the values at the right side, and/or parentheses around the variables at the left side, which makes no difference.
#
# If you place more variables at the left side than values at the right side, you get a runtime error. The same for placing fewer (unless you place just one, as shown above). However, you can create tuples at the right side by placing parentheses.

t1, t2 = ("apple", "banana"), "cherry"
print( t1 )
print( t2 )

# ### Tuple indices

# Just like with strings, you can access the individual elements of a tuple using indices. Where with strings the individual elements are characters, for tuples they are the values. For instance:

t1 = ("apple", "banana", "cherry", "durian")
print( t1[2] )

# You can even use slices, with the same rules a for strings (if you do not remember, check the previous chapter again). A slice of a tuple is another tuple. For example:

t1 = ("apple", "banana", "cherry", "durian", "orange")
print( t1[1:4] )

# Since tuples are indexed, an alternative for a `for` loop to access the individual elements of a tuple is to loop over the indices.

t1 = ("apple", "banana", "cherry", "durian", "orange")
i = 0
while i < len( t1 ):
    print( t1[i] )
    i += 1

# **Exercise**: Write a `for` loop that displays all the values of the elements of a tuple, and also displays their index.

# Values with index.
t1 = ("apple", "banana", "cherry", "durian", "orange")


# ### Tuple comparisons

# You can compare two tuples with each other by using the regular comparison operators. These operators first compare the first two elements of the tuples. If these are different, then the comparison will determine which one is "lower" based on the rules for these data types, and result in `True` or `False`. If they are equal, the second elements will be compared, etcetera.

t1 = ( "apple", "banana" )
t2 = ( "apple", "banana" )
t3 = ( "apple", "cherry" )
t4 = ( "apple", "banana", "cherry" )
print( t1 == t2 )
print( t1 < t3 )
print( t1 > t4 )
print( t3 > t4 )

# ### Tuple return values

# In the chapter about functions, you learned that functions can return multiple values. If you code something like that, what actually happens is that the function is returning a tuple. To deal with such return values, you assign them to variables as explained under "tuple assignments" above.

# ---

# ## Tuples are immutable

# Just like strings, tuples are immutable. This means that you cannot assign a new value to one element of a tuple. The example below will produce a runtime error when run.

t1 = ("apple", "banana", "cherry", "durian")
t1[0] = "orange"

# ---

# ## Applications of tuples

# Tuples are not used often in Python code (except as return values of functions). A logical application of tuples would be to deal with values that always occur in small collections. However, object orientation offers many tools and techniques to deal with such small collections, which means that programmers usually revert to object orientation when they need something like that. Object orientation follows in a later chapter.
#
# For the moment, here is an example of the use of tuples in an application. Suppose that you have to write a program that deals with geometric figures in 2-dimensional space. A concept that you need is that of a point: a location in 2D space that is identified by two coordinates. Rather than write functions that always require a separate X-coordinate and a separate Y-coordinate, you can specify that coordinates are always communicated in the form of tuples.

# +
from math import sqrt

# Returns the distance between two points in 2-dimensional space.
# The points are the parameters of the function; each point is a tuple of two numeric values.
def distance( p1, p2 ):
    return sqrt( (p1[0] - p2[0])**2 + (p1[1] - p2[1])**2 )

point1 = (1,2)
point2 = (5,5)
print( "Distance between", point1, "and", point2, "is", distance( point1, point2 ) )
# -

# An advantage of using tuples to communicate coordinates is that it is relatively easy to write functions that can deal with coordinates in higher-dimensional spaces too.

# +
from math import sqrt

# Distance between two points in N-dimensional space.
# The points should have the same dimension, i.e., they are tuples of
# numeric values, and they should have the same length.
def distance( p1, p2 ):
    total = 0
    for i in range( len( p1 ) ):
        total += (p1[i] - p2[i])**2
    return sqrt( total )

# 1-dimensional space
point1 = (1,)
point2 = (5,)
print( "1D: Distance between", point1, "and", point2, "is", distance( point1, point2 ) )

# 2-dimensional space
point1 = (1,2)
point2 = (5,5)
print( "2D: Distance between", point1, "and", point2, "is", distance( point1, point2 ) )

# 3-dimensional space
point1 = (1,2,4)
point2 = (5,5,8)
print( "3D: Distance between", point1, "and", point2, "is", distance( point1, point2 ) )
# -

# ---

# ## What you learned

# In this chapter, you learned about:
#
# - Tuples
# - Tuple assignments
# - Tuple indices
# - Immutability of tuples
# - Applications of tuples

# -------------

# ## Exercises

# ### Exercise 11.1

# A complex number is a number of the form `a + bi`, whereby `a` and `b` are constants, and `i` is a special value that is defined as the square root of `-1`. Of course, you never try to actually calculate what the square root of `-1` is, as that gives a runtime error; in complex numbers, you always let the `i` remain. For instance, the complex number `3 + 2i` cannot be simplified any further. Addition of two complex numbers `a + bi` and `c + di` is defined as `(a + c) + (b + d)i`. Represent a complex number as a tuple of two numeric values, and create a function that calculates the addition of two complex numbers. 

# Adding complex numbers.


# ### Exercise 11.2

# Multiplication of two complex numbers `a + bi` and `c + di` is defined as `(a*c - b*d) + (a*d + b*c)i`. Write a function that calculates the multiplication of two complex numbers.

# Multiplying complex numbers.


# ### Exercise 11.3

# Consider the definition of a new datatype. The new datatype is the "inttuple". An inttuple is defined as being either an integer, or a tuple consisting of inttuples. You see an example of an inttuple in the code block below. Write a function that prints all the integer values stored in an inttuple. Hint: Since the inttuple is defined recursively, a recursive function is probably the right approach. Use the `isinstance()` function (explained in the chapter on functions) to determine whether you are dealing with an integer or a tuple. If you do this correctly, the function will print the numbers 1 to 20 sequentially.

# Processing inttuples
inttuple = ( 1, 2, ( 3, 4 ), 5, ( ( 6, 7, 8, ( 9, 10 ), 11 ), 12, 13 ), ( ( 14, 15, 16 ), ( 17, 18, 19, 20 ) ) )

# -------------------------------------------------------------

# End of Chapter 11. Version 1.2.

StarcoderdataJupyterScriptsDedupFiltered