# LIST – II

#### Accessing list values

Lists allow to use slice notation as lst[start:end:step] . The output of the slice notation is a new list containing elements from index start to end-1 . If options are omitted start defaults to beginning of list, end to end of list and step to 1:

```lst = [1, 2, 3, 4]

lst[1:]     # [2, 3, 4]
lst[:3]     # [1, 2, 3]
lst[::2]    # [1, 3]
lst[::-1]   # [4, 3, 2, 1]
lst[-1:0:-1]# [4, 3, 2]
lst[5:8]    # [] since starting index is greater than length of lst, returns empty list
lst[1:10]   # [2, 3, 4] same as omitting ending index```

With this in mind, you can print a reversed version of the list by calling

`lst[::-1]  # [4, 3, 2, 1]`

When using step lengths of negative amounts, the starting index has to be greater than the ending index otherwise the result will be an empty list.

```lst[3:1:-1]  # [4, 3]

# Using negative step indices are equivalent to the following code:
reversed(lst)[0:2] # 0 = 1 -1
# 2 = 3 -1
#The indices used are 1 less than those used in negative indexing and are reversed.```

When lists are sliced the getitem() method of the list object is called, with a slice object. Python has a builtin slice method to generate slice objects. We can use this to store a slice and reuse it later like so,

```data = 'chandan purohit 22 2000' # assuming data fields of fixed length
name_slice = slice(0,19)
age_slice = slice(19,21)
salary_slice = slice(22,None)

#now we can have more readable slices
print(data[name_slice]) #chandan purohit
print(data[age_slice]) #'22'
print(data[salary_slice]) #'2000'```

#### Iterating over a list:

```my_list = ['foo', 'bar', 'baz']
for item in my_list:
print(item)
# Output: foo
# Output: bar
# Output: baz

for (index, item) in enumerate(my_list):
print('The item in position {} is: {}'.format(index, item))
# Output: The item in position 0 is: foo
# Output: The item in position 1 is: bar
# Output: The item in position 2 is: baz
```

#### Any and All

```nums = [1, 1, 0, 1]
all(nums)
# False
chars = ['a', 'b', 'c', 'd']
all(chars)
# True```

Likewise, any() determines if one or more values in an iterable evaluate to True

```nums = [1, 1, 0, 1]
any(nums)
# True
vals = [None, None, None, False]
any(vals)
# False```

While this example uses a list, it is important to note these built-ins work with any Iterable, including generators.

```vals = [1, 2, 3, 4]
any(val > 12 for val in vals)
# False
any((val * 2) > 6 for val in vals)
# True```

#### Concatenate and Merge lists

```# simplest way to concatenate list1 and list2
merged = list1 + list2```

zip returns a list of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables:

```alist = ['a1', 'a2', 'a3']
blist = ['b1', 'b2', 'b3']
for a, b in zip(alist, blist):
print(a, b)

# Output:
# a1 b1
# a2 b2
# a3 b3```

If the lists have different lengths then the result will include only as many elements as the shortest one:

```alist = ['a1', 'a2', 'a3']
blist = ['b1', 'b2', 'b3', 'b4']
for a, b in zip(alist, blist):
print(a, b)
# Output:
# a1 b1
# a2 b2
# a3 b3

alist = []
len(list(zip(alist, blist)))
# Output:
# 0```

For padding lists of unequal length to the longest one with None s use itertools.zip_longest ( itertools.izip_longest in Python 2)

```alist = ['a1', 'a2', 'a3']
blist = ['b1']
clist = ['c1', 'c2', 'c3', 'c4']
for a,b,c in itertools.zip_longest(alist, blist, clist):
print(a, b, c)

# Output:
# a1 b1 c1
# a2 None c2
# a3 None c3
# None None c4```

#### Remove duplicate values in list

Removing duplicate values in a list can be done by converting the list to a set that is an unordered collection of distinct objects). If a list data structure is needed, then the set can be converted back to a list using the function list() :

```names = ["aixk", "duke", "edik", "tofp", "duke"]
list(set(names))

# Output: ['duke', 'tofp', 'aixk', 'edik']
# Note that by converting a list to a set the original ordering is lost.```

#### Accessing values in nested list

Starting with a three-dimensional list and Accessing items in the list:

```alist = [[[1,2],[3,4]], [[5,6,7],[8,9,10], [12, 13, 14]]]
print(alist[0][0][1])
#2
#Accesses second element in the first list in the first list
print(alist[1][1][2])
#10
#Accesses the third element in the second list in the second list```

Performing support operations:

```alist[0][0].append(11)
print(alist[0][0][2])
#11
#Appends 11 to the end of the first list in the first list```

Using nested for loops to print the list:

```for row in alist: #One way to loop through nested lists
for col in row:
print(col)
#[1, 2, 11]
#[3, 4]
#[5, 6, 7]
#[8, 9, 10]
#[12, 13, 14]```

Note that this operation can be used in a list comprehension or even as a generator to produce efficiencies, e.g.:

```[col for row in alist for col in row]
#[[1, 2, 11], [3, 4], [5, 6, 7], [8, 9, 10], [12, 13, 14]]```

Not all items in the outer lists have to be lists themselves:

```alist[1].insert(2, 15)
#Inserts 15 into the third position in the second list```

Another way to use nested for loops. The other way is better but I’ve needed to use this on occasion:

```for row in range(len(alist)): #A less Pythonic way to loop through lists
for col in range(len(alist[row])):
print(alist[row][col])
#[1, 2, 11]
#[3, 4]
#[5, 6, 7]
#[8, 9, 10]
#15
#[12, 13, 14]

#Using slices in nested list:
print(alist[1][1:])
#[[8, 9, 10], 15, [12, 13, 14]]
#Slices still work

#The final list:
print(alist)
#[[[1, 2, 11], [3, 4]], [[5, 6, 7], [8, 9, 10], 15, [12, 13, 14]]]```

#### Initializing a List to a Fixed Number of Elements

For immutable elements (e.g. None , string literals etc.):

```my_list = [None] * 10
my_list = ['test'] * 10```

For mutable elements, the same construct will result in all elements of the list referring to the same object, for example, for a set:

```my_list=[{1}] * 10
print(my_list)
# [{1}, {1}, {1}, {1}, {1}, {1}, {1}, {1}, {1}, {1}]
```my_list=[{1} for i in range(10)]