# empty list and tuple
x = list()
print(x)

x = tuple()
print(x)

# Make a List with elements
x = [1, 2, 3]
print(x)

# List with different data type
x = ['a', 1, 'b', 2]
print(x)

# List of List
x = [[1,2,3], [4,5,6]]
print(x)

"""
Explanation: Python 3rd step: List and Tuple
List and Tuple are similar classes. Both of them can have any type of objects as elements in it.
Most of the operation that can be done in Tuple can be done in List, so I will mainly focus on List hereafter.
In order to make List use [] or list() function. In order to make Tuple, use tuple() function or comma + ().
Python does not support simple array (without external library). List is used where array is required in other language.
End of explanation
"""


t = (1, 2, 3)
t[1] = 5
print(t)

t = (1, [2], 3)
#print(id(t), id(t[0]), id(t[1]), id(t[2]))
#print(id(t[1][0]))
t[1][0] = 5
print(t)
#print(id(t), id(t[0]), id(t[1]), id(t[2]))
#print(id(t[1][0]))

x = [1, 2, 3]
y = x
y[0] = 4
print(x)
print(id(x), id(y))

x = [1, 2, 3]
y = x.copy()  # or y = x[:]
y[0] = 4
print(x)
print(id(x), id(y))

"""
Explanation: Mutable and Immutable
The difference of Tuple and List is that List is Mutable(changeable), but Tuple is Immutable(Not changeable).
When copying list, care must be taken. list_new = list_old just refers same object, so modification to one will affect another. In order to avoid that condition, use copy() method or slice.
End of explanation
"""


x = [1, 2, 3, 4, 5, 6]
print(x[3])
print(x[1:4])
print(x[-1])
print(x[::-1])

"""
Explanation: Index and Slice
Like String, Tuple and List support Index and Slice.
End of explanation
"""


x = [1, 2, 3, 4, 5, 6]
print('3 in x:', 3 in x)
print('4 not in x:', 4 not in x)
if 7 in x:
    print('7 is in list: x')
else:
    print('7 is not in x')

x = [1, 2, [3,4], 5]
print('3 in x:', 3 in x)
print('[1,2] in x', [1,2] in x)

"""
Explanation: Membership test
Like String, Tuple and List support in and not in oprator.
in and not in returns boolean value True or False.
End of explanation
"""


x = [1, 2, 3]
print(len(x))

for i in range(len(x)):
    if i > 0:
        print(x[i-1] + x[i])
    
# List of List
x = [[1,2,3], [4,5,6]]
print('Number of element of x:', len(x), 'Number of element of x[0]:', len(x[0]))

"""
Explanation: Length
Like String, len() function returns number of elements in the List
End of explanation
"""


x = [i for i in range(5)]
print (x)

# same result as below
y = list()
for i in range(5):
    y.append(i)
print(y)

x = [i**2 for i in range(5) if i != 3]
print(x)

def comprehension(n):
    return [i for i in range(n)]

def append_loop(n):
    x = list()
    for i in range(n):
        x.append(i)
    return x

print('List comprehension')
%timeit comprehension(1000000)
print('Append Loop')
%timeit append_loop  (1000000)

"""
Explanation: List Comprehensions
List Comprehenstions are often used in order to initialize. Its basic format is as follows. If statement is optional.
list_item = [expression  for ... if ...]
End of explanation
"""


# Change ??? to create list of 100 None

x = [??? for ??? in ???]
print(x)

"""
Explanation: Hands on
Create the list of 100 None using List comprehension
End of explanation
"""


help(list)

"""
Explanation: Method of List
List has many built-in method. help(list) shows how to use them
End of explanation
"""


x = [1,2]
x.append(3)
print(x)

y = [10,11]
x += y
print(x)

x.extend(y)
print(x)

x.insert(3, 4)
print(x)

"""
Explanation: List manipulation
Add item in List
append() method is used to add 1 item to end of list. extend() method and + operator are used to concatenate 2 lists. insert() method is used to insert at specific offset(position).
End of explanation
"""


x = [1,2,3,1,2,3]
val = x.pop()
print('x:', x, 'val:', val)

val = x.pop(0)   # pop first element
print('x:', x, 'val:', val)

x.remove(2)
print(x)

"""
Explanation: Remove item from List
pop() method is used to remove itme at specific position and return its value. (default: last element)
removed() method is used to removed specific value.
End of explanation
"""


def push(queue, val):
    queue.append(val)
    
def lifo_pop(queue):
    return(queue.pop())

def fifo_pop(queue):
    return(queue.pop(0))

x = [1,2,3]
push(x, 4)
print(fifo_pop(x))
print(lifo_pop(x))

# Sample of FIFO handling
# MCPC Problem-D Car Showroom

def set_car(car, room, room_size):
    '''
        car: String representing Car number
        room: showroom (List)
        room_size: max length of the room
        Return True if car placement is required
               False if car is already in the room
    '''
    if car in room:
        room.remove(car)
        room.append(car)
        return False

    if len(room) < room_size:
        room.append(car)
        return True

    # Remove LRU(top of list), and add new car to bottom of list
    room.pop(0)
    room.append(car)
    return True

## Main routine

showroom = list()

for car in (8, 1, 2, 3, 1, 4, 1, 5, 3, 4, 1, 4, 3, 2, 3, 1, 2, 8, 1, 2):
    print(set_car(car, showroom, 3), showroom)

"""
Explanation: Stack (LIFO)  and FIFO queue
With append() and pop() method, STACK (Last in First Out) and FIFO (First In First Out) queue can be implemented.
End of explanation
"""


# MCPC Rehearsal Problem-A
s1 = 'LIVINGROOM SMITH PIANO'
s2 = 'BATHROOM JONES TOOTHBRUSH'

database = list()

database.append(s1.split())  # str.split() returns LIST
database.append(s2.split())

print(database)

# WHAT IS IN THE LIVINGROOM?
for record in database:  # for index in range(len(database)) ; record = database[index]
    print(record)
    if record[0] == 'BATHROOM':
        print(record[2])
        break
else:      
    print('UNKNOWN')

"""
Explanation: 2-Dementional List
List can contain another List.
End of explanation
"""


