Functions in Python- Definition

Functions in Python are blocks of code that perform a specific task, and they can be defined using the def keyword.

Function template

def function_name(input_varibales, ...):
    processing
    return output_value_or_exppression

output_variable = function_name(output_values, ...)
print(output_variable)


Definition
:

  • A function in Python is a block of reusable code that performs a specific task.
  • It allows you to break down your program into smaller, more manageable pieces, making your code modular and easier to understand.

Function Call:

  • To use a function, you call it by its name followed by parentheses ().

Parameters and Arguments:

  • Functions can accept input data called parameters.
  • When calling a function, you provide the actual values, known as arguments, for these parameters.

Return Value:

  • Functions can optionally return a value using the return statement. This value can then be used in the rest of your program.

In simple words-

  • def : It is a keyword to define a function.
  • Function name should preferably be in snake_case.
  • Function defintion can consist of input parameters within ().
  • Function definition concludes with : and the body of function continues with indent, typically a tab space.
  • Function Call: When the function is called, the flow of control jumps to the function definition.
  • Execute Function: Perform the tasks defined inside the function.
  • If there’s a return statement, the function returns a value to the caller. return keyword returns the computed value in memory which can be accessed via the calling/output variable.
  • It the return statement is not present or not executed the function then returns a None value.
  • End: End of the function execution, control returns to the caller.

Snake case, also known as underscore_case, is a naming convention where each word in a compound name is separated by an underscore (_). All letters are lowercase. Here are some examples:

  • my_function_name
  • total_sales_amount
  • is_user_authenticated

Benefits of Snake Case:

  • Readability: Snake case improves code readability by clearly separating words, especially for longer names.
  • Consistency: Following a consistent naming convention makes code easier to understand and maintain for both you and others who collaborate on the project.
  • Python Convention: Snake case is the widely accepted convention for variable and function names in Python. Using it aligns your code with established practices in the Python community.

Examples of Naming with Snake Case:

Here’s a table illustrating how you can convert function names from other conventions to snake_case:

Original NameSnake Case Name
calculateTotalSalescalculate_total_sales
isUserLoggedInis_user_logged_in
checkIfStringIsEmptycheck_if_string_is_empty

Example Imagine a Function as a MonsterMachine

Think of a function as a special machine in your Python code. You give it certain inputs (like ingredients for a recipe), it performs a specific task (like cooking the ingredients), and then it can optionally provide an output (like the delicious meal!).

Steps to Build Your Function MonsterMachine:

  1. Name Your MonsterMachine: Choose a descriptive name for your function that reflects what it does. Let’s call our function greet.
  2. Define the Inputs (if any): These are like the ingredients you feed into your machine. If your function needs information to complete its task, specify variables within parentheses after the function name. For example, sayhello(name). Here, name is the input variable. These are Function Parameters:- Functions can take zero or more parameters.You can have default values for parameters as well.
  3. Tell Your MonsterMachine What to Do: Use Python code blocks to instruct your function what to accomplish with the inputs (or without them if there are none). Indentation is crucial here!
  4. Optional Output: If your function needs to return a value, use the return statement. This is like the cooked meal your machine produces.

Let’s Code a Greeter Machine!

Here’s how we can create a function called greet that takes a name as input and prints a greeting:

Python

def sayhello(name):
  """Prints a greeting message to the user."""
  print("Hello,", name + "!")

# Now let's use our greeter machine!
sayhello("Alice")  # Output: Hello, Alice!

Experimenting with Your Machine

We can call the greet function multiple times with different names to see it work its magic:

Python

sayhello("Bob")  # Output: Hello, Bob!
sayhello("Charlie")  # Output: Hello, Charlie!

Challenge: Build a Pizza Order Machine!

Can you create a function called order_pizza that takes the size (small, medium, large) and the number of toppings (as an integer) as inputs, and then prints the pizza order details?

Here’s a hint:

def order_pizza(size, num_toppings):
  # ... your code here ...

# Test your order_pizza function!
order_pizza("medium", 2)  # Output: You ordered a medium pizza with 2 toppings.

Parameters & Return Statement

Returning Values with Conditions:-

Magic8Ball Problem in Python

import random

def magic_8_ball_response(number):
    if number == 0:
        return "It is certain."
    elif number == 1:
        return "It is decidedly so."
    elif number == 2:
        return "Without a doubt."
    elif number == 3:
        return "Yes – definitely."
    elif number == 4:
        return "You may rely on it."
    elif number == 5:
        return "As I see it, yes."
    elif number == 6:
        return "Most likely."
    elif number == 7:
        return "Outlook good."
    elif number == 8:
        return "Yes."
    elif number == 9:
        return "Signs point to yes."
    elif number == 10:
        return "Reply hazy, try again."
    elif number == 11:
        return "Ask again later."
    elif number == 12:
        return "Better not tell you now."
    elif number == 13:
        return "Cannot predict now."
    elif number == 14:
        return "Concentrate and ask again."
    elif number == 15:
        return "Don't count on it."
    elif number == 16:
        return "My reply is no."
    elif number == 17:
        return "My sources say no."
    elif number == 18:
        return "Outlook not so good."
    elif number == 19:
        return "Very doubtful."
    else:
        return "Error: Invalid number."

def magic_8_ball():
    print("Welcome to the Magic 8 Ball!")
    question = input("Ask a yes or no question (or type 'quit' to exit): ")

    if question.lower() == 'quit':
        print("Goodbye!")
    elif question.strip() == "":
        print("You didn't ask a question!")
    else:
        number = random.randint(0, 19)
        response = magic_8_ball_response(number)
        print("Magic 8 Ball says: " + response)

if __name__ == "__main__":
    magic_8_ball()
import random

def magic_8_ball():
    answers = [
        "It is certain.",
        "It is decidedly so.",
        "Without a doubt.",
        "Yes – definitely.",
        "You may rely on it.",
        "As I see it, yes.",
        "Most likely.",
        "Outlook good.",
        "Yes.",
        "Signs point to yes.",
        "Reply hazy, try again.",
        "Ask again later.",
        "Better not tell you now.",
        "Cannot predict now.",
        "Concentrate and ask again.",
        "Don't count on it.",
        "My reply is no.",
        "My sources say no.",
        "Outlook not so good.",
        "Very doubtful."
    ]

    print("Welcome to the Magic 8 Ball!")
    question = input("Ask a yes or no question (or type 'quit' to exit): ")

    if question.lower() == 'quit':
        print("Goodbye!")
    elif question.strip() == "":
        print("You didn't ask a question!")
    else:
        response = random.choice(answers)
        print("Magic 8 Ball says: " + response)

if __name__ == "__main__":
    magic_8_ball()
import random

def magic_8_ball():
    answers = [
        "It is certain.",
        "It is decidedly so.",
        "Without a doubt.",
        "Yes – definitely.",
        "You may rely on it.",
        "As I see it, yes.",
        "Most likely.",
        "Outlook good.",
        "Yes.",
        "Signs point to yes.",
        "Reply hazy, try again.",
        "Ask again later.",
        "Better not tell you now.",
        "Cannot predict now.",
        "Concentrate and ask again.",
        "Don't count on it.",
        "My reply is no.",
        "My sources say no.",
        "Outlook not so good.",
        "Very doubtful."
    ]

    print("Welcome to the Magic 8 Ball!")
    while True:
        question = input("Ask a yes or no question (or type 'quit' to exit): ")
        if question.lower() == 'quit':
            print("Goodbye!")
            break
        response = random.choice(answers)
        print("Magic 8 Ball says: " + response)

if __name__ == "__main__":
    magic_8_ball()

Scope in Function @python

In Python, the scope of a variable refers to the region of the program where that variable is recognized. Variables can be defined in different parts of a program, and their scope determines where they can be accessed. There are four types of variable scopes in Python:

  1. Local Scope
  2. Enclosing (or nonlocal) Scope
  3. Global Scope
  4. Built-in Scope

1. Local Scope

Variables defined within a function have a local scope and are only accessible within that function.

def my_function():
    local_var = 10  # Local variable
    print(local_var)

my_function()  # Output: 10
print(local_var)  # NameError: name 'local_var' is not defined

2. Enclosing (or nonlocal) Scope

This scope is relevant for nested functions. The enclosing scope refers to the scope of the outer function in which a nested function is defined.

def outer_function():
    enclosing_var = 20

    def inner_function():
        print(enclosing_var)  # Accesses enclosing variable

    inner_function()

outer_function()  # Output: 20

To modify an enclosing variable from within a nested function, you can use the nonlocal keyword.

def outer_function():
    enclosing_var = 20

    def inner_function():
        nonlocal enclosing_var
        enclosing_var = 30
        print(enclosing_var)

    inner_function()  # Output: 30
    print(enclosing_var)  # Output: 30

outer_function()

3. Global Scope

Variables defined at the top level of a script or module are in the global scope. They are accessible from any part of the code, including within functions (unless shadowed by a local variable).

global_var = 40  # Global variable

def my_function():
print(global_var) # Accesses global variable

my_function() # Output: 40
print(global_var) # Output: 40

To modify a global variable within a function, you can use the global keyword.

global_var = 50

def my_function():
global global_var
global_var = 60
print(global_var)

my_function() # Output: 60
print(global_var) # Output: 60

4. Built-in Scope

This is the scope for built-in names like len, print, etc. These are always available in any part of the code.

print(len([1, 2, 3]))  # len is a built-in function

Scope Resolution (LEGB Rule)

Python resolves variable names using the LEGB rule, which stands for Local, Enclosing, Global, Built-in.

  1. Local: Names defined within a function.
  2. Enclosing: Names defined in the enclosing function(s) for nested functions.
  3. Global: Names defined at the top level of a script or module.
  4. Built-in: Names preassigned in the built-in names module.
Example of LEGB Rule
# Built-in scope
def min():
return "This is the built-in min function."

def outer_function():
# Global scope
global_var = "I am in the global scope."

# Enclosing scope
enclosing_var = "I am in the enclosing scope."

def inner_function():
# Local scope
local_var = "I am in the local scope."

print(local_var) # Accessing local variable
print(enclosing_var) # Accessing enclosing variable
print(global_var) # Accessing global variable
print(min()) # Accessing built-in function

inner_function()

outer_function()

Key Points to Remember

  • Local variables are only accessible within the function they are defined in.
  • Enclosing variables are accessible within nested functions and can be modified using the nonlocal keyword.
  • Global variables are accessible throughout the module and can be modified using the global keyword.
  • Built-in names are always available and can be overridden (though not recommended).

What happen behind the scene in Python when a function gets called?

When a function gets called in Python, a series of steps occur behind the scenes. These steps manage the execution of the function, handle its local environment, and ensure the proper flow of control. Here’s a detailed breakdown of what happens:

1. Function Call

When a function is called, Python interprets the call and prepares to execute the function’s code.

def my_function():
print("Hello, world!")

my_function() # Function call

2. Stack Frame Creation

A new stack frame (also known as an activation record) is created for the function call. This stack frame contains:

  • Local variables and arguments: Storage for the function’s local variables and parameters.
  • Return address: Information on where to return after the function execution completes.
  • Instruction pointer: Keeps track of the current position in the function’s code.

3. Argument Passing

The arguments provided to the function call are evaluated and passed to the function. These arguments are then stored in the new stack frame.

def add(a, b):
return a + b

result = add(2, 3) # Arguments 2 and 3 are evaluated and passed to `a` and `b`

4. Local Scope and Environment Setup

The function’s local scope and environment are set up. This includes:

  • Initializing local variables: Variables declared inside the function.
  • Binding arguments to parameters: The passed arguments are bound to the function’s parameters.
  • Setting up the local namespace: A dictionary that holds the function’s local variables.

5. Code Execution

The function’s code is executed line by line. The instruction pointer within the stack frame keeps track of the current line being executed.

def my_function():
x = 10 # Local variable initialization
y = 20 # Local variable initialization
return x + y # Code execution

result = my_function() # Function execution

6. Function Return

When the function execution completes, it returns a value (if any). The return value is passed back to the calling location.

def add(a, b):
return a + b

result = add(2, 3) # `result` receives the value 5

7. Stack Frame Cleanup

After the function returns, its stack frame is destroyed, and the local variables and parameters go out of scope. The return address in the calling function is used to continue execution from where the function was called.

8. Back to Caller

Control returns to the point in the code where the function was called. The function’s return value can be used in subsequent operations.

def multiply(a, b):
return a * b

result = multiply(4, 5) # Control returns here with the result 20
print(result) # Output: 20

Example Walkthrough

Let’s go through a more detailed example:

def greet(name):
message = f"Hello, {name}!"
return message

print(greet("Alice"))
  1. Function Call: greet("Alice") is called.
  2. Stack Frame Creation: A new stack frame is created for greet.
  3. Argument Passing: The argument "Alice" is passed and bound to the parameter name.
  4. Local Scope and Environment Setup: The local variable message is initialized.
  5. Code Execution:
    • message = f"Hello, {name}!" is executed, resulting in message being "Hello, Alice!".
    • return message is executed, returning "Hello, Alice!".
  6. Function Return: The function returns the string "Hello, Alice!".
  7. Stack Frame Cleanup: The stack frame for greet is destroyed.
  8. Back to Caller: Control returns to the print statement, which outputs "Hello, Alice!".

Behind the Scenes

  • Namespace Management: Each function call has its own local namespace, separate from the global namespace.
  • Garbage Collection: Local variables are cleaned up once they go out of scope, freeing memory.
  • Call Stack: The call stack manages function calls, with each call creating a new stack frame.

Lambda Function in Python

A lambda function in Python is a small anonymous function defined using the lambda keyword. Lambda functions can have any number of arguments but can only have one expression. The expression is evaluated and returned when the function is called. Lambda functions are often used for short, simple operations or as arguments to higher-order functions.

Syntax

lambda arguments: expression

Examples

  1. Basic Example
# A lambda function that adds 10 to the input value
add_ten = lambda x: x + 10
print(add_ten(5)) # Output: 15
  1. Lambda Function with Multiple Arguments
# A lambda function that multiplies two numbers
multiply = lambda x, y: x * y
print(multiply(2, 3)) # Output: 6
  1. Using Lambda with map() Function
# Using lambda with map to square each number in the list
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
print(squared) # Output: [1, 4, 9, 16, 25]
  1. Using Lambda with filter() Function
# Using lambda with filter to get even numbers from the list
numbers = [1, 2, 3, 4, 5, 6]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # Output: [2, 4, 6]
  1. Using Lambda with reduce() Function

The reduce() function is part of the functools module and is used to apply a rolling computation to sequential pairs of values in a list.

from functools import reduce

# Using lambda with reduce to get the product of all numbers in the list
numbers = [1, 2, 3, 4]
product = reduce(lambda x, y: x * y, numbers)
print(product) # Output: 24
  1. Sorting with Lambda

Lambda functions are often used for sorting data structures.

# Sorting a list of tuples based on the second element
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda x: x[1])
print(pairs) # Output: [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
  1. Lambda Function in a Function

Lambda functions can be used inside other functions.

def make_incrementor(n):
return lambda x: x + n

increment_by_5 = make_incrementor(5)
print(increment_by_5(10)) # Output: 15

Advantages of Lambda Functions

  1. Concise Syntax: Lambda functions provide a clear and concise way to write small functions.
  2. Functional Programming: They are often used with functions like map(), filter(), and reduce().
  3. Anonymous Functions: Lambda functions do not require a name, making them useful for short-lived operations.

Limitations of Lambda Functions

  1. Single Expression: Lambda functions are limited to a single expression. They cannot contain statements or multiple expressions.
  2. Readability: Overuse of lambda functions can lead to less readable code, especially for complex operations.

Practical Usage

Lambda functions are ideal for use cases where small, throwaway functions are needed, such as sorting, filtering, and mapping operations. They allow for more compact and potentially more readable code in these scenarios.

# Example of practical usage in data manipulation
data = [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}, {'name': 'Charlie', 'age': 35}]

# Sorting data by age using a lambda function
sorted_data = sorted(data, key=lambda x: x['age'])
print(sorted_data)
# Output: [{'name': 'Bob', 'age': 25}, {'name': 'Alice', 'age': 30}, {'name': 'Charlie', 'age': 35}]

In this example, the lambda function extracts the ‘age’ attribute from each dictionary in the list, allowing the sorted() function to sort the dictionaries by age. This demonstrates the power and simplicity of lambda functions in real-world applications.

map() Function in Python

The map() function in Python applies a given function to all items in an input list (or any iterable) and returns an iterator that yields the results. The function that is applied can be a built-in function, a user-defined function, or a lambda function.

Syntax

map(function, iterable, ...)
  • function: A function that is applied to each element of the iterable.
  • iterable: One or more iterables (like list, tuple, etc.) whose elements are to be mapped.
  • ...: You can pass more than one iterable if the function requires multiple arguments.

Examples

  1. Basic Example with a Built-in Function
# Using map with the built-in function `str` to convert numbers to strings
numbers = [1, 2, 3, 4, 5]
str_numbers = list(map(str, numbers))
print(str_numbers) # Output: ['1', '2', '3', '4', '5']
  1. Using map() with a User-Defined Function
# User-defined function to square a number
def square(x):
return x ** 2

numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(square, numbers))
print(squared_numbers) # Output: [1, 4, 9, 16, 25]
  1. Using map() with a Lambda Function
# Using lambda function to cube each number in the list
numbers = [1, 2, 3, 4, 5]
cubed_numbers = list(map(lambda x: x ** 3, numbers))
print(cubed_numbers) # Output: [1, 8, 27, 64, 125]
  1. Using map() with Multiple Iterables

When using multiple iterables, the function must accept that many arguments, and map() stops when the shortest iterable is exhausted.

# Using map with two lists to add corresponding elements
numbers1 = [1, 2, 3]
numbers2 = [4, 5, 6]
sum_numbers = list(map(lambda x, y: x + y, numbers1, numbers2))
print(sum_numbers) # Output: [5, 7, 9]
  1. Converting Items in a List
# Convert list of integers to list of strings
numbers = [1, 2, 3, 4, 5]
str_numbers = list(map(str, numbers))
print(str_numbers) # Output: ['1', '2', '3', '4', '5']
  1. Processing Strings with map()
# Using map to convert a list of strings to uppercase
words = ["apple", "banana", "cherry"]
uppercase_words = list(map(str.upper, words))
print(uppercase_words) # Output: ['APPLE', 'BANANA', 'CHERRY']

Practical Usage

Converting a List of Temperatures from Celsius to Fahrenheit

# User-defined function to convert Celsius to Fahrenheit
def celsius_to_fahrenheit(c):
return (c * 9/5) + 32

celsius_temps = [0, 20, 37, 100]
fahrenheit_temps = list(map(celsius_to_fahrenheit, celsius_temps))
print(fahrenheit_temps) # Output: [32.0, 68.0, 98.6, 212.0]

Removing Whitespace from a List of Strings

# Using map to strip leading and trailing whitespace from each string in the list
lines = [" line1 ", " line2 ", " line3 "]
stripped_lines = list(map(str.strip, lines))
print(stripped_lines) # Output: ['line1', 'line2', 'line3']

Applying a Complex Function to a List

# Function to calculate the BMI given weight and height
def calculate_bmi(weight, height):
return weight / (height ** 2)

weights = [70, 80, 90]
heights = [1.75, 1.80, 1.65]
bmis = list(map(calculate_bmi, weights, heights))
print(bmis) # Output: [22.857142857142858, 24.691358024691358, 33.05785123966942]

Data Transformation

Transforming each element in a list or another iterable without writing explicit loops.

Example: Converting Strings to Integers

str_numbers = ['1', '2', '3', '4']
int_numbers = map(int, str_numbers)

print(list(int_numbers))  # Output: [1, 2, 3, 4]

Example: Applying Mathematical Functions

import math

values = [1, 4, 9, 16]
square_roots = map(math.sqrt, values)

print(list(square_roots))  # Output: [1.0, 2.0, 3.0, 4.0]

Data Cleaning

Cleaning or preprocessing data elements in a list.

Example: Stripping Whitespace from Strings

dirty_strings = ['  hello  ', '  world  ']
clean_strings = map(str.strip, dirty_strings)

print(list(clean_strings))  # Output: ['hello', 'world']

Applying Functions to Multiple Iterables

Using map with multiple iterables to apply a function that takes multiple arguments.

Example: Adding Corresponding Elements of Two Lists

def add(x, y):
    return x + y

list1 = [1, 2, 3]
list2 = [4, 5, 6]
result = map(add, list1, list2)

print(list(result))  # Output: [5, 7, 9]

Data Aggregation

Aggregating data by applying functions to each element.

Example: Getting Lengths of Strings

words = ['apple', 'banana', 'cherry']
lengths = map(len, words)

print(list(lengths))  # Output: [5, 6, 6]

Functional Programming

Using map to support functional programming paradigms, making code more declarative.

Example: Using Lambda Functions

numbers = [1, 2, 3, 4]
squared = map(lambda x: x ** 2, numbers)

print(list(squared))  # Output: [1, 4, 9, 16]

DataFrame Operations

Applying transformations to DataFrame columns in libraries like Pandas.

Example: Converting DataFrame Column to Uppercase

import pandas as pd

data = {'names': ['alice', 'bob', 'charlie']}
df = pd.DataFrame(data)
df['names_upper'] = df['names'].map(str.upper)

print(df)
# Output:
#      names names_upper
# 0    alice      ALICE
# 1      bob        BOB
# 2  charlie    CHARLIE

Combining with Other Functional Constructs

Using map with other functional programming constructs like filter and reduce.

Example: Using Map and Filter Together

numbers = [1, 2, 3, 4, 5]
even_squares = map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, numbers))

print(list(even_squares))  # Output: [4, 16]

Dynamic Data Generation

Generating data dynamically based on some patterns or rules.

Example: Generating a Range of Dates

from datetime import datetime, timedelta

start_date = datetime(2023, 1, 1)
days = [start_date + timedelta(days=i) for i in range(10)]
formatted_dates = map(lambda d: d.strftime('%Y-%m-%d'), days)

print(list(formatted_dates))  # Output: ['2023-01-01', '2023-01-02', ..., '2023-01-10']

Batch Processing

Processing batches of data efficiently.

Example: Processing Batches of Log Entries

def process_log(log_entry):
    # Process the log entry
    return log_entry.upper()

log_entries = ['error 1', 'warning 2', 'info 3']
processed_logs = map(process_log, log_entries)

print(list(processed_logs))  # Output: ['ERROR 1', 'WARNING 2', 'INFO 3']

Performance Considerations

  • Memory Efficiency: map() returns an iterator, which is memory efficient compared to list comprehensions that generate the entire list at once.
  • Readability: Using map() can sometimes improve code readability, especially when performing simple transformations. However, for complex operations, list comprehensions or explicit loops may be more readable.

Summary

  • map(): Applies a function to all items in an iterable and returns an iterator.
  • Use Cases: Simple transformations, multiple iterables, inline lambda functions.
  • Advantages: Memory efficient, concise for simple operations.
  • Limitations: Less readable for complex operations, stops at the shortest iterable when using multiple iterables.

The map() function is a powerful tool in Python for applying functions to iterable objects, providing both efficiency and simplicity in many scenarios.

Filter functions in Python

Filter functions in Python allow you to create a new iterable from an existing one, selecting only the elements that meet certain criteria. This is achieved using the built-in filter() function, which applies a filtering function to each element of an iterable and returns an iterator containing only those elements for which the filtering function returns True.

Syntax

filter(function, iterable)
  • function: A function that tests each element of the iterable. It should return True or False.
  • iterable: The iterable to be filtered (like lists, tuples, sets, etc.).

Examples

  1. Basic Example with a User-Defined Function
# User-defined function to check if a number is even
def is_even(num):
return num % 2 == 0

numbers = [1, 2, 3, 4, 5, 6]
even_numbers = filter(is_even, numbers)
print(list(even_numbers)) # Output: [2, 4, 6]
  1. Using Lambda Functions

Lambda functions are often used with filter() because they allow you to define simple functions in a concise way.

# Using lambda to filter out odd numbers
numbers = [1, 2, 3, 4, 5, 6]
odd_numbers = filter(lambda x: x % 2 != 0, numbers)
print(list(odd_numbers)) # Output: [1, 3, 5]
  1. Filtering Strings
# Filtering a list of strings to include only those with length greater than 3
words = ["apple", "kiwi", "banana", "pear"]
long_words = filter(lambda word: len(word) > 3, words)
print(list(long_words)) # Output: ['apple', 'kiwi', 'banana']
  1. Filtering with Multiple Conditions
# Filtering numbers to include only those that are even and greater than 10
numbers = [5, 10, 15, 20, 25, 30]
filtered_numbers = filter(lambda x: x > 10 and x % 2 == 0, numbers)
print(list(filtered_numbers)) # Output: [20, 30]
  1. Filtering Objects with Attributes

Assume you have a list of objects, each with an attribute, and you want to filter based on that attribute.

class Person:
def __init__(self, name, age):
self.name = name
self.age = age

people = [Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35)]
adults = filter(lambda person: person.age >= 30, people)
print([person.name for person in adults]) # Output: ['Alice', 'Charlie']
  1. Filtering Nested Structures

Filtering nested structures like lists of lists or dictionaries can also be done with filter().

# Filtering a list of dictionaries
students = [
{"name": "Alice", "grade": 85},
{"name": "Bob", "grade": 75},
{"name": "Charlie", "grade": 90}
]
passing_students = filter(lambda student: student["grade"] >= 80, students)
print(list(passing_students)) # Output: [{'name': 'Alice', 'grade': 85}, {'name': 'Charlie', 'grade': 90}]

Practical Usage

  1. Filtering Data from a CSV File

You can use filter() to process data from a CSV file, filtering rows based on some criteria.

import csv

# Assuming a CSV file with 'name' and 'score' columns
with open('data.csv', 'r') as file:
reader = csv.DictReader(file)
high_scorers = filter(lambda row: int(row['score']) > 80, reader)
for student in high_scorers:
print(student['name'], student['score'])
  1. Filtering Pandas DataFrame Rows

If you work with Pandas, you often need to filter rows in a DataFrame. While Pandas has its own filtering methods, you can also use Python’s filter().

import pandas as pd

# Creating a DataFrame
data = {'name': ['Alice', 'Bob', 'Charlie'], 'score': [85, 75, 90]}
df = pd.DataFrame(data)

# Using filter with DataFrame
filtered_rows = filter(lambda row: row['score'] > 80, df.to_dict('records'))
print(list(filtered_rows)) # Output: [{'name': 'Alice', 'score': 85}, {'name': 'Charlie', 'score': 90}]

Performance Considerations

  • Efficiency: filter() returns an iterator, making it memory-efficient for large datasets.
  • Readability: While filter() can be very readable for simple conditions, complex filtering might be better handled with list comprehensions or explicit loops for clarity.

Summary

  • filter(): Applies a function to each element of an iterable and returns only those elements for which the function returns True.
  • Use Cases: Filtering numbers, strings, objects, nested structures, and data from files or databases.
  • Combining with Lambda: Lambda functions are often used for inline, concise filtering operations.
  • Integration: Can be used with other libraries like Pandas for efficient data manipulation.

By practicing with various examples and integrating filter() into your data processing tasks, you’ll become proficient in using this powerful function in Python.

Recursion in python- recusrsive functions in detail

Recursion is a programming technique where a function calls itself directly or indirectly. Recursive functions are particularly useful for solving problems that can be broken down into smaller, simpler subproblems of the same type.

Key Concepts of Recursion

  1. Base Case: The condition under which the recursion ends. Without a base case, a recursive function would call itself indefinitely, leading to a stack overflow.
  2. Recursive Case: The part of the function where the function calls itself with a smaller or simpler argument.

Tips for Writing Recursive Functions

  1. Identify the Base Case: Clearly define the simplest instance of the problem, which can be solved without further recursion.
  2. Ensure Progress: Each recursive call should progress towards the base case, usually by simplifying the problem.
  3. Test with Small Inputs: Start by testing the function with small input values to ensure the base and recursive cases are correctly defined.
  4. Consider Iterative Solutions: Some problems are more efficiently solved with iteration rather than recursion, especially if they involve a large number of recursive calls, which can lead to stack overflow.

General Structure of a Recursive Function

def recursive_function(parameters):
if base_case_condition:
return base_case_value
else:
# Perform some operation
return recursive_function(modified_parameters)

Examples of Recursive Functions

1. Factorial of a Number

The factorial of a non-negative integer nnn is the product of all positive integers less than or equal to nnn. It is denoted by n!n!n!.

n!=n×(n−1)!n! = n times (n-1)!n!=n×(n−1)!

Recursive Definition:

  • Base case: 0!=10! = 10!=1
  • Recursive case: n!=n×(n−1)!n! = n times (n-1)!n!=n×(n−1)!

Python Implementation:

def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1)

# Testing the factorial function
print(factorial(5)) # Output: 120

2. Fibonacci Sequence

The Fibonacci sequence is a series of numbers where each number is the sum of the two preceding ones, usually starting with 0 and 1.

F(n)=F(n−1)+F(n−2)F(n) = F(n-1) + F(n-2)F(n)=F(n−1)+F(n−2)

Recursive Definition:

  • Base case: F(0)=0F(0) = 0F(0)=0, F(1)=1F(1) = 1F(1)=1
  • Recursive case: F(n)=F(n−1)+F(n−2)F(n) = F(n-1) + F(n-2)F(n)=F(n−1)+F(n−2)

Python Implementation:

def fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)

# Testing the fibonacci function
print(fibonacci(7)) # Output: 13

3. Sum of a List

Calculate the sum of all elements in a list.

Recursive Definition:

  • Base case: An empty list has a sum of 0.
  • Recursive case: The sum of a list is the first element plus the sum of the rest of the list.

Python Implementation:

def sum_list(lst):
if len(lst) == 0:
return 0
else:
return lst[0] + sum_list(lst[1:])

# Testing the sum_list function
print(sum_list([1, 2, 3, 4])) # Output: 10

4.for generating all permutations of a string

This function uses recursion to generate all permutations. The base case handles the simplest scenario where the string length is 1, returning the string itself in a list. For longer strings, it iterates through each character, removes it from the string, and recursively generates permutations of the remaining characters. These partial permutations are then combined with the removed character to form the full permutations.

Explanation:

  • Base Case: When the length of the string is 1, return a list containing just that string.
  • Recursive Case: For each character in the string, remove the character and recursively find permutations of the remaining substring. Prepend the removed character to each of these permutations and add them to the list of permutations.

Let’s break down the execution of the recursive permutations function step by step using the input "abc".

Step-by-Step Execution

  1. Initial Call: permutations("abc")
    • The input string is "abc", which is longer than 1 character.
    • Initialize perms as an empty list: perms = [].
  2. First Level of Recursion:
    • Loop over each character in "abc":
      • For i = 0 (character 'a'):
        • Remove 'a', remaining string: "bc".
        • Recursive call: permutations("bc").
  3. Second Level of Recursion: permutations("bc")
    • The input string is "bc", which is longer than 1 character.
    • Initialize perms as an empty list: perms = [].
  4. Second Level: Loop over "bc":
    • For i = 0 (character 'b'):
      • Remove 'b', remaining string: "c".
      • Recursive call: permutations("c").
  5. Third Level of Recursion: permutations("c")
    • The input string is "c", which has only 1 character.
    • Base case: return ["c"].
  6. Back to Second Level: Continue with "bc":
    • For i = 0 (character 'b'):
      • Combine 'b' with permutations of "c": 'b' + "c" = "bc".
      • Add to perms: perms = ["bc"].
    • For i = 1 (character 'c'):
      • Remove 'c', remaining string: "b".
      • Recursive call: permutations("b").
  7. Third Level of Recursion: permutations("b")
    • The input string is "b", which has only 1 character.
    • Base case: return ["b"].
  8. Back to Second Level: Continue with "bc":
    • For i = 1 (character 'c'):
      • Combine 'c' with permutations of "b": 'c' + "b" = "cb".
      • Add to perms: perms = ["bc", "cb"].
    • Return ["bc", "cb"] to the first level call.
  9. Back to First Level: Continue with "abc":
    • For i = 0 (character 'a'):
      • Combine 'a' with permutations of "bc":
        • 'a' + "bc" = "abc".
        • 'a' + "cb" = "acb".
      • Add to perms: perms = ["abc", "acb"].
    • For i = 1 (character 'b'):
      • Remove 'b', remaining string: "ac".
      • Recursive call: permutations("ac").
  10. Second Level of Recursion: permutations("ac")
    • The input string is "ac", which is longer than 1 character.
    • Initialize perms as an empty list: perms = [].
  11. Second Level: Loop over "ac":
    • For i = 0 (character 'a'):
      • Remove 'a', remaining string: "c".
      • Recursive call: permutations("c").
  12. Third Level of Recursion: permutations("c")
    • The input string is "c", which has only 1 character.
    • Base case: return ["c"].
  13. Back to Second Level: Continue with "ac":
    • For i = 0 (character 'a'):
      • Combine 'a' with permutations of "c": 'a' + "c" = "ac".
      • Add to perms: perms = ["ac"].
    • For i = 1 (character 'c'):
      • Remove 'c', remaining string: "a".
      • Recursive call: permutations("a").
  14. Third Level of Recursion: permutations("a")
    • The input string is "a", which has only 1 character.
    • Base case: return ["a"].
  15. Back to Second Level: Continue with "ac":
    • For i = 1 (character 'c'):
      • Combine 'c' with permutations of "a": 'c' + "a" = "ca".
      • Add to perms: perms = ["ac", "ca"].
    • Return ["ac", "ca"] to the first level call.
  16. Back to First Level: Continue with "abc":
    • For i = 1 (character 'b'):
      • Combine 'b' with permutations of "ac":
        • 'b' + "ac" = "bac".
        • 'b' + "ca" = "bca".
      • Add to perms: perms = ["abc", "acb", "bac", "bca"].
    • For i = 2 (character 'c'):
      • Remove 'c', remaining string: "ab".
      • Recursive call: permutations("ab").
  17. Second Level of Recursion: permutations("ab")
    • The input string is "ab", which is longer than 1 character.
    • Initialize perms as an empty list: perms = [].
  18. Second Level: Loop over "ab":
    • For i = 0 (character 'a'):
      • Remove 'a', remaining string: "b".
      • Recursive call: permutations("b").
  19. Third Level of Recursion: permutations("b")
    • The input string is "b", which has only 1 character.
    • Base case: return ["b"].
  20. Back to Second Level: Continue with "ab":
    • For i = 0 (character 'a'):
      • Combine 'a' with permutations of "b": 'a' + "b" = "ab".
      • Add to perms: perms = ["ab"].
    • For i = 1 (character 'b'):
      • Remove 'b', remaining string: "a".
      • Recursive call: permutations("a").
  21. Third Level of Recursion: permutations("a")
    • The input string is "a", which has only 1 character.
    • Base case: return ["a"].
  22. Back to Second Level: Continue with "ab":
    • For i = 1 (character 'b'):
      • Combine 'b' with permutations of "a": 'b' + "a" = "ba".
      • Add to perms: perms = ["ab", "ba"].
    • Return ["ab", "ba"] to the first level call.
  23. Back to First Level: Continue with "abc":
    • For i = 2 (character 'c'):
      • Combine 'c' with permutations of "ab":
        • 'c' + "ab" = "cab".
        • 'c' + "ba" = "cba".
      • Add to perms: perms = ["abc", "acb", "bac", "bca", "cab", "cba"].
  24. Final Result:
    • Return the final list of permutations: ["abc", "acb", "bac", "bca", "cab", "cba"].

Advanced Examples

1. Recursive Binary Search

Binary search is an efficient algorithm for finding an item from a sorted list of items.

Recursive Definition:

  • Base case: If the list is empty, the item is not found.
  • Recursive case: Compare the target with the middle element and recursively search in the appropriate half.

Python Implementation:

def binary_search(arr, target, low, high):
if low > high:
return -1 # Target is not found
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
return binary_search(arr, target, mid + 1, high)
else:
return binary_search(arr, target, low, mid - 1)

# Testing the binary_search function
arr = [1, 2, 3, 4, 5, 6, 7]
target = 5
print(binary_search(arr, target, 0, len(arr) - 1)) # Output: 4

2. Tower of Hanoi

The Tower of Hanoi is a classic problem where you have to move a set of disks from one rod to another, with the help of a third rod, following certain rules.

Recursive Definition:

  • Base case: If there is only one disk, move it directly.
  • Recursive case: Move n−1 n-1 n−1 disks to the auxiliary rod, move the nth disk to the target rod, then move the n−1 n-1 n−1 disks from the auxiliary rod to the target rod.

Python Implementation:

def tower_of_hanoi(n, source, target, auxiliary):
if n == 1:
print(f"Move disk 1 from {source} to {target}")
return
tower_of_hanoi(n - 1, source, auxiliary, target)
print(f"Move disk {n} from {source} to {target}")
tower_of_hanoi(n - 1, auxiliary, target, source)

# Testing the tower_of_hanoi function
tower_of_hanoi(3, 'A', 'C', 'B')
# Output:
# Move disk 1 from A to C
# Move disk 2 from A to B
# Move disk 1 from C to B
# Move disk 3 from A to C
# Move disk 1 from B to A
# Move disk 2 from B to C
# Move disk 1 from A to C

Recursive Function Design Principles

  1. Define the Problem Clearly: Break down the problem into smaller subproblems that resemble the original problem.
  2. Identify the Base Case(s): Ensure the recursion terminates by defining base case(s) that are simple and solvable without further recursion.
  3. Simplify the Problem: Ensure each recursive call works on a simpler or smaller version of the problem, progressing towards the base case.
  4. Avoid Redundant Calculations: Use techniques like memoization to store results of expensive recursive calls to avoid redundant calculations.
  5. Test Thoroughly: Recursion can be tricky to debug. Test your recursive functions with a variety of inputs, including edge cases, to ensure correctness.

By mastering these principles and practicing with different problems, you’ll become proficient in writing and understanding recursive functions in Python.


Discover more from AI HintsToday

Subscribe to get the latest posts sent to your email.

Table of Contents

    Trending

    Discover more from AI HintsToday

    Subscribe now to keep reading and get access to the full archive.

    Continue reading