# Common Data Structures

Data structures are the basic building blocks of programming. They offer distinct methods of organizing data to ensure efficient access based on your specific needs.

### List

* It is `mutable`, which means its elements can be modified after creation.
    
* Lists can contain elements of `different data types` and can be accessed, modified, and iterated upon easily.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1687354005566/dad7bd5f-dd5e-4d2d-88da-74dfca1ed021.png align="left")
    
    ```python
    list_value = [2,3,5,7,11]
    
    print(list_value[0]) # output = 2
    print(list_value[1]) # output = 3
    print(list_value[-1]) #output = 11
    print(list_value[-2]) #output = 7
    ```
    
    **Methods**
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1687354116594/6cda296b-dc0c-46a2-9808-14839713f37c.png align="center")
    
    ```python
    '''Various list methods'''
    
    list_value = [0, 1, 2, 3, 4]
    list_value.append(5)
    print(list_value)  # Output: [0, 1, 2, 3, 4, 5]
    
    list_value.clear()
    print(list_value)  # Output: []
    
    list_value = [0, 1, 2, 3, 4]
    new_list = list_value.copy()
    print(new_list)  # Output: [0, 1, 2, 3, 4]
    
    list_value = [0, 1, 2, 3, 4, 1, 2, 1]
    count = list_value.count(1)
    print(count)  # Output: 3
    
    list_value.extend([5, 6, 7])
    print(list_value)  # Output: [0, 1, 2, 3, 4, 1, 2, 1, 5, 6, 7]
    
    index = list_value.index(3)
    print(index)  # Output: 3
    
    list_value.insert(2, "hello")
    print(list_value)  # Output: [0, 1, 'hello', 2, 3, 4, 1, 2, 1, 5, 6, 7]
    
    popped_element = list_value.pop(3)  # Removes and returns the element at index 3
    print(popped_element)  # Output: 2
    print(list_value)  # Output: [0, 1, 'hello', 3, 4, 1, 2, 1, 5, 6, 7]
    
    list_value.remove(1)  # Removes the first occurrence of 1
    print(list_value)  # Output: [0, 'hello', 3, 4, 1, 2, 1, 5, 6, 7]
    
    list_value.reverse()
    print(list_value)  # Output: [7, 6, 5, 1, 2, 1, 4, 3, 'hello', 0]
    
    list_value.sort()
    print(list_value)  # Output: [0, 1, 1, 2, 3, 4, 5, 6, 7]
    ```
    
    **Slicing**
    
* Python allows you to extract a portion of a list by specifying a `start` index, an `end` index, and an optional `step` value.For instance, (list\_name`[start:end:step]`)
    
    ```python
    list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    # Basic slicing examples
    print(list[2:6])     # Output: [2, 3, 4, 5]
    print(list[:4])      # Output: [0, 1, 2, 3]
    print(list[6:])      # Output: [6, 7, 8, 9]
    print(list[1:8:2])   # Output: [1, 3, 5, 7]
    
    # Negative indices slicing examples
    print(list[-5:-2])   # Output: [5, 6, 7]
    print(list[-4:])     # Output: [6, 7, 8, 9]
    print(list[:-3])     # Output: [0, 1, 2, 3, 4, 5, 6]
    
    # Step value slicing examples
    print(list[::2])     # Output: [0, 2, 4, 6, 8]
    print(list[1:8:3])   # Output: [1, 4, 7]
    ```
    

### Tuples

* Tuples are `immutable`, meaning their elements cannot be modified after creation.
    
* They are used to represent fixed collections of related values.
    
    ```python
    # Creating a tuple
    my_tuple = (1, 2, 'a', 'b')
    
    # Accessing elements
    print(my_tuple[0])    # Output: 1
    print(my_tuple[2])    # Output: 'a'
    
    # Trying to modify elements (results in an error)
    my_tuple[1] = 'x'    # TypeError: 'tuple' object does not support item assignment
    ```
    

### Dictionary

* Dictionaries are associative data structures that store data as `key-value` pairs.
    
* They provide fast and efficient access to values based on their corresponding `keys`.
    
* Keys within a dictionary must be `unique`, and they are typically `immutable` data types, such as strings or numbers.
    
    ```python
    # Creating a dictionary
    my_dict = {'name': 'Nirmal', 'age': 23, 'city': 'Butwal'}
    
    # Accessing elements
    print(my_dict['name'])    # Output: 'Nirmal'
    print(my_dict['age'])     # Output: 23
    
    # Modifying elements
    my_dict['age'] = 26
    print(my_dict['age'])     # Output: 26
    
    # Adding new elements
    my_dict['occupation'] = 'Engineer'
    print(my_dict) # Output: {'name': 'Nirmal', 'age': 26, 'city': 'Butwal', 'occupation': 'Engineer'}
    
    # Iterating over the dictionary
    for key, value in my_dict.items():
        print(key, ':', value)
    ```
    

### Sets and Frozenset

* **Sets** are unordered collections of `unique elements`, while frozenset is an `immutable version` of a set in Python.
    
* Sets are used to perform mathematical set operations like `union`, `intersection`, and `difference`.
    
    ```python
    set_value = {1, 2, 3, 4, 5}# Creating a set
    set_value.add(6)# Adding elements to a set
    set_value.remove(3)# Removing elements from a set
    
    # Checking membership
    print(2 in set_value)  # Output: True
    
    set1 = {1, 2, 3}
    set2 = {3, 4, 5}
    
    print(set1.union(set2))         # Output: {1, 2, 3, 4, 5}
    print(set1.intersection(set2))  # Output: {3}
    print(set1.difference(set2))    # Output: {1, 2}
    ```
    
    ```python
     '''Implementing frozenset'''
    #Set is mutable
    s = {1,2,3,4}
    s.add(5) # s = {1,2,3,4,5}
    
    #Making immutable using frozenset
    value = frozenset(s)
    value.add(6) # raise error
    ```
    

### Array

* Unlike lists, arrays are fixed in size and can only hold elements of the same data type.
    
* Arrays provide faster access and better memory efficiency compared to lists when working with large amounts of data.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1687353795461/6389583d-780e-468c-bcd1-e4a4f6d313e7.png align="left")
    
    ```python
    import array
    
    # Creating an array of integers
    int_array = array.array('i', [1, 2, 3, 4, 5])
    
    # Creating an array of floating-point numbers
    float_array = array.array('f', [1.0, 2.5, 3.7, 4.2])
    
    print(int_array)      # Output: array('i', [1, 2, 3, 4, 5])
    print(float_array)    # Output: array('f', [1.0, 2.5, 3.7, 4.2])
    ```
    

### Stacks(LIFOS)

* A stack is a linear data structure that follows the First-In-Last-Out (FILO) principle.
    
* It operates in a similar way to a stack of objects, where the last object placed on top is the first one to be removed.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1687353204704/305343b1-0362-4ee0-aff5-ce8752b8dba0.gif align="center")
    
    ```python
    # Create an empty stack
    stack_value = []
    
    # Push elements onto the stack
    stack_value.append(10)
    stack_value.append(20)
    stack_value.append(30)
    
    # Pop elements from the stack
    print(stack_value.pop())  # Output: 30
    print(stack_value.pop())  # Output: 20
    
    # Check if the stack is empty
    print(len(stack_value) == 0)  # Output: False
    
    # Get the size of the stack
    print(len(stack_value))  # Output: 1
    ```
    

### Queues(FIFOS)

* A queue is a linear data structure that follows the `First-In-First-Out (FIFO)` principle.
    
* It works similarly to a real-world queue, where the person who arrives first is served first.
    
* In a queue, elements are added at the tail(end) and removed from the head(front).
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1687353256352/64990893-5a96-4789-bd14-c287ce4bae50.png align="center")
    
    ```python
    import queue
    
    queue_value = queue.Queue()# Create a queue object
    
    # Enqueue(add) elements into the queue
    queue_value.put(10)
    queue_value.put(20)
    queue_value.put(30)
    print(queue_value)#<queue.Queue object at 0x7fb483786b80>
    
    # Dequeue(get and remove) elements from the front of the queue
    print(queue_value.get())  # Output: 10
    print(queue_value.get())  # Output: 20
    
    # Check if the queue is empty
    print(queue_value.empty())  # Output: False
    
    # Get the size of the queue
    print(queue_value.qsize())  # Output: 1
    ```
    

### Priority Queues

* A priority queue is a data structure where elements are assigned priorities and the element with the highest priority is dequeued first.
    
* Elements are enqueued with a priority value, and the queue automatically sorts the elements based on their priorities.
    
    ```python
    import queue
    
    priority_queue = queue.PriorityQueue()# Create a priority queue object
    
    # Enqueue elements into the priority queue
    priority_queue.put((3, "Apple"))
    priority_queue.put((1, "Banana"))
    priority_queue.put((2, "Orange"))
    
    # Dequeue elements from the priority queue
    print(priority_queue.get())  # Output: (1, 'Banana')
    print(priority_queue.get())  # Output: (2, 'Orange')
    
    # Check if the priority queue is empty
    print(priority_queue.empty())  # Output: False
    
    # Get the size of the priority queue
    print(priority_queue.qsize())  # Output: 1
    ```
