PPL-PT-II Question Bank Ans Key
PPL-PT-II Question Bank Ans Key
Part-A
Non-atomic: Operations that are non-atomic but interruptible by multiple processes can cause
problems.
Race conditions: A race condition occurs of the outcome depends on which of several
processes gets to a point first.
Blocking: Processes can block waiting for resources. A process could be blocked for long
period of time waiting for input from a terminal. If the process is required to periodically update
some data, this would be very undesirable.
Starvation: Starvation occurs when a process does not obtain service to progress.
Deadlock: Deadlock occurs when two processes are blocked and hence neither can proceed to
execute
A process takes more time to terminate. A thread takes less time to terminate.
It takes more time for creation. It takes less time for creation.
A process does not share data with each other. Threads share data with each other.
An event represents an occurrence that other parts of the system or program may listen for and
respond to. Events can trigger specific actions, such as handling user input, completing a task, or
signaling a change in state.
Ex:
square = lambda x: x ** 2
12. What are the fundamental design considerations for parameter-passing methods?
Efficiency: The method should be efficient in terms of memory and time. For example, passing
large data by reference is faster than copying it by value.
Functionality and Safety: The method should allow the function to behave as expected. Pass-by-
value prevents changes to the original data, while pass-by-reference allows changes to the
original data
13. Define exception handling
Exception handling allows programmers to detect and manage errors during runtime, ensuring that
the program continues executing or provides meaningful error messages rather than crashing
14. What is concurrent programming?
Concurrent programming is a programming paradigm that allows multiple tasks or processes to run
simultaneously or in an overlapping manner, improving resource utilization and performance. It is
often achieved through multithreading or parallelism
15. Explain the following terms : (a) Message passing (b) Monitors
A preprocessor directive is a special instruction given to the preprocessor to perform tasks before the
actual compilation of the code. These tasks typically involve including external files, defining
constants, or controlling code inclusion based on conditions.
Example:
Note: **
Currying and a curried function are related concepts, but they are not exactly the same:
1. Constant Definition
2. Macro Definition
1. Constant Definition: This form is used to define a constant value using the #define directive. The
defined value can be used throughout the program. Example:
#define PI 3.14
2. Macro Definition: This form defines a macro that can take arguments and be used like a function.
It helps in reusing code.
Example:
REPL stands for Read-Eval-Print Loop. REPL is commonly used in languages like Python,
JavaScript, and Lisp for interactive development and testing. It refers to an interactive programming
environment where:
Part-B
1. Show the stack with all activation record instances, including the dynamic chain, when
execution reaches position 1 in the following skeletal program. This program uses the deep-
access method to implement dynamic scoping
void fun1() {float a; }
void fun2() { int b,c; }
void fun3() { float d; }
void main() { char e, f, g; }
The calling sequence for this program for execution to reach fun3 is
main calls fun2
fun2 calls fun1
fun1 calls fun1
fun1 calls fun3
Answer:
The scenario describes a program execution using the deep-access method of dynamic scoping. To illustrate
the stack with all activation record instances including the dynamic chain as the execution reaches position 1
in fun3, let's follow the calling sequence:
fun1 is then called by fun2, thereby placing its activation record on top of fun2's.
Next, fun1 calls itself recursively, which adds another fun1 activation record on the stack.
At position 1 in fun3, the stack from top to bottom (with the dynamic chain) would be:
Each activation record would typically contain information such as local variables, parameters, dynamic link
to the caller, return address, and possibly other bookkeeping information.
2. Consider the following program written in C syntax complete the program with full code
void main()
{ int value = 2, list[5] = {1, 3, 5, 7, 9};
swap(value, list[0]);
swap(list[0], list[1]);
swap(value, list[value]);
} For each of the following parameter-passing methods, what are all of the values of the
variables value and list after each of the three calls to swap?
a. Passed by value
b. Passed by reference
c. Passed by value-result
Discuss the design issues of Exception Handling.
Answer:
Parameter-Passing Methods:
In pass-by-value, the values of value and list[0] are copied into the parameters a and b in the swap
function. Any changes made to a andb inside swap do not affect the original variables in main.
1. Initial values:
o value = 2
o list = {1, 3, 5, 7, 9}
value = 2
list = {1, 3, 5, 7, 9}
In pass-by-reference, the addresses (references) of value and the elements of list are passed to the
function. As a result, changes made to the parameters a andb inside swap directly affect the original
values of value and list.
1. Initial values:
o value = 2
o list = {1, 3, 5, 7, 9}
value = 2
list = {3, 1, 5, 7, 9}
In pass-by-value-result, the argument is passed by value initially, but the result is copied back to the
actual parameter after the function completes. This method has characteristics of both pass-by-value
(input) and pass-by-reference (output).
1. Initial values:
o value = 2
o list = {1, 3, 5, 7, 9}
value = 2
list = {3, 1, 5, 7, 9}
3. What is an event? How the events are handled in various OOP languages.
An event is an action or occurrence that can be detected by the program, typically initiated by the user
(mouse clicks, keyboard presses etc.), or by the system (like timers or file changes).
1. Java
In Java, events are handled using Event Listeners and Listeners. Java provides a rich set of event
classes (like ActionEvent, MouseEvent, etc.), and listeners (like ActionListener, MouseListener) that
handle these events.
Example:
import java.awt.event.*;
import javax.swing.*;
frame.add(button);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
In this example, the ActionListener handles the click event on the button.
2. C#
In C#, events are handled using delegates. An event is declared using the event keyword and is
usually triggered by a specific action.
Example:
using System;
class Program {
public delegate void EventHandler(object sender, EventArgs e);
public event EventHandler MyEvent;
Here, an event MyEvent is triggered by invoking the TriggerEvent method, and a handler is added
using the += operator.
3. JavaScript
JavaScript handles events in a browser environment using Event Listeners. DOM elements can listen
for events (like click, keydown, etc.) and execute specified callback functions.
Example:
javascript
Copy
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
alert('Button clicked!');
});
In this example, the event listener listens for the click event on the button and shows an alert when it
occurs.
Python handles events using libraries such as Tkinter (for GUIs) or Pygame (for games). In Tkinter,
events are handled by binding event handlers to specific widgets.
Example with Tkinter:
import tkinter as tk
def on_button_click():
print("Button clicked!")
root = tk.Tk()
button = tk.Button(root, text="Click me", command=on_button_click)
button.pack()
root.mainloop()
Deep-Access Method:
The deep-access method refers to how dynamic scoping is implemented by searching for variable
bindings within the call stack. This method is so-called because it involves "deeply" searching
through the active function calls at runtime, starting from the current function and moving outward
through its caller functions.
1. Initial Variable Lookup: When a variable is accessed inside a function, the runtime
environment first checks if the variable is locally defined within that function.
2. Search Through Call Stack: If the variable is not found locally, the system then searches the
caller's environment, and if it is still not found, the search continues further up the call stack
through each function call that led to the current execution point.
3. Dynamic Binding: This search is performed dynamically at runtime, meaning that the value
of a variable can change depending on the context of the function calls at that particular
moment.
4. Global Scope: If the variable is not found within the call stack, it is checked in the global
environment (the environment outside all functions). If the variable is not found there, an error
is typically raised.
function A() {
B(); // call to B
function B() {
Flexibility
Easy of Use
Difficulty in Debugging
Unintended Variable Shadowing
Performance Overhead
Synchronization in programming refers to the coordination of multiple threads or processes to ensure that they
execute in a safe and predictable manner, especially when they share resources like memory, files, or
hardware. Without proper synchronization, concurrent threads or processes may access shared resources at the
same time, causing conflicts, data corruption, or unpredictable behavior.
1. Locks (Mutexes)
A lock (also called a mutex) is the simplest and most common method for ensuring that only one thread can
access a shared resource at a time. When a thread locks a resource, other threads are blocked from accessing it
until the lock is released.
Mutex (Mutual Exclusion): A mutex is an object used to manage access to a resource. A thread must
acquire the mutex before accessing the shared resource, and release it when done.
# Shared resource
counter = 0
def increment():
global counter
with lock: # Lock the resource before accessing
counter += 1
print(f"Counter value: {counter}")
# Create threads
threads = []
for _ in range(5):
t = threading.Thread(target=increment)
threads.append(t)
t.start()
for t in threads:
t.join() # Wait for all threads to complete
2. Semaphores
A semaphore is a synchronization primitive that controls access to a shared resource by maintaining a set of
permits. It allows a specified number of threads to access a resource simultaneously. If no permits are
available, threads must wait until a permit is released.
Binary Semaphore (also called a Mutex): This is a semaphore with only two states (0 or 1). It can be
used for mutual exclusion, similar to a lock.
Counting Semaphore: It allows a fixed number of threads to access a shared resource concurrently.
def access_resource(thread_id):
print(f"Thread-{thread_id} is waiting for the semaphore")
semaphore.acquire() # Wait for a permit
print(f"Thread-{thread_id} is accessing the resource")
time.sleep(2) # Simulate some work
print(f"Thread-{thread_id} is releasing the semaphore")
semaphore.release() # Release the permit
# Create threads
threads = []
for i in range(5):
t = threading.Thread(target=access_resource, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join() # Wait for all threads to complete
3. Condition Variables
A condition variable allows threads to wait for certain conditions to be true before proceeding with
execution. Condition variables are often used in conjunction with a lock or mutex to protect access to shared
resources.
Threads can wait for a condition to be met, and another thread can signal when the condition is met (usually
through notify() or notify_all()).
def wait_for_increment():
with condition:
condition.wait() # Wait for notification
print("Condition met, proceeding with work")
# Create threads
threads = []
for _ in range(3):
t = threading.Thread(target=wait_for_increment)
threads.append(t)
t.start()
for t in threads:
t.join()
increment_thread.join()
4. Read-Write Locks
A read-write lock allows multiple threads to read from a shared resource simultaneously, but gives exclusive
access to one thread for writing. This improves performance when there are frequent read operations and few
write operations.
Read Lock: Multiple threads can acquire the read lock and read the resource simultaneously.
Write Lock: Only one thread can acquire the write lock, and it has exclusive access to the resource.
# Shared resource
data = 0
def release_read(self):
with self.read_lock:
self.readers -= 1
if self.readers == 0:
self.write_lock.release() # Last reader releases the writer lock
def acquire_write(self):
self.write_lock.acquire()
def release_write(self):
self.write_lock.release()
def write_data(value):
rw_lock.acquire_write()
global data
data = value
print(f"Writing data: {data}")
rw_lock.release_write()
for t in threads:
t.join()
5. Barriers
A barrier is a synchronization mechanism used to block threads until all threads reach a certain point in the
program, ensuring they proceed together. This is commonly used in parallel computing when multiple threads
need to synchronize at specific points.
Example in Python (using threading):
def task(thread_id):
print(f"Thread-{thread_id} starting")
barrier.wait() # Wait for other threads to reach the barrier
print(f"Thread-{thread_id} proceeding after the barrier")
# Create threads
threads = []
for i in range(3):
t = threading.Thread(target=task, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
ML (Meta Language) family of programming languages, can compute the factorial function using recursion
or an iterative approach. The recursive approach is the most natural way to express the factorial function, as
it directly reflects the mathematical definition of factorial.
A Recursive function can be defined as a routine that calls itself directly or indirectly. It is
particularly useful for problems that can be broken down into smaller subproblems, such as computing
the factorial of a number.
def factorial(n):
# Base case
if n == 0:
return 1
# Recursive case
return n * factorial(n - 1)
# Driver Code
if __name__ == "__main__":
n=4
print("Factorial of", n, "is:", factorial(n))
Output:
Factorial of 4 is:24
7. Write a prolog description of your family tree (based only on facts), going back to your
grandparents and including all descendants. Be sure to include all relationships.
Facts: These are statements about specific individuals (like "John is Mary's parent").
Rules: These are logical statements that help derive new facts (like "John is Mary's grandparent if
John is the parent of Mary's parent").
Example :
% Facts about parents
parent(john, mary). % John is the parent of Mary
parent(john, james). % John is the parent of James
parent(jane, mary). % Jane is the parent of Mary
parent(jane, james). % Jane is the parent of James
niece_or_nephew(X, Y) :-
aunt_or_uncle(Y, X),
male(X). % Assuming X is a male to be a nephew
catch(int) { . . . }
}
catch(float) { . . . }
} In each of the four throw statements, where is the exception handled? Note that fun1 is called from
fun2 in class Small.
In this C++ skeletal program, there are multiple throw statements inside two classes: Big and Small. We need
to understand where each exception is caught based on the throw statements inside both classes.
Class Big and fun1() Method:
class Big {
int i;
float f;
In the Big class, the fun1() method throws two types of exceptions:
1. throw i;: This throws an exception of type int (since i is of type int).
2. throw f;: This throws an exception of type float (since f is of type float).
The first exception (throw i;) is thrown inside fun1() and is an int. The program will look for a
catch(int) block, but there is none explicitly present. However, if this exception is thrown, it will not
be caught by the catch(float) block, because it is only designed to handle float exceptions. In this case,
the program doesn't show the catch(int) block, so this exception may cause the program to
terminate unless explicitly handled elsewhere (not shown in the code).
The second exception (throw f;) is a float exception and is caught by the catch(float) block inside
the fun1() method. This is where the float exception is handled.
cpp
Copy
class Small {
int j;
float g;
In the Small class, the method fun2() calls fun1() from Big. There are two throw statements in fun2():
Inside fun2(), there is a try-catch block that surrounds the call to Big::fun1(). After that, there are two
throw statements.
9. Write a program that asks the user for a number and displays ten times the number. Also the program
must throw an exception when the user enters a value greater than 10
10. Explain about Parameter Passing methods with detailed example.
11. Define monitor. Explain how cooperation synchronization and competition synchronization are
implemented using monitors.
12. Explain about the types of Dynamic Scoping.
In deep access dynamic scoping, the variable lookup starts from the current function's activation
record, and if the variable isn't found, the search continues back through the stack (activation records
of previous function calls) to find the variable. The lookup will follow the entire call chain.
How it works:
If a function accesses a variable, the search for that variable starts from its own activation
record.
If the variable is not found in the current function, it checks the activation record of the
function that called it.
This continues until the search reaches the top of the call stack (the main function or the
program's entry point).
Advantages:
It allows functions to have access to variables from any function in the call stack, providing
flexibility.
Disadvantages:
Deep dynamic scoping can make it difficult to reason about variable bindings and values,
since variables from any function in the call chain can be accessed at any point.
It can also make the program harder to maintain and debug, as it increases the chances of
variable name conflicts or unintended side effects.
In shallow access dynamic scoping, when a variable is referenced, it is first looked up in the current
function’s activation record. However, if the variable is not found in the current function, it only looks
in the immediate caller's activation record, not continuing up the stack beyond that.
How it works:
If a function accesses a variable, the search first looks in its own activation record.
If the variable is not found, the search moves one level up the call stack (the calling function's
activation record), but does not go beyond that. The search stops there.
Advantages:
It is easier to understand and debug than deep dynamic scoping, because it is clear that only
the immediate caller's scope is relevant.
The scope of variable lookup is restricted, which can prevent unintended variable shadowing
or conflicts.
Disadvantages:
It is less flexible than deep dynamic scoping, because it only allows access to the most
immediate caller's scope.
This can limit the ability of functions to access variables in more distant function calls in the
call chain.
Types of Subprograms
Functions: A function takes input, performs operations, and returns a value. It may
take parameters and has a return type.
o Example: In C++, a function to calculate the factorial of a number:
int factorial(int n) {
if (n == 0) return 1;
else return n * factorial(n - 1);
}
Procedures: Procedures perform an action but do not return a value. They may
modify the state or execute side effects.
o Example: In Pascal, a procedure to print a message:
class Calculator {
public int add(int a, int b) {
return a + b;
}
}
Subprograms often accept input parameters (arguments) to operate on. Parameters are defined
in the subprogram's header, while arguments are passed when the subprogram is called.
Call-by-Value: The actual value of the argument is passed, meaning changes to the
parameter do not affect the argument.
Call-by-Reference: A reference to the actual argument is passed, meaning any change
in the parameter will affect the argument.
Local Variables: Variables declared inside a subprogram. They are created when the
subprogram is invoked and destroyed when the subprogram exits.
Global Variables: Variables declared outside any subprogram and can be accessed by
any part of the program. However, relying on global variables in subprograms may
lead to errors due to shared access.
5. Return Values
6. Recursive Subprograms
int factorial(int n) {
if (n == 0) return 1;
return n * factorial(n - 1);
}
Example in Python:
In OOP languages (like Java, C++, Python), subprograms are often encapsulated within
classes as methods. Methods operate on the data (attributes) of the object and can modify its
state.
Methods can have access modifiers (public, private) to control visibility and
encapsulation.
Constructors are special subprograms used for initializing objects.
class Car {
String model;
9. Subprogram Overloading
Languages like C++, Java, and Python, subprograms can be overloaded. This means you can
define multiple subprograms with the same name but different parameters.
Features of LISP:
In PROLOG, a list is a fundamental data structure used to store collections of items. Lists are an
ordered sequence of elements enclosed in square brackets ([]). Each list can hold any type of data,
including atoms, numbers, or other lists.
Example:
[1, 2, 3, 4].
In PROLOG, you can access the head (first element) and tail (remaining elements) of a list using
pattern matching.
Example:
Head = 1,
Tail = [2, 3, 4].
List Operations:
PROLOG has several built-in predicates for manipulating lists, such as:
member(X, List): Checks if element X is a member of the list.
append(List1, List2, Result): Appends two lists.
length(List, Length): Finds the length of a list.
Example of member/2:
In PROLOG, a goal statement is essentially a query that asks the system to find values that satisfy
certain conditions defined by facts and rules. A goal is a query that PROLOG tries to satisfy using
backtracking—systematically exploring possible solutions.
Goal statements are written as predicates (logical assertions or relationships) followed by arguments.
These predicates can be:
A goal can be a simple fact or a complex query involving multiple conditions and logic.
Example of a fact:
father(john, mary).
Example of a goal:
father(john, X).
This goal asks, "Who is the child of John?" The system will return X = mary based on the fact
father(john, mary).
Example of a Rule:
This rule states that X is an ancestor of Y if X is the father of Y. The :- symbol is read as "if" or "is
true if."
Backtracking in PROLOG:
PROLOG uses backtracking to find solutions to goals. If the first attempt to satisfy a goal fails,
PROLOG tries other possible options (facts and rules) systematically until it either finds a solution or
exhausts all possibilities.
father(john, mary).
father(john, paul).
father(john, X).
This goal will first match father(john, mary) and return X = mary. Then, backtracking occurs, and
PROLOG will match father(john, paul) and return X = paul.