Fundamentals of DataScience Wk.3 - Sequentials
PadhAIdata_objects_sequential-200320-190119-01

drawing

by One Fourth Labs

For more reading or to go indepth refer official reference of python language https://docs.python.org/3/reference/

Also refer to the Padhai-One Github repo for updates & other notebooks

List

Lists are mutable sequence type data object. It is used for storing python objects in sequential structure. Lists preserve the ordering of elements i.e each element will present in a specific index and will not get shuffled unless modified explicitly.

List objects can be modified (mutable). This provides a greater flexibility.

Creating list

List can be created in 2 ways

  1. using [] square brackets
  2. using list() function, which takes any sequence type or iterable objects (like other list, tuple, set) as argument and converts to list
In [1]:
# empty list
a = []
b = list()

# list with values
c = [1,2,3,4,5]
d = list((6,7,8,9)) # create list out of tuple (sequence type)
e = c # shallow copy from a list

# Print
print("a = ",a, "\nb = ",b, "\nc = ",c, "\nd = ",d, "\ne = ",e )
a =  [] 
b =  [] 
c =  [1, 2, 3, 4, 5] 
d =  [6, 7, 8, 9] 
e =  [1, 2, 3, 4, 5]

One can see that tuple object is converted to list.
What other objects can we convert to list?

In [2]:
a = (1,2,3,4) # tuple
b = {5,6,7,8} # set
c = {'a': 1, 'b':2, 'c':3} #dictionary
d = "truth alone triumphs"
p = list(a)
q = list(b)
r = list(c)
s = list(d)

print("p = ", p, "\nq =", q, "\nr =", r, "\ns =" ,s)
p =  [1, 2, 3, 4] 
q = [8, 5, 6, 7] 
r = ['a', 'b', 'c'] 
s = ['t', 'r', 'u', 't', 'h', ' ', 'a', 'l', 'o', 'n', 'e', ' ', 't', 'r', 'i', 'u', 'm', 'p', 'h', 's']

One can notice that for dictionary only the keys are taken for creating the list not the values.

And for strings all the characters including 'space' is converted into elements of a string.

Other sequence or set or dictionary can be nested inside a list. again this need not be homogenous.

In [3]:
a = [
     [1,2, 3], 
     [4,5,6],
     [7,8,9]
] # list of list 

b = [
     {"a":1, "b": 2},
     (1,2,3,4)
] # list with mixture of tuple and sictionary

print("a = ", a)
print("b =", b)
a =  [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
b = [{'a': 1, 'b': 2}, (1, 2, 3, 4)]



Accessing elements

List elements can be accessed using indices.

Index starts from 0 to n-1 where n is number of elements.

In [4]:
a = [1,2,3,4,5]
print("First element:", a[0])
print("Second element:", a[1])
print("Last(fifth) element:",a[4])
First element: 1
Second element: 2
Last(fifth) element: 5

One can use negative indexing to access the elements. It accesses the elements in reverse.

Index starts from -1 to -n where n is number of elements

In [5]:
a = [1,2,3,4,5]
print("Last element:",a[-1])
print("last second(fourth) element:", a[-2])
print("First element:", a[-5])
Last element: 5
last second(fourth) element: 4
First element: 1

one can have nested lists. And for accessing one has to use [] index operator multiple times.

In [6]:
b = [
     [1,2,3,4,5,6],
     [10, 20, 30]
]
print("First element of b:", b[0])
print("First element inside first element of b:", b[0][0])
print("last element inside last element of b:", b[1][-1])
First element of b: [1, 2, 3, 4, 5, 6]
First element inside first element of b: 1
last element inside last element of b: 30


Can one find if a element is present in a list?
one can do that using the in statement.

In [7]:
a = ["cat", "dog", "parrot"]
a_dog = "dog" in a
a_wolf = "wolf" in a

print("is dog present:", a_dog)
print("is wolf present:", a_wolf)
is dog present: True
is wolf present: False

For the nested lists, it only checks the top level objects, and will not check the objects inside.

In [8]:
b = [ [1,2,3,4,5], 
      [11,12,13,14,15,16,17],
      [21,22,23,24,25,26]]
b1 = 11 in b
b2 = 11 in b[1]

print("Is 11 in b:", b1)
print("Is 11 in b[1]:", b2)
Is 11 in b: False
Is 11 in b[1]: True

As said checking b for 11 then it fails because all elements of b is a list and nothing matches 11. Where as is second case we check list inside the list explicitly.



In some cases one may want to know in which index a specific element is present in addition to knowing if it is present.

For such cases list has index() method.

In [9]:
c = [1,2,3,4,5,6,7,8,9]
idx = c.index(6)
print("Index of 6:", idx) 
Index of 6: 5

Notice it returned index id as 5 which is sixth position. Remember that index start from 0



Inserting elements

List is mutable object, so one can add elements to existing object without creating new object.
This behaviour is different from immutable sequence like tuple.

In [10]:
# Appending new element
a = [1,2,3]
a.append(55)

# Print
print("a = ",a)
a =  [1, 2, 3, 55]

What if one wants to append multiple elements to a list?

In [11]:
## Appending multiple elements
a = [10, 20, 30 , 40]
try:
    a.append(66, 77) # this will throw ERROR
except Exception as error:
    print("Error:", error)
Error: append() takes exactly one argument (2 given)

append() can only be used to append one element at a time. So one cannot pass multiple arguments.
One possible solution that could come to mind is to pass the elements as a list.

In [12]:
a = [10, 20, 30 , 40]
b = [66, 77]
a.append(b)
print("a = ",a)
a =  [10, 20, 30, 40, [66, 77]]

One can see that 5th element is a list not a int. List object nested inside a list. This is not desired in many cases.
It could be a case where we need linear structure.



for loop can be used iterate over the elements of second list and append it to the first, which is called extending a list. Is it the only way?

List has extend() method which makes it easy to append another list to a list which gives same result as above.

In [13]:
a = [10, 20, 30 , 40]
b = [66, 77]
# Iterating and appending elements
for i in b:
    a.append(i)
    
c = [10, 20, 30 , 40]
d = [66, 77]
# extending the list
c.extend(d)

print("a = ",a,"\nc =",c )
a =  [10, 20, 30, 40, 66, 77] 
c = [10, 20, 30, 40, 66, 77]


One can observe that each new element is appended at end of the list and list is a sequence preserving structure.

There can be cases where wee need to append or insert elements to a specific index of a list.
This can be done with insert(). It takes 2 arguments one for index and other for element.

In [14]:
a = [10, 20, 40 , 50]
a.insert(2, 33) # inserts 33 in second index
print("a =", a)
a = [10, 20, 33, 40, 50]



Deleting elements

If one needs to remove an element, one can use remove(). But there is a catch here !

In [15]:
a = [11, 12, 13, 14, 15, 16, 17]
a.remove(13) # removes 13 from list
print("a =", a)

b = [11, 22, 333, 44, 333, 55, 66, 77]
b.remove(333) # two 333 are present, only one gets removed
print("b =",b)
a = [11, 12, 14, 15, 16, 17]
b = [11, 22, 44, 333, 55, 66, 77]

When an object (value) is present in list more than once. remove() will remove only the first element that matches according to indexing.

In order to remove all elements matching a data object (value), one has to iterate over the list which has to be done with care.

In [16]:
a = [11, 22, 333, 44, 333, 55, 66, 77]
r = 333
while r in a:
        a.remove(r)
print("a =", a)
a = [11, 22, 44, 55, 66, 77]


If one wants to remove any object(value) at specific location , one can use pop()

In [17]:
a = [22,33,44, 555, 66,77,88]
a.pop(3) #remove data object at index 3

print("a =", a)
a = [22, 33, 44, 66, 77, 88]


Incase if one need to remove specific slice of elements from a list or all elements of a list
Is iterating over the list and removing each element is the only way? No

One can use del to remove a specific slice of elements or to remove entire object.

In [18]:
a = [1,2,3,4,5,6,7,8,9,0]
del a[2:6] # deletes elements from index 2 to 5 in the object.
print('`a` after modification =', a)
`a` after modification = [1, 2, 7, 8, 9, 0]

When used for removing slice from the list the existing object is modified. As one can see below that the object id remains the same.

In [19]:
a = [1,2,3,4,5,6,7,8,9,0]
print("Id of `a` before:", id(a))
del a[2:6] # deletes elements from index 2 to 5 in the object.
print('`a` after modification =', a)
print("Id of `a` after:", id(a))
Id of `a` before: 140513348308360
`a` after modification = [1, 2, 7, 8, 9, 0]
Id of `a` after: 140513348308360

One can use del on the object as whole.

In [20]:
c = [5,6,7,8,9]
del c # reference to object is removed

try: 
    print(c) #This will throw ERROR since c is not defined
except NameError as error:
    print("Error:", error)
Error: name 'c' is not defined

When using del on the entire object. The object remains same and exists in memory. Only the reference between variable and object is broken and variable becomes undefined.

In [21]:
d = [10, 20, 30, 40]
print("Id of `d`:", id(d))
e = d
del d # reference to object is removed

print("Id of `e`:", id(e))
print("Object in e:", e) # `e` still references the object
Id of `d`: 140513347910216
Id of `e`: 140513347910216
Object in e: [10, 20, 30, 40]


To remove all elements one can use clear() method, this will retain the old object and only remove the elements

In [22]:
a = [100, 200, 300, 400, 500, 600]
a.clear()
print("a =", a)
a = []

At this point a question might come to mind that why should we clear elements of a list object and reuse the same object

Check the below case where we link the object in variable a to another variable b by shallow copy. And we are changing the list object in a by assigning a new list.

In [23]:
a = [11, 22, 33, 44, 55, 66, 77, 88]
b = a # a & b refers to same object
print("before modifying a: \na =", a, " id= ",id(a) )
print("b =", b, " id= ",id(b))

a = [11] # a now point to new object
print("after modifying a: \na =", " id= ",id(a) )
print("b =", " id= ",id(b))
before modifying a: 
a = [11, 22, 33, 44, 55, 66, 77, 88]  id=  140513347793800
b = [11, 22, 33, 44, 55, 66, 77, 88]  id=  140513347793800
after modifying a: 
a =  id=  140513347615496
b =  id=  140513347793800

The object initially a had, is not destroyed or modified, it still exists in memory and b still references the old object, which might not be desired in some case.

Also the link between objects a and b i.e object referenced by b is no longer associated to a.The link is broken which could be bad in some case.

In [24]:
a = [11, 22, 33, 44, 55, 66, 77, 88]
b = a # a & b refers to same object

print("Initially: \na =", a," id= ",id(a))
print(" b =", b," id= ",id(b))

a.clear()

print("clearing a: \na =", a, " id= ",id(a))
print("b =", b, " id= ",id(b))
Initially: 
a = [11, 22, 33, 44, 55, 66, 77, 88]  id=  140513348039496
 b = [11, 22, 33, 44, 55, 66, 77, 88]  id=  140513348039496
clearing a: 
a = []  id=  140513348039496
b = []  id=  140513348039496



Duplicating

It may not be the case always to have same object in two different variables.

Sometimes one may want to create a copy of a list and do some modification independent of other. In such cases it is better create two different objects. For which one can use copy() which creates a deep copy of the object.
Alternatively, one can use list() which is used to create new list from sequence type object.

In [25]:
a = [10, 20 , 30, 40 ,50]
b = a.copy() # deep copy
b.remove(10)

c = list(a)
c.remove(50)

print("a =", a, " id=",id(a))
print("b =", b, " id=",id(b))
print("b =", c, " id=",id(c))
a = [10, 20, 30, 40, 50]  id= 140513347890440
b = [20, 30, 40, 50]  id= 140513348041096
b = [10, 20, 30, 40]  id= 140513348366600

But one would wonder what could be the difference between copy() and list().

list() function takes any iteratable data objects, iterates through elements converts to list type object. This is same for list type objects.

whereas copy() method directly creates a copy of list object. This makes copy() method faster than list()

In [26]:
%%timeit -n1 -r10
a = [1,2,3,4,5,6,7,8,9]
for i in range(1000000):
    b = a.copy
1 loop, best of 10: 45.5 ms per loop
In [27]:
%%timeit -n1 -r10
a = [1,2,3,4,5,6,7,8,9]
for i in range(1000000):
    b = list(a)
1 loop, best of 10: 165 ms per loop


Arranging elements

One would know by now that list is sequence preserving. What if one wants to sort the elements fo a list?
One can always write a simple sorting logic or easily use sort() method to sort the elements.

Sort by default arranges the list elements in ascending order.

In [28]:
a = [66, 55, 77, 11, 33, 99]
a.sort()

print('a =', a)
a = [11, 33, 55, 66, 77, 99]

That seems fine, but what if the elements are string type objects?

sort() arranges string object elements based on character of according to ASCII values.

In [29]:
a = ["eye",  "ball", "dog", "apple", "cat", "aaa"]
a.sort()

print('a =', a)
a = ['aaa', 'apple', 'ball', 'cat', 'dog', 'eye']

Again, what if one wants to sort according to differnt criteria say length of the words?

One can create a function that returns a number based on some criterea and pass the function as aargument to sort() which will sort the list based on the ascending order of number returned for each element.

In [30]:
def my_crit(x):
    return len(x) # returns length of string

a = ["fff",  "b", "dd", "eeeee", "cccc", "aaaaaa"]
a.sort(key = my_crit)

print('a =', a)
a = ['b', 'dd', 'fff', 'cccc', 'eeeee', 'aaaaaa']

It could be seen that sort() is by default ascending order. If one wants to sort in descending order,one can do that by passing argument as reverse=True

In [31]:
a = [66, 55, 77, 11, 33, 99]
a.sort(reverse = True)

print('a =', a)
a = [99, 77, 66, 55, 33, 11]


There will be cases where one may want to reverse the order of list elements. Not the descending sort, just a simple reverse of given list.

This can be done using reverse() method. But is it the only approach?

With some wit one can use indexing to read the list in reverse.

Note that reverse() modifies object, whereas indexing doesn't modify the object, it just a way of reading.

In [32]:
a = ['apple', 'ball', 'cat', 'dog', 'eye']
a.reverse()
print("a =", a)

b = ['v', 'w', 'x', 'y', 'z']
print("reading in reverse b =", b[::-1]) # prints in reverse order
print("actual b =", b) #object remains same
a = ['eye', 'dog', 'cat', 'ball', 'apple']
reading in reverse b = ['z', 'y', 'x', 'w', 'v']
actual b = ['v', 'w', 'x', 'y', 'z']



Other functionalities

One can find the total number of elements in a list using len() function, which will return the number of elements.

In [33]:
a = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6]
b = [[1,2], [4,5,6]] #list with list objects

print("Num of elements in a:", len(a))
print("Num of elements in b:", len(b))
Num of elements in a: 6
Num of elements in b: 2

It can be seen that for b it show 2, len() considers only objects it contains, does not take into account elements present in the object. So b has two list objects which is returned.

list can have duplicate elements i.e same element multiple times, which is intentional design choice. With that being teh case what if one wants to find count of certain object(value) present in a list?

For which one can use count() method

In [34]:
a = [1, 2, 3, 4, 5, 1, 2, 1, 3, 3, 4, 1]
b = [2, [1,2], [2]]
a_c = a.count(1)
b_c = b.count(2)
print("Count of 1 in a:", a_c)
print("Count of 2 in b:", b_c)
Count of 1 in a: 4
Count of 2 in b: 1

One see the same behaviour as in len(). count of 2 in b is 1, which is true because the other two elements are list object not 2. It tries to match with exact object and does not go inside object and checks.

Below is a example where it looks for exact list object and return count

In [35]:
b = [3, [3,1,1], [3], [3]]
b_c = b.count([3])
print("Count of [3] in b:", b_c)
Count of [3] in b: 2

It looks for exact list object as [3] and returns its count.



There would be cases where one will be in need to find sum of all elements in a list. For which one can use sum() function

In [36]:
a = [1.1, 2, 3.3, 4]
s = sum(a)
print("sum is ", s)
sum is  10.4

But the list has to have arithmetically summable object like float, int; else one will get error.

In [37]:
a = ["dog", "cat"] # string objects
try:
    sum(a) # this will throw error
except Exception as error:
    print("Error in a:", error)


b = [1,2, [1,2] ] #list object
try:
    sum(b) # this will throw error
except Exception as error:
    print("Error in b:", error)
Error in a: unsupported operand type(s) for +: 'int' and 'str'
Error in b: unsupported operand type(s) for +: 'int' and 'list'

In second case it has two int object and a list object. Though the inner list has int object, function doesn't not care about that. It just considers the object types in the first level. Adding up a int and list is not possible.



Operators

The result will be stored in a new list object, existing objects will not get modified.

Similar to strings one can use + operator for concatenate two lists.

+ operator works only between two lists, operation between list and other data type is not supported.

In [38]:
x = [1,2,3]
y = [4,5,6]
z = x+y
print("z =", z)
z = [1, 2, 3, 4, 5, 6]
In [39]:
m = [0,9,8,7]

try:
    n = m + 1
except Exception as error:
    print("Error:", error )
Error: can only concatenate list (not "int") to list

One can use * to replicate list elements similar to strings when multiplied by int.

One cannot use * with between list and other data objects like float or even a list.

In [40]:
a = [1,2,3]
b = a*3
print("b =", b)
b = [1, 2, 3, 1, 2, 3, 1, 2, 3]
In [41]:
b = [5,6,7]
try:
    c = b*b #This will throw ERROR
except Exception as error:
    print("Error1:", error)

try:
    c = b*3.2 #Tjis will throw ERROR
except Exception as error:
    print("Error2:", error)
Error1: can't multiply sequence by non-int of type 'list'
Error2: can't multiply sequence by non-int of type 'float'

One cannot use / or - operators on a list

In [42]:
a = [4, 5]
try:
    c = a/3 
except Exception as error:
    print("Error on /:", error)

try:
    c = a-3 
except Exception as error:
    print("Error on -:", error)
Error on /: unsupported operand type(s) for /: 'list' and 'int'
Error on -: unsupported operand type(s) for -: 'list' and 'int'


Tuple

Tuple are immutable sequence type objects. The immutable property means that the tuple object once created cannot be modified.

Tuple preserves the ordering of elements i.e each element will present in a specific index and will not get shuffled.

The Immutable nature of the tuple makes it faster object creation time.

Creating Tuple

Tuples can be created in 2 ways

  1. using () parenthesis
  2. using tuple() function, which takes any sequence type or iterable objects (like other list, tuple, set) as argument and converts to tuple
In [43]:
# empty tuple
a = ()
b = tuple()

# tuple with values
c = (1,2,3,4,5)
d = list([6,7,8,9]) # create tuple out of lsit (sequence type)
e = c # shallow copy from a tuple

# Print
print("a = ",a, "\nb = ",b, "\nc = ",c, "\nd = ",d, "\ne = ",e )
a =  () 
b =  () 
c =  (1, 2, 3, 4, 5) 
d =  [6, 7, 8, 9] 
e =  (1, 2, 3, 4, 5)

One can see that lsit object is converted to tuple. What other objects can we convert to tuple?

In [44]:
a = [1,2,3,4] # list
b = {5,6,7,8} # set
c = {'a': 1, 'b':2, 'c':3} #dictionary
d = "truth alone triumphs"
p = tuple(a)
q = tuple(b)
r = tuple(c)
s = tuple(d)

print("p =", p, "\nq =", q, "\nr =", r, "\ns =" ,s)
p = (1, 2, 3, 4) 
q = (8, 5, 6, 7) 
r = ('a', 'b', 'c') 
s = ('t', 'r', 'u', 't', 'h', ' ', 'a', 'l', 'o', 'n', 'e', ' ', 't', 'r', 'i', 'u', 'm', 'p', 'h', 's')

One can notice that for dictionary only the keys are taken for creating the tuple not the values.
And for strings all the characters including 'space' is converted into elements of a string.

Though the tuples are immutable the elements of tuple can be mutable objects like list. And tuple objects need not be homogenous.

In [45]:
a = (
     (1,2,3), 
     (4,5,6),
     (7,8,9)
 ) # tuple of tuple 

b = (
     {"a":1, "b": 2},
     [1,2,3,4]
 ) # list with mixture of lsit and sictionary

print("a = ", a)
print("b =", b)
a =  ((1, 2, 3), (4, 5, 6), (7, 8, 9))
b = ({'a': 1, 'b': 2}, [1, 2, 3, 4])



Accessing elements

Tuple elements can be accessed using indices.

Index starts from 0 to n-1 where n is number of elements.

In [46]:
a = (1,2,3,4,5)
print("First element:", a[0])
print("Second element:", a[1])
print("Last(fifth) element:",a[4])
First element: 1
Second element: 2
Last(fifth) element: 5

One can use negative indexing to access the elements. It accesses the elements in reverse.

Index starts from -1 to -n where n is number of elements

In [47]:
a = (1,2,3,4,5)
print("Last element:",a[-1])
print("last second(fourth) element:", a[-2])
print("First element:", a[-5])
Last element: 5
last second(fourth) element: 4
First element: 1

One can have nested tuples. And for accessing one has to use [] index operator multiple times.

In [48]:
b = (
     (1,2,3,4,5,6),
     (10, 20, 30)
)
print("First element of b:", b[0])
print("First element inside first element of b:", b[0][0])
print("last element inside last element of b:", b[1][-1])
First element of b: (1, 2, 3, 4, 5, 6)
First element inside first element of b: 1
last element inside last element of b: 30


Can one find if a element is present in a tuple?

one can do that using the in statement.

In [49]:
a = ("cat", "dog", "parrot")
a_dog = "dog" in a
a_wolf = "wolf" in a

print("is dog present:", a_dog)
print("is wolf present:", a_wolf)
is dog present: True
is wolf present: False

For the nested tuple, it only checks the top level objects, and will not check the objects inside.

In [50]:
b = ((1,2,3,4,5), 
      (11,12,13,14,15,16,17),
      (21,22,23,24,25,26))
b1 = 11 in b
b2 = 11 in b[1]

print("Is 11 in b:", b1)
print("Is 11 in b[1]:", b2)
Is 11 in b: False
Is 11 in b[1]: True

As said checking b for 11 then it fails because all elements of b is a list and nothing matches 11. Where as is second case we check list inside the list explicitly.



In some cases one may want to know in which index a specific element is present in addition to knowing if it is present.

For such cases tuple has index() method.

In [51]:
c = (1,2,3,4,5,6,7,8,9)
idx = c.index(3)
print("Index of 3:", idx) 
Index of 3: 2

Notice it returned index id as 2 which is third position. Remember that index start from 0

Handling tuples

Tuples are not modifyable, thus has limited operations and methods associated with it.

One can use del to remove tuple objects.

In [52]:
a = (4,5,6,7)
del a # deletes tuple
try:
    print(a)
except Exception as error:
    print("Error:", error)
Error: name 'a' is not defined

But one cannot use del to remove any elements from tuple object. This is because tuples are immutable. One can create and destroy tuple but not modify them directly.

In [53]:
a = (1,2,3,4,5,6,7,8,9,0)

try:
    del a[2:6] # This will throw error
except Exception as error:
    print("Error:", error) 
Error: 'tuple' object does not support item deletion

Extending or Modifying is not possible since they are immutable but there might be a case where one might necessarily modify the objects at some point and retain the nature as tuple objects.

The work around for above will be to convert the tuple to list object, edit the object and convert back to tuple.

In [54]:
a = (1,2,3)
temp = list(a) + [4,5] #concatenate two lists
a = tuple(temp)
print(a)
(1, 2, 3, 4, 5)

but this should be lost resort in most unavoidalble case. If one knows the data object has to be modified often. It is better to use list, since the conversion back and forth would be time consuming.



Other functionalities

Tuples are not modifyable, thus they has very few methods and functions associated with them.

One can find the total number of elements in a tuple using len() function, which will return the number of elements.

In [55]:
a = (1.1, 2.2, 3.3, 4.4, 5.5, 6.6)
b = ((1,2), (4,5,6)) #tuple with list objects

print("Num of elements in a:", len(a))
print("Num of elements in b:", len(b))
Num of elements in a: 6
Num of elements in b: 2

It can be seen that for b it show 2, len() considers only objects it contains, does not take into account elements present in the object. So b has two tuple objects which is returned.

tuple can have duplicate elements i.e same element multiple times, which is intentional design choice. With that being teh case what if one wants to find count of certain object(value) present in a tuple?

For which one can use count() method

In [56]:
a = (1, 2, 3, 4, 5, 1, 2, 1, 3, 3, 4, 1)
b = (2, (1,2), (2))
a_c = a.count(1)
b_c = b.count(2)
print("Count of 1 in a:", a_c)
print("Count of 2 in b:", b_c)
Count of 1 in a: 4
Count of 2 in b: 2

One see the same behaviour as in len(). count of 2 in b is 1, which is true because the other two elements are tuple object not 2. It tries to match with exact object and does not go inside object and checks.

Below is a example where it looks for exact tuple object and return count

In [57]:
b = (3, (3,1,1), (3), (3))
b_c = b.count((3))
print("Count of [3] in b:", b_c)
Count of [3] in b: 3

It looks for exact tuple object as [3] and returns its count.



There would be cases where one will be in need to find sum of all elements in a tuple. For which one can use sum() function

In [58]:
a = (1.1, 2, 3.3, 4)
s = sum(a)
print("sum is ", s)
sum is  10.4

But the tuple has to be arithmetically summable object like float, int; else one will get error.

In [59]:
a = ("dog", "cat") # string objects
try:
    sum(a) # this will throw error
except Exception as error:
    print("Error in a:", error)


b = (1,2, (1,2) ) #list object
try:
    sum(b) # this will throw error
except Exception as error:
    print("Error in b:", error)
Error in a: unsupported operand type(s) for +: 'int' and 'str'
Error in b: unsupported operand type(s) for +: 'int' and 'tuple'

In second case it has two int object and alist object. Though the inner tuple has int object, function doesn't not care about that. It just considers the object types in the first level. Adding up a int and list is not possible.



Operators

The result will be stored in a new tuple object. Thus immutability is not an issue for using operator.

Similar to strings one can use + operator for concatenate two tuples.

+ operator works only between two lists, operation between tuple and other data type is not supported.

In [60]:
x = (1,2,3)
y = (4,5,6)
z = x+y
print("z =", z)
z = (1, 2, 3, 4, 5, 6)
In [61]:
m = [0,9,8,7]

try:
    n = m + 1
except Exception as error:
    print("Error:", error )

v = (7,8)
w = [9,10] # list
try:
    u = v+w # this will throw error
except Exception as error:
    print("Error:", error)
Error: can only concatenate list (not "int") to list
Error: can only concatenate tuple (not "list") to tuple

One can use * to replicate tuple elements similar to strings when multiplied by int.

One cannot use * with between list and other data objects like float or even a list.

In [62]:
a = (1,2,3)
b = a*3
print("b =", b)
b = (1, 2, 3, 1, 2, 3, 1, 2, 3)
In [63]:
b = (5,6,7)
try:
    c = b*b #This will throw Error
except Exception as error:
    print("Error1:", error)

try:
    c = b*3.2 #This will throw Error
except Exception as error:
    print("Error2:", error)
Error1: can't multiply sequence by non-int of type 'tuple'
Error2: can't multiply sequence by non-int of type 'float'

One cannot use / or - operators on a list

In [64]:
a = (4, 5)
try:
    c = a/3 
except Exception as error:
    print("Error on /:", error)

try:
    c = a-3 
except Exception as error:
    print("Error on -:", error)
Error on /: unsupported operand type(s) for /: 'tuple' and 'int'
Error on -: unsupported operand type(s) for -: 'tuple' and 'int'


Please refer to the Padhai-One Github repo for updates & other notebooks