Python Basics Crash Course
Rany ElHousieny, PhD???
Generative AI ENGINEERING MANAGER | ex-Microsoft | AI Solutions Architect | Generative AI & NLP Expert | Proven Leader in AI-Driven Innovation | Former Microsoft Research & Azure AI | Software Engineering Manager
This article serves as a quick refresher or a jumpstart for Python before delving into machine learning concepts. While it is not a comprehensive Python course, it provides a solid foundation and helps you choose the direction to pursue. Let's dive into the key concepts of Python programming!
This course is part of the following article:
We will be using Google Colaboratory Python notebooks to avoid setup and environment delays. The focus of this article is to get you up and running in Machine Learning with Python, and we can do all that we need there. Here is the link below:
============================
Focused Topics:
Important topics in case you want to get deeper into those areas:
Install Python
Lists and list operations:
Strings
Sets
Tuples
Dictionaries
Mutable vs Immutable variables and Pass By Reference vs Value
Object-Oriented Programing (OOP) in Python
File management
Network Calls with "requests":
=============================
Variables and Data Types:
Python allows you to assign values to variables and perform operations on them. It supports various data types such as integers, floats, strings, booleans, lists, tuples, and dictionaries.
Numbers
Python supports both integers (int) and float. You can get the type of a number using "type()" as follows:
Numbers' Operations
Addition, multiplications, Division, Subtraction.
Exponentiation.?
In Python, the exponentiation operator is `**`. To raise a number to the power of another number, you can use this operator.
For example, to calculate 2 raised to the power of 3, you would write:
```python
result = 2 ** 3
```
In this case, the value of `result` will be 8, as 2 raised to the power of 3 is equal to 8.
The Modulo Operator
The mod operator, represented by the symbol `%`, is known as the modulo operator in Python. It performs the modulo operation, which calculates the remainder when one number is divided by another.
The modulo operation is useful in various scenarios, such as determining if a number is even or odd, checking divisibility, cycling through a set of values, and more.
Here's how the modulo operator works:
```python
a % b
```
In this expression, `a` and `b` are numbers. The modulo operator divides `a` by `b` and returns the remainder.
Examples:
- Assigning values to variables:
x = 10
name = "John"
is_student = True
- Numbers and mathematical operations:
# Numbers and mathematical operations:
a = 5
b = 3
addition = a + b
print ("addition = ", addition)
multiplication = a * b
print ("multiplication = ", multiplication)
division = a / b
print ("division = ", division)
modulo = a % b
print ("modulo = ", modulo)
- Strings and string operations:
In Python, strings are sequences of characters and they can be manipulated in a variety of ways using built-in methods and operations. Here's a breakdown of some commonly used Python string operations:
1. Concatenation (`+`): You can use the `+` operator to combine two strings together.
```python
greeting = "Hello"
name = "John"
message = greeting + ", " + name?# "Hello, John"
```
2. Repetition (`*`): The `*` operator allows you to repeat a string a specified number of times.
```python
```
3. String Interpolation: There are several ways to format strings in Python to insert variables.
name = "John"
age = 30
# Using f-strings (Python 3.6+)
message = f"My name is {name} and I am {age} years old."
# Using .format() method
message = "My name is {} and I am {} years old.".format(name, age)
4. Length (`len()`): The `len()` function returns the number of characters in a string.
length = len("Hello, World!")?# 13
5. Case Conversion: Python provides string methods to convert strings to lower case or upper case.
s = "Hello, World!"
lower_case = s.lower()?# "hello, world!"
upper_case = s.upper()?# "HELLO, WORLD!"
6. Stripping Whitespace (`strip()`): The `strip()` method removes leading and trailing whitespace from a string.
```python
s = "??Hello, World!??"
stripped = s.strip()?# "Hello, World!"
```
7. Replacing Substrings (`replace()`): The `replace()` method replaces occurrences of a substring within a string.
s = "Hello, Bob!"
new_s = s.replace("Bob", "John")?# "Hello, John!"
??
8. Splitting Strings (`split()`): The `split()` method divides a string into a list of substrings based on a delimiter.
s = "Hello, World!"
words = s.split(" ")?# ['Hello,', 'World!']
These are just a few of the numerous operations and methods available for strings in Python. For a more complete picture, refer to Python's official documentation on string methods.
# Strings and string operations
greeting = "Hello"
print ("greeting = ", greeting)
# greeting = Hello
name = "Alice"
print ("name = ", name)
# name = Alice
message = greeting + ", " + name
print ("message = ", message)
# message = Hello, Alice
length = len(message)
print ("length = ", length)
# length = 12
uppercase = message.upper()
print ("uppercase = ", uppercase)
# uppercase = HELLO, ALICE
lowercase = message.lower()
print ("lowercase = ", lowercase)
# lowercase = hello, alice
- Lists and list operations:
# Lists and list operations:
fruits = ['apple', 'banana', 'orange']
print ("fruits = ", fruits)
# fruits = ['apple', 'banana', 'orange']
first_fruit = fruits[0]
print ("first_fruit = ", first_fruit)
# first_fruit = apple
fruits.append('pear')
print ("fruits = ", fruits)
# fruits=['apple', 'banana', 'orange', 'pear']
fruits.remove('banana')
print ("fruits = ", fruits)
# fruits = ['apple', 'orange', 'pear']
- Dictionaries and dictionary operations:
A dictionary in Python is an unordered collection of data values that are used to store data values like a map. Dictionaries are mutable, which means they can be changed. Each item in a dictionary has a key-value pair.
Creating a Dictionary: Dictionaries are defined by enclosing a comma-separated list of key-value pairs in curly braces `{}`.?
```python
my_dict = {'name': 'John', 'age': 27, 'job': 'Engineer'}
```
In this example, 'name', 'age', and 'job' are the keys, and 'John', 27, 'Engineer' are their respective values.
Dictionary Operations:
1. Accessing Items: You can access the items of a dictionary by referring to their key name.
name = my_dict['name']?# 'John'
2. Changing Values: You can change the value of a specific item by referring to its key name.
my_dict['age'] = 30?# change the age to 30
3. Adding Items: Adding an item to the dictionary is done by using a new index key and assigning a value to it.
my_dict['city'] = 'New York'?# adds a new key-value pair
```
4. Removing Items: The `pop()` method removes the item with the specified key name.
```python
my_dict.pop('job')?# removes 'job' from dictionary
5. Looping through a Dictionary: You can loop through a dictionary by using a `for` loop.
for key in my_dict:
?print(key)?# prints all keys in my_dict
?print(my_dict[key])?# prints all values in my_dict
6. Checking if a Key Exists: To determine if a specified key is present in a dictionary, use the `in` keyword.
if "name" in my_dict:
?print("'name' is a key in my_dict")
7. Dictionary Length: To determine how many items (key-value pairs) a dictionary has, use the `len()` function.
print(len(my_dict))
8. Clearing a Dictionary: The `clear()` method empties the dictionary.
my_dict.clear()
9. Copying a Dictionary: The `copy()` method makes a copy of the dictionary.
```python
new_dict = my_dict.copy()
```
The above are some of the most common operations you can perform with Python dictionaries. Python's official documentation offers a comprehensive list of dictionary methods.
# Dictionaries and dictionary operations
person = {'name': 'John', 'age': 25, 'occupation': 'engineer'}
print("person = ", person)
# person = {'name': 'John', 'age': 25, 'occupation': 'engineer'}
person['age'] = 26
print("person['age'] = ", person['age'])
# person['age'] = 26
occupation = person['occupation']
print("occupation = ", occupation)
# occupation = engineer
2. Control Flow:
Control flow statements allow you to control the execution of your code based on certain conditions. It includes if statements, for loops, while loops, and functions.
Examples:
- If statement:
# If statement:
age = 18
if age >= 18:
print("You are an adult.")
else:
print("You are a minor.")
# You are an adult.
- For loop:
# - For loop:
fruits = ['apple', 'banana', 'orange']
for fruit in fruits:
print(fruit)
'''
apple
banana
orange
'''
- While loop:
# - While loop:
counter = 0
while counter < 5:
print(counter)
counter += 1
'''
0
1
2
3
4
'''
- Functions:
# - Functions:
def greet(name):
print('Hello, ' + name + '!' )
greet('Rany ElHousieny')
# Hello, Rany ElHousieny!
The range() Function:
The `range()` function in Python is used to generate a sequence of numbers within a specified range. It is commonly used in for loops to iterate over a sequence of numbers.
The `range()` function takes up to three arguments: `start`, `stop`, and `step`. The `start` argument specifies the starting point of the range (inclusive), the `stop` argument specifies the ending point of the range (exclusive), and the `step` argument specifies the increment between each number in the range (default is 1).
Here are a few examples to illustrate the usage of the `range()` function:
1. Example of generating a sequence of numbers:
```python
for i in range(5):
??print(i)
```
Output:
0
1
2
3
4
In this example, the `range(5)` generates a sequence of numbers from 0 to 4. The for loop iterates over each number in the range and prints it.
2. Example of specifying a start and stop:
```python
for i in range(2, 7):
5
6
In this example, the `range(2, 7)` generates a sequence of numbers from 2 to 6. The for loop iterates over each number in the range and prints it.
3. Example of specifying a start, stop, and step:
for i in range(1, 10, 2):
7
9
In this example, the `range(1, 10, 2)` generates a sequence of odd numbers from 1 to 9. The `step` argument of 2 specifies that the range should increment by 2 between each number. The for loop iterates over each number in the range and prints it.
The `range()` function is a versatile tool for generating sequences of numbers in Python and is commonly used in various programming tasks, such as looping and indexing.
3. Input and Output:
Python provides functions to interact with the user through input and display output.
Examples:
- Input:
# - Input:
def greet(name):
print('Hello, ' + name + '!' )
name = input ('Enter your name:')
greet(name)
# Hello, Rany ElHousieny!
- Output:
# - Output:
name = 'Rany'
print('Hello, ' + name + '!' )
# Hello, Rany!
4. Libraries and Modules:
Python has a rich ecosystem of libraries and modules that provide additional functionality. You can import and use these libraries in your programs.
A module in Python is a file that contains a set of functions, classes, and variables that you can include in your application. Think of a module as a code library that provides a collection of related code that can be reused in different programs.
There are several reasons why we use modules in Python:
1. Modularity: Modules allow you to organize your code into logical units, making it easier to understand, maintain, and reuse. Instead of writing all your code in a single file, you can separate it into modules based on functionality.
2. Code Reusability: By defining functions, classes, and variables in a module, you can reuse them in multiple programs without having to rewrite the code. This saves time and effort in developing new applications.
In Python, you can use the `import` statement to include a module in your program. For example, `import math` imports the `math` module, which provides various mathematical functions and values. You can access the functions and variables defined in the `math` module using the dot notation, like `math.pi` to access the value of pi.
# 4. Libraries and Modules:
import math
print(math.pi)
# 3.141592653589793
You can also use the `import` statement to rename a module for convenience. For example, `import numpy as np` imports the `numpy` module and assigns it the alias `np`. This allows you to use the functions and classes provided by `numpy` by prefixing them with `np`, such as `np.array` to create a NumPy array.
Another way to import from a module is by using the `from` keyword. For example, `from math import pi` imports only the `pi` variable from the `math` module, so you can directly use `pi` without the need to prefix it with `math`.
# 4. Libraries and Modules:
from math import pi
print(pi)
# 3.141592653589793
Similarly, you can import specific elements from a module using the `from` keyword. In the example `from numpy import array`, only the `array` function from the `numpy` module is imported, allowing you to use `array` directly without referencing `numpy`.
Overall, modules in Python help in organizing and structuring your code, promoting reusability, and making it easier to work with external code libraries or custom code you have created.
5. Scope of Variables in Python
领英推荐
The scope of a variable determines where the variable can be accessed or referenced in a program. In Python, there are four types of scopes: global scope, local scope, enclosing scope, and built-in scope.
1. Global Scope: Variables declared outside any function or class have global scope. They can be accessed from anywhere within the program. Global variables are usually defined at the beginning of a program and are available to all functions.
2. Local Scope: Variables declared inside a function have local scope. They can only be accessed within that specific function. Local variables are created when the function is called and are destroyed when the function completes its execution. Local variables can have the same name as global variables, but the local variable takes precedence over the global variable within the function.
3. Enclosing Scope: If a function is defined inside another function, the inner function has access to variables in the outer function. These variables belong to the enclosing scope. The inner function can access and modify the variables of the enclosing scope, but not vice versa.
4. Built-in Scope: The built-in scope contains names that are pre-defined in Python. These names include keywords, functions, exceptions, and other attributes. They are always available to use in any Python program.
The order of scope resolution is local, enclosing, global, and then built-in. When a variable is referenced, Python looks for it in the local scope first, then the enclosing scope, global scope, and finally the built-in scope. If the variable is not found in any of these scopes, a NameError is raised.
Understanding the concept of scope is important for variable management and avoiding conflicts between different parts of your program.
Here are some examples:
Example:
x = 10 # This is a global variable
def func():
print(x) # We can access the global variable inside this function
func() # Will print: 10
print(x) # We can also access the global variable outside the function. Will print: 10
2. Local Variables: Local variables are declared inside a function or local scope and they can only be used inside that function.
Example:
def func():
y = 5 # This is a local variable, it only exists inside this function
print(y)
func() # Will print: 5
print(y) # Will raise a NameError because y is not defined in the global scope
3. The?global?Keyword: If you need to modify a global variable from within a function, you can use the?global?keyword.
Example:
x = 10
def func():
global x # This tells Python that we want to use the global x, not create a local one
x = 5 # This changes the global x, not a local one
func()
print(x) # Will print: 5, because the function changed the global x
Without the?global?keyword in the function, Python would create a new local variable?x, and changes to?x?in the function would not affect the global?x.
It's generally better to avoid relying on global variables in your code, because they can lead to confusion and errors, especially in larger programs. If you find you're using them a lot, it might be a sign that you should reorganize your code, perhaps by passing variables as arguments to functions or by using classes.
Python in Production
Python is a dynamically typed language, meaning types are checked during runtime and not before it. While this provides flexibility, it may lead to potential bugs in a production environment due to undefined or unexpected types. Here are several strategies to avoid such issues:
1. **Use Type Annotations**: Python 3.5 introduced optional type hints, where you can "hint" the type of the variables. While Python's interpreter doesn't enforce them, these hints can be checked by third-party tools like Mypy, helping identify potential type errors in your code.
??```python
??def greet(name: str) -> str:
????return 'Hello ' + name
??```
??In the above code, `name` is expected to be of type `str`, and the function is expected to return a `str`.
2. **Use Docstrings**: Detailed documentation strings ("docstrings") for functions can help clarify the intended types of arguments and return values.
??```python
??def add(a, b):
????"""
????Function to add two numbers
????:param a: int: a number
????:param b: int: another number
????:return: int: returns the sum
????"""
????return a + b
??```
??Here, the docstring clearly mentions the expected types of `a` and `b` (both integers), and also mentions the return type.
3. **Implement Unit Testing**: Unit tests involve testing individual portions of your code to ensure they behave as expected. By providing a range of inputs and their expected outputs, you can catch and correct potential bugs before deployment.
4. **Use 'isinstance' Python Built-in Function**: The 'isinstance' function can be used to ensure a variable is a particular type before continuing. This can prevent bugs due to unexpected types.
??```python
??if isinstance(var, expected_type):
????# proceed with specific operation on 'var'
??```
5. **Error Handling**: Write your code to anticipate and gracefully handle error conditions. By using Python's 'try and except' blocks, you can catch exceptions and deal with them, rather than letting your program crash.
??```python
??try:
????# operation that may raise an exception
??except ExceptionType:
????# code to handle the exception
??```
6. **Code Reviews**: Regularly review your code as well as your team's code. This may help identify potential type-related bugs. It's also a chance to establish and adhere to best coding practices.
7. **Use IDEs or Linters**: Tools like PyCharm, pylint, or flake8 can detect potential issues in your code, such as use of an undefined or unexpected type.
Remember that while Python's flexibility allows you to avoid specifying type information, doing so can make your code more predictable and easier to debug, particularly for other developers or for your future self. Establishing good coding practices can greatly reduce the incidence of bugs related to undefined or unexpected types in your Python code.
Google's Python style guide
Google's Python style guide provides a set of rules for Python coding style, including naming conventions. Here's a quick summary of the naming standards:
Here's a summary of Google's Python Naming standards with examples:
1. Module Names: Modules should have short, lowercase names. If needed, you can use underscores to improve readability.?
Example: `my_module.py`
2. Class Names: Class names should follow the UpperCaseCamelCase convention, also known as CapWords or PascalCase.?
Example: `MyClass`
3. Exception Names: Exceptions should be classes and are named like classes. However, if the class is an exception, ensure it ends with `Error`.
Example: `MyCustomError`
In Python, exceptions are errors that occur during the execution of a program. When these errors occur, Python creates an exception object. If not handled, this results in the program stopping and displaying an error message.
Exception names in Python are usually class names. According to Google's Python style guide, these names should adhere to the UpperCaseCamelCase convention or CapWords, and they should typically end with the word "Error". This is a common practice that improves code readability by making it clear that the class is intended to be an exception.
For example, consider a custom exception that you'd raise when there's an error in connection (like a network issue):
```python
class ConnectionError(Exception):
??pass
```
In this example, `ConnectionError` is the exception name. If a connection error occurred in your program, you could raise this exception with the `raise` keyword:
```python
raise ConnectionError("Unable to connect to server")
```
In this context, following the naming convention allows anyone reading your code to recognize `ConnectionError` as an exception.
4. Function Names: Function names should be lowercase, with underscore separating words to enhance readability. This style is often referred to as snake_case.?
Example: `my_function()`
5. Function and Method Arguments: The first argument of an instance method should be `self`, and the first argument of a class method should be `cls`.
Example:
```python
class MyClass:
?def instance_method(self, arg1, arg2):
??pass
?@classmethod
?def class_method(cls, arg1, arg2):
??pass
```
6. Constant Names: Constants, typically declared at a module level, are written in all caps with underscores separating words.
Example: `MY_CONSTANT = 50`
7. Variable Names: Variables should be in lowercase, with words separated by underscores to improve readability.?
Example: `my_variable = 10`
8. Method Names and Instance Variables: Method names and instance variables follow the same convention as function names i.e., snake_case.
Example:?
```python
class MyClass:
?def my_method(self):
??pass
?my_instance_variable = 10
```
9. Non-public Methods and Instance Variables: To indicate a method or an instance variable is intended for internal use (non-public), prefix the name with a single underscore `_`.
Example:?
```python
class MyClass:
?def _my_internal_method(self):
??pass
?_my_internal_variable = 10
```
10. Naming Conventions for Maximum Clarity: Names should be descriptive and meaningful to clearly indicate the purpose of your variables, functions, classes, or modules. Avoid vague names like `tmp` or `v`.
Example:
```python
# Good
student_scores = [89, 90, 88, 92]
# Bad
s = [89, 90, 88, 92]
```
Remember, these guidelines aim to enhance code clarity, maintainability, and consistency in Python programming.
Practice Problems:
Reverse An Array
Reverse a given list of numbers.
Example One
{
"nums": [50, 35, 78, 66, 17]
}
Output:
[17, 66, 78, 35, 50]
Example Two
{
"nums": [50, 40, 30, 20]
}
Output:
[20, 30, 40, 50]
Notes
Constraints:
------
Solution:
To reverse the given list of numbers, we can use two pointers approach. We will start with one pointer at the beginning of the list and another pointer at the end of the list. We will swap the elements at these two pointers and move the pointers towards each other until they meet in the middle of the list.
Here is the step-by-step algorithm to reverse an array:
1. Initialize two pointers, one at the beginning of the array (start) and another at the end of the array (end).
2. While start < end, do the following:
??a. Swap the elements at start and end pointers.
??b. Move the start pointer one step forward.
??c. Move the end pointer one step backward.
3. Return the modified array.
Here is the implementation of the algorithm in Python:
```python
def reverse_array(nums):
??start = 0
??end = len(nums) - 1
???
??while start < end:
????nums[start], nums[end] = nums[end], nums[start]
????start += 1
????end -= 1
???
??return nums
```
Let's test the implementation with the given examples:
```python
print(reverse_array([50, 35, 78, 66, 17]))
# Output: [17, 66, 78, 35, 50]
print(reverse_array([50, 40, 30, 20]))
# Output: [20, 30, 40, 50]
```
The implemented function takes an array as input, modifies it in-place, and returns the modified array. The time complexity of the algorithm is O(n), where n is the length of the input array.
Let's explore other solutions.
Solution 2
Of course, you can use the reverse method.
def reverse_array(nums):
? ? nums.reverse()
? ? return nums
The .reverse() method in Python also runs in O(n) time complexity where n is the length of the list. Using .reverse() method can be faster because Python is optimized for its built-in functions, but in terms of time complexity, both approaches are equivalent. The reverse() function also reverses the elements in place, so it does not use any extra space. It's a matter of preference and style which one to use.
Solution 3
def reverse_array(nums):
??return [x for x in nums[::-1]]
The previous code does indeed reverse the array, however, it's not being done in-place and it creates a new list which is the reverse of the input list. This means the original list stays unchanged, which might not be desired in scenarios where memory efficiency is a concern. It also doesn't meet the specification given in the problem to modify the input array in-place.
In terms of time complexity, slicing with a negative step like nums[::-1] makes a reversed copy of the list and requires O(n) time, where n is the length of the list. The list comprehension also requires O(n) time, so overall, the time complexity of the provided function is O(n).
Solution 4:
def reverse_array(nums):
? ?
? ? return nums[::-1]
The previous code does indeed reverse the array, but similar to the previous example with list comprehension, it's not being done in-place. It creates a new list which is the reverse of the input list. This means that the original list stays unchanged. If memory efficiency is a concern, this method could potentially use twice as much space as necessary.
In terms of time complexity, slicing with a negative step like nums[::-1] makes a reversed copy of the list and requires O(n) time, where n is the length of the list.?
If in-place modification of the input array is not a requirement, this is a perfectly valid and efficient way to reverse a list in Python. However, according to the requirements given in the problem, we are required to modify the input array in-place. For in-place reversal, you should use the two-pointers approach or the built-in reverse() function.
----------------------------
Problem 2
???
Insert An Element At A Specific Position In An Array
Given an array of numbers, insert a given element at the specified position in the array.
Example One
{
"nums": [2, 4, 5, 6, -1],
"element": 3,
"position": 2
}
Output:
[2, 3, 4, 5, 6]
Example Two
{
"nums": [70, 60, 50, -1],
"element": 40,
"position": 4
}
Output:
[70, 60, 50, 40]
Notes
Constraints:
Solution 1
You can use the Python's built-in list method `insert` to insert an element in the list at a specific position. The `insert` method takes two arguments: the index at which the element needs to be inserted, and the element itself.
However, you need to take care of the fact that the given position follows 1-based indexing, while Python's list indices start from 0. Therefore, we need to subtract one from the position before passing it to the `insert` method.
Also, we need to remove the -1 at the end of the list which indicates a blank position. This can be done using Python's `pop` method which removes and returns the last element from the list.
Here is the Python implementation of the problem:
```python
def insert_element(nums, element, position):
??nums.pop()?# Remove the -1 indicating a blank position.
??nums.insert(position - 1, element)?# Subtract one from the position because Python lists use 0-based indexing.
??return nums
```
Let's test the function with the given examples:
```python
print(insert_element([2, 4, 5, 6, -1], 3, 2))
# Output: [2, 3, 4, 5, 6]
print(insert_element([70, 60, 50, -1], 40, 4))
# Output: [70, 60, 50, 40]
```
The time complexity of the `insert` method is O(n) where n is the length of the list. This is because it requires shifting all elements at positions greater or equal to the specified position to make room for the new element. The `pop` method runs in constant time, i.e., O(1). Therefore, the overall time complexity of the function is O(n).
----------
Solution 2:
def insert_element_at_position(nums, element, position):
? ?
? ? if (position <= len(nums)):
? ? ? ? i = len(nums) - 1
? ? ? ? while i >= (position):
? ? ? ? ? ? nums [i] = nums [i-1]
? ? ? ? ? ? i -= 1
? ? ? ? ? ??
? ? ? ? nums[position - 1] = element
? ? ? ? ? ??
? ? return nums
This is another valid way to solve the problem without using Python's built-in `insert` method. This is a manual approach where you shift all elements from the last index to the given position one step to the right, thereby making space for the new element. The new element is then placed in the correct position.
Here is the commented Python code for better understanding:
def insert_element_at_position(nums, element, position):
??if (position <= len(nums)):?# Ensure the position is within the list's length
????i = len(nums) - 1?# Start from the last element
????while i >= (position):?# Continue until reaching the position
??????nums[i] = nums[i-1]?# Shift the element one step towards the right
??????i -= 1?# Move one step towards the beginning
????nums[position - 1] = element?# Place the new element in the correct position
??return nums
Let's test it with the given examples:
```python
print(insert_element_at_position([2, 4, 5, 6, -1], 3, 2))?# Output: [2, 3, 4, 5, 6]
print(insert_element_at_position([70, 60, 50, -1], 40, 4))?# Output: [70, 60, 50, 40]
```
The time complexity of this function is O(n) where n is the length of the list, as in the worst case we may have to shift all elements in the list one step towards the right. This is same as the function using `insert`. The space complexity is O(1) as we are using a fixed amount of space and modifying the list in-place.
Solution 3
def insert_element_at_position(nums, element, position):
? ?
? ? l = len(nums)
? ? nums.insert(position-1,element)
? ? return nums[:l]
This function first inserts the element at the given position. Then, it returns the array up to the length of the original array, effectively dropping the -1 from the end of the array.
Here is a breakdown of the function:
```python
def insert_element_at_position(nums, element, position):
??l = len(nums)?# Get the length of the input array
??nums.insert(position-1,element)?# Insert the element at the specified position
??return nums[:l]?# Return the array up to the length of the input array
```
Let's test this function with the given examples:
```python
print(insert_element_at_position([2, 4, 5, 6, -1], 3, 2))?# Output: [2, 3, 4, 5, 6]
print(insert_element_at_position([70, 60, 50, -1], 40, 4))?# Output: [70, 60, 50, 40]
```
The time complexity of this function is O(n) where n is the length of the list, same as the previous implementations. The space complexity is O(1) as we are modifying the list in-place and not using any additional space.
Solution 4:
Another possible solution could involve manually shifting the elements to the right of the position where the new element needs to be inserted. Here's how you can do it:
```python
def insert_element_at_position(nums, element, position):
??for i in range(len(nums) - 1, position - 1, -1):
????nums[i] = nums[i-1]
??nums[position - 1] = element
??return nums
```
In this function, we start from the end of the array and move each element one step to the right, until we reach the position where the new element is to be placed. Then, we place the new element at that position.
Let's test this function with the given test cases:
```python
print(insert_element_at_position([2, 4, 5, 6, -1], 3, 2))?# Output: [2, 3, 4, 5, 6]
print(insert_element_at_position([70, 60, 50, -1], 40, 4))?# Output: [70, 60, 50, 40]
```
This function is similar to the one provided earlier which manually shifts the elements, and it also has a time complexity of O(n) and a space complexity of O(1).
Conclusion:
This Python crash course serves as a quick refresher or jumpstart before exploring machine learning concepts. It covers the essential elements of Python programming, including variables, data types, control flow, input/output, and working with libraries. By familiarizing yourself with these concepts, you'll be well-prepared to embark on your journey into the exciting world of machine learning using Python.
Appendix
PyCharm