<jupyter_start><jupyter_text>Table of Contents1&nbsp;&nbsp;Lists1.1&nbsp;&nbsp;Indexing1.2&nbsp;&nbsp;Nested List1.3&nbsp;&nbsp;Slicing1.4&nbsp;&nbsp;Built in List Functions2&nbsp;&nbsp;List Operations3&nbsp;&nbsp;More functionality while using List3.1&nbsp;&nbsp;typecast3.2&nbsp;&nbsp;append()3.3&nbsp;&nbsp;extend()3.4&nbsp;&nbsp;count()3.5&nbsp;&nbsp;index()3.6&nbsp;&nbsp;insert(x,y)3.7&nbsp;&nbsp;pop()3.8&nbsp;&nbsp;remove()3.9&nbsp;&nbsp;del()3.10&nbsp;&nbsp;reverse()3.11&nbsp;&nbsp;sort()3.12&nbsp;&nbsp;Copying a list (be careful while copying)  Lists Lists are the most commonly used data structure. Think of it as a sequence of data that is enclosed in square brackets and data are separated by a comma. Each of these data can be accessed by calling it's index value.Lists are declared by just equating a variable to '`[ ]`' or `list`.<jupyter_code>a = []
print(type(a))   # return the type of a variable
print(len(a))    # computes the length of a list
a
a = list()
print(type(a))                  # updated list
type(a)
b = a.append(1)   # NoneType means returns no value, object is NoneType
print(b)<jupyter_output>None<jupyter_text>One can directly assign the sequence of data to a list `x` as shown.<jupyter_code>x = [1, 1 + 2j, 'apple', True]     # can hold different data types<jupyter_output><empty_output><jupyter_text>In python, list can carry all different data types.  Indexing<jupyter_code>x = [1, 1 + 2j, 'apple', True]     # can hold different data types<jupyter_output><empty_output><jupyter_text>In python, Indexing starts from 0. Thus, now the list `x`, which has four elements will have `apple` at index 2 and `True` at index 3 .<jupyter_code>x[0]
x[2]
print("{}\n{}\n{}\n{}".format(type(x[0]), type(x[1]), type(x[2]),type(x[3])))<jupyter_output><class 'int'>
<class 'complex'>
<class 'str'>
<class 'bool'><jupyter_text>Indexing can also be done in reverse order. That is the last element can be accessed first. Here, indexing starts from `-1`. Thus index value `-1` will be `True` and index `-2` will be `apple`.<jupyter_code>x[-1]
x[-2]<jupyter_output><empty_output><jupyter_text>As you might have already guessed, `x[0] = x[-4], x[1] = x[-3]`. This concept can be extended towards lists with more many elements.  Nested List<jupyter_code>y = ['carrot','potato']<jupyter_output><empty_output><jupyter_text>Here we have declared two lists `x` and `y` each containing its own data. Now, these two lists can again be put into another list say `z` which will have it's data as two lists. This list inside a list is called as **`nested lists`** and is how an array would be declared which we will see later.<jupyter_code>z  = [x,y]
print(z)<jupyter_output>[[1, (1+2j), 'apple', True], ['carrot', 'potato']]<jupyter_text>Indexing in nested lists can be quite confusing if you do not understand how indexing works in python. So let us break it down and then arrive at a conclusion.Let us access the data 'apple' in the above nested list.First, at index `0` there is a list `[1, (1+2j), 'apple', True]` and at index 1 there is another list `['carrot', 'potato']`. Hence `z[0]` should give us the first list which contains `'apple'`.<jupyter_code>z1 = z[0]
print(z1)<jupyter_output>[1, (1+2j), 'apple', True]<jupyter_text>Now observe that `z1` is not at all a nested list thus to access `'apple'`, `z1` should be indexed at 2.<jupyter_code>z1[2]<jupyter_output><empty_output><jupyter_text>Instead of doing the above, In python, you can access 'apple' by just writing the index values each time side by side.<jupyter_code>z[0][2]
x = [2, 'dog', [3, 3], ['cat', [3, 'cow']]]
print(x)
# Guess the output
print(x[3][0])      # how access an element in nested list
# Can you suggest the indexing to print 'cow'?<jupyter_output><empty_output><jupyter_text>Slicing Indexing was only limited to accessing a single element, Slicing on the other hand is accessing a sequence of data inside the list. In other words **`slicing`** the list.Slicing is done by defining the index values of the first element and the last element from the parent list that is required in the sliced list. It is written as parentlist `[ a : b ]` where `a`,`b` are the index values from the parent list. If a or b is not defined then the index value is considered to be the `first value` for `a` if `a` is not defined and the `last value` for `b` when `b` is not defined.<jupyter_code>num = [0,1,2,3,4,5,6,7,8,9]
print(num[0:4])
print(num[4:])<jupyter_output>[0, 1, 2, 3]
[4, 5, 6, 7, 8, 9]<jupyter_text>You can also slice a parent list with a fixed length or step length.<jupyter_code>num[:9:3]<jupyter_output><empty_output><jupyter_text>Built in List Functions To find the length of the list or the number of elements in a list, **len( )** is used.<jupyter_code>len(num)<jupyter_output><empty_output><jupyter_text>If the list consists of all integer elements then **min( )** and **max( )** gives the minimum and maximum value in the list.<jupyter_code>min(num)
max(num)<jupyter_output><empty_output><jupyter_text>List Operations Lists can be concatenated by adding, '`+`' them. The resultant list will contain all the elements of the lists that were added. The resultant list will not be a nested list.<jupyter_code>x = ['apple', 'orange']
y = ['carrot','potato']
x+y
[1,2,3] + [5,4,7]<jupyter_output><empty_output><jupyter_text>There might arise a requirement where you might need to check if a **particular element is there in a predefined list**. Consider the below list.<jupyter_code>names = ['Earth','Air','Fire','Water']<jupyter_output><empty_output><jupyter_text>To check if 'Fire' and 'Rajath' is present in the list names. A conventional approach would be to use a for loop and iterate over the list and use the if condition. But in python you can use '`a in b`' concept which would return '`True`' if a is present in b and '`False`' if not.<jupyter_code>'Fire' in names
'Rajath' in names<jupyter_output><empty_output><jupyter_text>In a list with elements as string, **`max( )`** and **`min( )`** is applicable. **`max( )`** would return a string element whose unicode value is the highest and the lowest when **`min( )`** is used. Note that only the first index of each element is considered each time and if they value is the same then second index considered so on and so forth.<jupyter_code>mlist = ['bzaa','ds','nc','az','z','klm']
print(max(mlist))
print(min(mlist))<jupyter_output>z
az<jupyter_text>Here the first index of each element is considered and thus `z` has the highest unicode value thus it is returned and minimum unicode value is `a`.<jupyter_code>But what if numbers are declared as strings?
But what if numbers are declared as strings
nlist = ['1','84','93','1000']
print(max(nlist))
print(min(nlist))<jupyter_output>93
1<jupyter_text>Even if the numbers are declared in a string the first index of each element is considered and the maximum and minimum values are returned accordingly. But if you want to find the **`max( )`** string element based on the length of the string then another parameter '`key=len`' is declared inside the **`max( )`** and **`min( )`** function.<jupyter_code>names = ['Earth','Air','Fire','Water']
print(max(names, key=len))
print(min(names, key=len))<jupyter_output>Earth
Air<jupyter_text>But even 'Water' has length 5. **`max()`** or **`min()`** function returns the first element when there are two or more elements with the same length.Any other built in function can be used in place of `len`.  More functionality while using List  typecast A string can be converted into a list by using the **list()** function.<jupyter_code>lst = list('hello')
lst
str(lst)    # reverse doesn't work<jupyter_output><empty_output><jupyter_text>append() **`append( )`** is used to add a element at the end of the list.<jupyter_code>lst = [1,1,4,8,7]
lst.append(1)
print(lst)<jupyter_output>[1, 1, 4, 8, 7, 1]<jupyter_text>**`append( )`** function can also be used to add a entire list at the end. Observe that the resultant list becomes a nested list.<jupyter_code>lst1 = [5,4,1,8]
lst.append(lst1)
print(lst)<jupyter_output>[1, 1, 4, 8, 7, 1, [5, 4, 1, 8]]<jupyter_text>extend() But if nested list is not what is desired then **`extend( )`** function can be used.<jupyter_code>lst.extend(lst1)
print(lst)<jupyter_output>[1, 1, 4, 8, 7, 1, [5, 4, 1, 8], 5, 4, 1, 8]<jupyter_text>count()**`count( )`** is used to count the number of a particular element that is present in the list.<jupyter_code>lst
lst.count(1)<jupyter_output><empty_output><jupyter_text>Notice that `count()` does count inside the child list  index() **`index( )`** is used to find the index value of a particular element. Note that if there are multiple elements of the same value then the first index value of that element is returned.<jupyter_code>lst
lst.index(8)<jupyter_output><empty_output><jupyter_text>insert(x,y) **`insert(x,y)`** is used to insert a element `y` at a specified index value `x`. **`append( )`** function made it only possible to insert at the end.<jupyter_code>lst.insert(5, 'name')
print(lst)<jupyter_output>[1, 1, 4, 8, 7, 'name', 1, [5, 4, 1, 8], 5, 4, 1, 8]<jupyter_text>**`insert(x,y)`** inserts but **does not replace element**. If you want to replace the element with another element you simply assign the value to that particular index.<jupyter_code>lst[5] = 'Python'
print(lst)<jupyter_output>[1, 1, 4, 8, 7, 'Python', 1, [5, 4, 1, 8], 5, 4, 1, 8]<jupyter_text>pop() **`pop( )`** function **return the last element** in the list. This is similar to the operation of a stack. Hence it wouldn't be wrong to tell that **lists can be used as a stack**.<jupyter_code>lst.pop()
lst<jupyter_output><empty_output><jupyter_text>**`pop( )`** can also remove element based on it's index value which can be assigned to a variable.<jupyter_code>lst.pop(1)
lst<jupyter_output><empty_output><jupyter_text>remove() One can also remove element by specifying the element itself using the **remove( )** function.<jupyter_code>lst.remove('Python')
print(lst)<jupyter_output>[1, 4, 8, 7, 1, [5, 4, 1, 8], 5, 4, 1]<jupyter_text>Note that if there are multiple elements of the same value then the first index value of that element is removed.  del() Alternative to **`remove()`** function but with using index value is **`del()`**<jupyter_code>del(lst[1])
print(lst)<jupyter_output>[1, 8, 7, 1, [5, 4, 1, 8], 5, 4, 1]<jupyter_text>reverse() The entire elements present in the list can be reversed by using the **`reverse()`** function.<jupyter_code>lst = [1,1,4,8,7]
lst1 = [5,4,1,8]
lst.append(lst1)
lst.reverse()
print(lst)<jupyter_output>[[5, 4, 1, 8], 7, 8, 4, 1, 1]<jupyter_text>Note that the nested list [5,4,2,8] is treated as a single element of the parent list `lst`. Thus the elements inside the nested list is not reversed.  sort() Python offers built in operation **`sort( )`** to arrange the elements in ascending order.<jupyter_code>a = [3,4,5,1,3,2]
a.sort()
print(a)<jupyter_output>[1, 2, 3, 3, 4, 5]<jupyter_text>For descending order, By default the reverse condition will be False for reverse. Hence changing it to True would arrange the elements in descending order.<jupyter_code>a.sort(reverse=True)
print(a)<jupyter_output>[5, 4, 3, 3, 2, 1]<jupyter_text>Similarly, for lists containing string elements, **`sort( )`** would sort the elements based on it's unicode value in ascending and by specifying **`reverse=True`** in descending.<jupyter_code>names = ['Earth','Air','Fire','Water']
names.sort()
print(names)
names.sort(reverse=True)
print(names)<jupyter_output>['Air', 'Earth', 'Fire', 'Water']
['Water', 'Fire', 'Earth', 'Air']<jupyter_text>To sort based on length **`key=len`** should be specified as shown.<jupyter_code>names.sort(key=len)
print(names)
names.sort(key=len,reverse=True)
print(names)<jupyter_output>['Air', 'Fire', 'Water', 'Earth']
['Water', 'Earth', 'Fire', 'Air']<jupyter_text>Copying a list (be careful while copying)Most of the new python programmers commit this mistake. Consider the following,<jupyter_code>lista= [2,1,4,3]
listb = lista
print(listb)<jupyter_output>[2, 1, 4, 3]<jupyter_text>Here, We have declared a list, `lista = [2,1,4,3]`. This list is copied to `listb` by assigning it's value and it get's copied as seen.  Let see what happens if we perform some random operations on `lista`.<jupyter_code>lista.pop()
print(lista)
lista.append(9)
print(lista)
print(listb)<jupyter_output>[2, 1, 4, 9]<jupyter_text>`listb` has also changed though no operation has been performed on it. This is because python have assigned the same memory space of `lista` to `listb`. So how do fix this? If you recall, in slicing we had seen that `parentlist[a:b]` returns a list from parent list with start index `a` and end index `b` and if `a` and `b` is not mentioned then by default it considers the first and last element. We use the same concept here. By doing so, we are assigning the data of `lista` to `listb` as a variable.<jupyter_code>lista = [2,1,4,3]
listb = lista[:]
print(listb)
lista.pop()
print(lista)
lista.append(9)
print(lista)
print(listb)<jupyter_output>[2, 1, 4, 3]
{'fraction_non_alphanumeric': 0.13509578544061301, 'fraction_numerical': 0.0232183908045977, 'mean_word_length': 5.840146750524109, 'pattern_counts': {'":': 0, '<': 142, '<?xml version=': 0, '>': 142, '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': '40', 'dir': 'jupyter-structured-clean-dedup', 'id': '300213', 'license': 'MIT', 'n_tokens_mistral': 4605, 'n_tokens_neox': 4381, 'n_words': 1654, 'path': '01_PythonWorkshop/Day02/02_Lists.ipynb', 'repo_name': 'erkundanec/Data-Science-Full', 'size': '13050'}