FP Final Key-2
FP Final Key-2
Short Answers:
Q1. What is functional programming ?
A. Functional programming (FP) is a programming paradigm where you build software by
composing pure functions, avoiding shared state and mutable data. The key idea is to treat
computation as the evaluation of mathematical functions and avoid changing state or
producing side effects.
Q2. Identify the Python method used to add a new key-value pair to the dictionary.
A. In Python, the method used to add a new key-value pair to a dictionary is the update()
method or by simply using the square bracket ([]) notation.
Output:
{'a': 1, 'b': 2, 'c': 3}
Output:
{'a': 1, 'b': 2, 'c': 3}
Q3. Given the statement import math, which function would you use to calculate the
square root of the number?
A. After importing the math module in Python, you can use the math.sqrt() function to
calculate the square root of a number.
For example:
import math
result = math.sqrt(16)
print(result)
Output: 4.0
The math.sqrt() function returns the square root of the given number. If the number is
negative, it will raise a ValueError.
Q4. Explain the difference between a try block and an except block in Python.
A.
1. Try Block:
● The try block is used to wrap the code that you want to test for exceptions (errors).
● You place the code that may raise an error inside the try block. If no error occurs,
the program continues as normal.
2. Except Block:
● The except block is used to handle the exception (error) that was raised in the try
block.
● It "catches" the exception and allows you to handle it (e.g., print a custom message,
log the error, or perform an alternative action).
Q5. What Python module is commonly used to send emails using the SMTP protocol?
A. In Python, the smtplib module is commonly used to send emails using the SMTP
(Simple Mail Transfer Protocol). This module provides a simple interface for sending email
to an SMTP server.
Q6. Given a database connection, which Python module function is used to execute a
SQL query?
A. In Python, when working with a database connection (like with SQLite, MySQL,
PostgreSQL, etc.), the execute() method is commonly used to run a SQL query.
Higher-Order Functions
2. Functions as Return Values: A higher-order function can return another function as its
result. This is often used in function composition or partially applied functions.
Q8. How does modularity in Haskell improve the maintainability of large codebases?
A. Modularity in Haskell improves maintainability by breaking code into small, reusable, and
focused components. Key benefits include:
4. Abstraction:
5. Lazy Evaluation:
6. Declarative Style:
7. Concurrency:
8. Package Management:
A. Recursion plays a central role in Lisp programming due to the language’s functional
nature and its emphasis on manipulating recursive data structures like lists. In Lisp, recursion
is often used as the primary mechanism for iteration and defining repetitive tasks.
Long Answers:
Q11. Write a Python program to demonstrate the usage of the map() and filter()
functions. Explain how these functions align with functional programming principles.
A. Here's a Python program that demonstrates the usage of the map() and filter()
functions:
numbers = [1, 2, 3, 4, 5]
def square(x):
return x ** 2
# Apply the square function to each element in the list using map()
def is_even(x):
return x % 2 == 0
Output:
Q12. Analyze the advantages and disadvantages of using functional programming over
object-oriented programming in large-scale software development.
○ Disadvantage: While FP often uses immutable data and functions that don't
modify state, this can lead to performance overheads. Creating new copies of
data structures can be inefficient in cases where large amounts of data need to
be updated frequently.
3. Complexity with Side Effects:
○ Advantage: OOP languages typically have a larger set of libraries, tools, and
frameworks that are specifically built for large-scale software development.
4. Easier for Teams to Work Together:
○ Advantage: OOP’s clear division of responsibilities between classes and
objects makes it easier for teams to work collaboratively, as different team
members can work on different objects or modules without much overlap.
Q13. Explain the immutability of tuples in python. How does this property align with
functional programming? Provide examples using tuple methods like Count( ) and
Index( ).
In Python, tuples are immutable sequences, meaning that once a tuple is created, its
elements cannot be modified, added, or removed. This immutability property is one of the
key characteristics that sets tuples apart from lists, which are mutable.
1. Predictability: Since data can't change unexpectedly, the behavior of the program is
more predictable.
2. Safety: Avoiding side effects (i.e., changes to shared state) makes the code safer,
particularly in multi-threaded or concurrent environments.
3. Referential Transparency: Functions in FP should always return the same output for
the same input. Immutability helps enforce this behavior.
1. count()
The count() method returns the number of times a specific element appears in the tuple. It
does not modify the tuple, which is consistent with the idea of immutability.
Example of count():
# Creating a tuple
my_tuple = (1, 2, 3, 2, 4, 2)
count_of_2 = my_tuple.count(2)
2. index()
The index() method returns the index of the first occurrence of a specified element. It
doesn't alter the tuple, reinforcing the idea of immutability.
Example of index():
# Creating a tuple
index_of_30 = my_tuple.index(30)
Q14. How do Python string methods like split(), join(), and replace support functional
programming? Write a Python program to process a paragraph of text using these
methods.
A. Python string methods like split(), join(), and replace() align well with
functional programming principles. Here's how:
1. split(): This method splits a string into a list of substrings based on a delimiter.
2. join(): This method joins elements of an iterable (like a list or tuple) into a single
string, separating them with a specified delimiter.
Let’s write a Python program that processes a paragraph of text using the split(),
join(), and replace() methods.
Example Program:
and mutable data, which makes programs easier to test and reason about."""
sentences = paragraph.split('.')
print("Original Paragraph:")
print(paragraph)
print("\nModified Paragraph:")
print(modified_paragraph)
Output:
Original Paragraph:
and mutable data, which makes programs easier to test and reason about.
Modified Paragraph:
and mutable data, which makes programs easier to test and reason about.
Exception handling in Python is a mechanism that allows you to manage errors gracefully
without interrupting the normal flow of a program. When an error occurs (an exception is
raised), the program can catch that error and handle it in a controlled way using a
try-except block.
Python's exception handling is built around the try, except, else, and finally
blocks:
1. try: Code that might raise an exception is placed inside the try block.
2. except: If an exception occurs in the try block, Python will look for an appropriate
except block to handle the exception.
3. else: If no exception occurs in the try block, the code inside the else block will
execute.
4. finally: This block will execute no matter what, whether or not an exception
occurred. It's typically used for cleanup actions like closing files or releasing
resources.
Python allows you to handle multiple exceptions in several ways. You can:
def divide_numbers():
try:
# Input values
# Perform division
except ZeroDivisionError:
except ValueError:
except Exception as e:
finally:
print("Execution completed.")
divide_numbers()
Example Output:
Execution completed.
Execution completed.
Execution completed.
Q16. Analyze the advantage of using tuple methods over list methods for storing and
retrieving immutable data. Provide examples of key tuple methods like index() and
count().
A. Advantages of Using Tuple Methods Over List Methods for Storing and Retrieving
Immutable Data
Tuples and lists are both sequence data types in Python, but they serve different purposes and
have distinct characteristics. The key advantage of using tuple methods over list methods
when working with immutable data lies in their immutability property, which provides
several benefits:
Advantage over Lists: Lists are mutable, so their data can be modified after creation, leading
to potential issues like unintentional changes or side effects. Tuples, on the other hand,
prevent such modifications, making them more reliable in scenarios requiring constant data.
Advantage over Lists: Lists incur overhead due to their dynamic nature, which allows
modification of data (adding/removing elements). This makes lists less efficient than tuples
when dealing with large datasets that do not need modification.
1. index() Method:
The index() method returns the index of the first occurrence of a specified value in the
tuple. If the value is not found, it raises a ValueError.
Example:
index_of_30 = my_tuple.index(30)
2. count() Method:
The count() method returns the number of occurrences of a specified element in the tuple.
Example:
my_tuple = (1, 2, 3, 2, 4, 2, 5)
count_of_2 = my_tuple.count(2)
# Output: Count of 2: 3
Q17. Evaluate the advantages of file modes (r, w, a, rb, etc.) in Python's file handling.
provide examples to demonstrate their usage.
A. Advantages of Different File Modes
r (Read): Simple and effective for reading content from existing files.
w (Write): Useful for creating or overwriting files, ensuring you start with fresh content.
a (Append): Keeps existing content intact while adding new data, ideal for log files or
growing data.
rb, wb, ab (Binary Modes): Essential for handling binary data (e.g., images, videos), where
text encoding doesn’t apply.
r+, w+ (Read/Write Modes): Combine reading and writing capabilities, enabling in-place
modification of file contents.
x (Exclusive Creation): Prevents overwriting existing files, ensuring safe creation of new
files.
content = file.read()
print(content)
● Use Case: Overwrite or create a new file newfile.txt with the specified content.
data = file.read()
binary_data = b'\x00\x01\x02\x03'
file.write(binary_data)
content = file.read()
file.seek(0)
file.write("New content")
file.seek(0)
● Use Case: Overwrite and read back the content of the file.
try:
except FileExistsError:
● Use Case: Ensure the file exclusive.txt is created only if it doesn't already
exist.
File Input/Output (I/O) in Python refers to the process of reading data from files and writing
data to files. Python provides built-in support for interacting with files through a series of
functions and methods.
File I/O Methods in Python:
def append_and_read_file(filename):
print(content)
filename = 'example.txt'
append_and_read_file(filename)
Output After Execution: The contents of the file example.txt will be updated and
printed:
Hello, this is the first line.
Q19. Explain the concept of sending emails in Python. explain the concept of smtplib
module and provide an example to send a simple email without attachments.
Sending emails in Python can be done using the smtplib module, which is a built-in
Python library designed to interact with SMTP (Simple Mail Transfer Protocol) servers.
SMTP is the protocol used to send emails over the internet. The smtplib module allows
Python programs to send emails via an SMTP server, and it supports sending both plain-text
and HTML emails. It can also be used to send emails with attachments, although that requires
additional modules like email.
● SMTP (Simple Mail Transfer Protocol): SMTP is the standard protocol for sending
emails. It is used by email servers to send email messages to each other.
● SMTP Server: An SMTP server is used to send emails, such as Gmail's SMTP server
(smtp.gmail.com), which allows you to send emails through a Gmail account.
● Authentication: When sending emails, authentication is required to ensure that only
authorized users can send messages from an email address.
Let's send a basic plain-text email using Gmail’s SMTP server (smtp.gmail.com). The
program will connect to the SMTP server, authenticate the sender’s email account, and send a
simple email message.
def send_email():
sender_email = "[email protected]"
receiver_email = "[email protected]"
password = "your_email_password" # Your email account password (for Gmail, you may
need an app-specific password)
msg = MIMEMultipart()
msg['From'] = sender_email
msg['To'] = receiver_email
body = "Hello, this is a test email sent from Python using smtplib."
msg.attach(MIMEText(body, 'plain'))
try:
except Exception as e:
finally:
server.quit()
send_email()
Example Output:
Q20. Evaluate the advantages of using generators over iterators in Python. Write a
generator function to yield prime numbers below a given limit and explain its efficiency.
Both generators and iterators in Python are tools that allow you to iterate over a sequence of
items. However, there are significant advantages to using generators, especially in terms of
efficiency and ease of use.
○ Generators produce values lazily, meaning they only compute and yield a
value when required (on demand), whereas iterators typically require all
elements to be generated or stored upfront. This makes generators much more
memory efficient, especially when dealing with large datasets or infinite
sequences.
2. Memory Efficiency:
○ Since generators only yield one item at a time and do not store the entire
collection in memory, they are ideal for iterating over large data or sequences
that might be too large to fit in memory at once. On the other hand, iterators
might need to load the entire dataset into memory, which can be inefficient for
large datasets.
3. Simpler and Cleaner Code:
○ Generators are easier to implement and read. You can write a generator
function using the yield keyword, which simplifies the code for sequences
that need to generate values on the fly. Iterators, on the other hand, require
more boilerplate code with the implementation of methods like __iter__()
and __next__().
4. Performance:
○ Since generators don’t store all items at once, they tend to have better
performance when working with large or infinite datasets because they only
compute the next value as needed, avoiding redundant computations or
memory overhead.
Let’s write a generator function to yield prime numbers below a given limit. We'll also
explain its efficiency.
Example Output:
Efficiency:
○ The generator doesn't store the entire list of primes in memory. It only
computes one prime at a time and yields it, making it highly memory efficient,
especially when working with larger values of limit.
2. Computational Efficiency:
○ The use of yield ensures that each prime number is calculated only when
requested. This is a more efficient approach compared to building an entire list
of primes upfront.
○ Additionally, the primality test uses the square root approach, which makes the
primality check more efficient than checking divisibility up to the number
itself.
3. Lazy Evaluation:
○ The prime numbers are generated lazily, so if we only need a few primes (e.g.,
for the first few iterations of the loop), the function won't compute and yield
the entire sequence. This improves performance when dealing with larger
limits.
Q21. Write a Python program to connect to an SQLite database, create a table, insert
some records, and fetch the data. Explain the usage of sqlite3 module.
A.
SQLite Python Program: Create Table, Insert Records, and Fetch Data
Here’s a step-by-step Python program that connects to an SQLite database, creates a table,
inserts some records, and fetches the data.
import sqlite3
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
cursor.execute('''
''')
rows = cursor.fetchall()
print(row)
conn.close()
Output:
2. conn.cursor(): Creates a cursor object, which is used to interact with the
database and execute SQL queries.
5. cursor.fetchall(): Fetches all the rows from the result of a SELECT query
and returns them as a list of tuples.
2. Inheritance:
Example:
# Parent class
class Animal:
self.name = name
def speak(self):
class Dog(Animal):
dog = Dog("Buddy")
cat = Cat("Whiskers")
3. Polymorphism:
Example:
# Parent class
class Shape:
def area(self):
pass
class Square(Shape):
self.side = side
def area(self):
return self.side ** 2
class Circle(Shape):
def area(self):
# Polymorphism in action
Output:
Area: 16
Area: 28.26
Q23. Explain the concept of lazy evaluation in Haskell. Provide an example program to
demonstrate how lazy evaluation handles infinite lists.
Because of lazy evaluation, Haskell can handle infinite lists or streams efficiently. An
infinite list is only evaluated as needed. For example, you can define an infinite list of
numbers like [1, 2, 3, 4, 5, ...], but Haskell will only calculate as many numbers
from that list as are actually required by the program.
Let's demonstrate lazy evaluation with an infinite list of natural numbers, and we will fetch
only the first few elements from the list.
naturals :: [Integer]
firstTenNaturals :: [Integer]
main :: IO ()
Output:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Q24. Write a Haskell program to demonstrate the use of higher-order functions. Use the
functions to process a list of integers.
In Haskell, higher-order functions are functions that take one or more functions as arguments
or return a function as a result. Higher-order functions are a fundamental feature of functional
programming, and they allow us to abstract common patterns of computation, making the
code more concise and reusable.
Let's write a Haskell program to demonstrate the use of higher-order functions by processing
a list of integers.
1. map
2. filter
3. foldr (fold right)
Example Program
main :: IO ()
main = do
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Output:
Processed result: 30
Q25. Create a Haskell program to calculate the factorial of a number using both
recursion and a higher-order function. Compare the approaches.
In Haskell, factorial can be calculated using both recursion (which is a fundamental concept
in functional programming) and higher-order functions. We will demonstrate both
approaches and compare them.
In the recursive approach, we define the factorial function by reducing the problem to smaller
subproblems. The base case is when the number is 0 or 1, where the factorial is 1. For any
other number n, the factorial is n * factorial(n - 1).
In this approach, we will use the foldr function to accumulate the product of numbers from
1 to n. The foldr function is a higher-order function that recursively processes the list and
accumulates a result.
Haskell Code
factorialRecursive 0 = 1
factorialRecursive n = n * factorialRecursive (n - 1)
main :: IO ()
main = do
let num = 5
putStrLn $ "Factorial of " ++ show num ++ " using recursion: " ++ show resultRec
putStrLn $ "Factorial of " ++ show num ++ " using foldr: " ++ show resultFoldr
Output:
1. Recursion:
Higher-order functions are one of the key features of Haskell and functional programming in
general. They allow for more abstract, modular, and reusable code. A higher-order function is
one that either:
The importance of higher-order functions in Haskell lies in their ability to allow you to treat
functions as first-class citizens. This leads to more powerful and expressive programming
patterns, such as:
In Haskell, higher-order functions such as foldr and foldl are commonly used for
reducing or accumulating values over a list or other data structures.
Both foldr (fold right) and foldl (fold left) are higher-order functions that process a list
by combining its elements using a binary function. They both reduce a list of values to a
single value (often referred to as "accumulation" or "reduction"), but they differ in the
direction in which they process the list.
1. foldr (Fold Right):
○ The foldr function processes the list from right to left. It takes a function, a
starting accumulator value, and a list. It applies the function to the elements of
the list, combining the elements starting from the rightmost element.
○
○ foldr f z [x1, x2, ..., xn] is equivalent to f x1 (f x2 (
... (f xn z) ... )).
2. foldl (Fold Left):
○ The foldl function processes the list from left to right. It also takes a
function, an accumulator, and a list, but it applies the function to the elements
starting from the leftmost element.
○
○ foldl f z [x1, x2, ..., xn] is equivalent to f (...(f (f z
x1) x2) ...) xn.
Let's look at examples to better understand how both foldr and foldl work.
main :: IO ()
main = do
let result = sumList [1, 2, 3, 4, 5]
●
● It starts by applying + from the rightmost element (5) and continues towards the
leftmost element (1), accumulating the sum.
main :: IO ()
main = do
●
● It starts by applying + from the leftmost element (1) and continues towards the
rightmost element (5), accumulating the sum.
○ .
main :: IO ()
main = do
Q27. Explain the basic data types in LISP. Provide examples to demonstrate the usage
of numbers, strings, and symbols in a LISP program.
LISP (LISt Processing) is one of the oldest programming languages and is known for its
symbolic expression (S-expression) structure. In LISP, everything is represented as a list or
an atom, with atoms being the basic building blocks of data. The core data types in LISP
include:
Below is a LISP program that demonstrates the usage of numbers, strings, and symbols.
(setq message (concatenate 'string greeting " Welcome to the world of LISP."))
(setq result (+ x y)) ; Adding the values of x and y using the symbols
Summary of Advantages:
● Modularity: Constants represented by symbols are easily reusable and maintainable across
the program
.● Maintainability: Changes to constant values only need to be made in one place, ensuring
consistency and reducing the risk of errors
● Consistency: Using symbols ensures that constants are referenced consistently throughout
the code.
.● Memory Efficiency: Symbols are stored once in a symbol table, reducing memory usage.
Symbols in LISP help make code more expressive and easier to read by providing meaningful
names for constants instead of using arbitrary values. When you use a symbol, the meaning
of the constant becomes clear, which improves the self-documenting nature of the code.
Example: Without symbols, constants like 3.14159 would appear in the code multiple
times. If you used a symbol like PI instead, it would be clear that the value represents the
mathematical constant for Pi.
;; Without symbol: The meaning of '3.14159' is unclear without context.
;; With symbol: 'PI' clearly represents the constant value for Pi.
(setq PI 3.14159)
2. Modularity
Symbols enable greater modularity by decoupling the constant’s value from its usage in the
code. Instead of hardcoding values throughout the program, you can define constants once
using symbols and refer to them wherever needed. This modular approach allows easier
updates to constants and enhances the reusability of code.
Example: If PI is used throughout the program, you only need to change its definition in one
place, rather than updating every occurrence of 3.14159 manually.
;; Define the constant once
(setq PI 3.14159)
● If the value of Pi ever changes or needs to be adjusted for precision, you can simply
update the definition of PI, and the rest of the code will automatically use the new
value.
Q29. Design a LISP program to define a list of student marks and calculate the average
mark.
LISP Code:
Output:
B. Demonstrate the use of basic list operations such as car, cdr and cons.
;; 1. Using car
2. Using cdr
3. Using cons:
Q30. Create a LISP program to generate the Fibonacci series up to a given number
using both recursion and iteration. Compare the performance of these approaches.
;; Example usage
(setq n 10)
(if (= n 0) a
b)))
;; Example usage
(setq n 10)
Example Output:
Comparing Performance:
Recursive Approach:
The recursive approach has exponential time complexity (O(2^n)) . This makes the recursive
approach inefficient for large values of n.
As the number grows, the number of function calls increases rapidly, leading to significant
performance degradation.
Iterative Approach:
The iterative approach has linear time complexity (O(n))This makes it much more efficient
for large values of n.
It avoids the overhead of recursion and repeated calculations, making it the preferred
approach for performance.