0% found this document useful (0 votes)
43 views

OOPs Practical Final

1. The document describes C++ classes for vectors and matrices with static members and methods. It includes a friend function for multiplying matrices and vectors. 2. A Matrix class is implemented with dynamic memory allocation using new/delete. Proper constructor, destructor, copy constructor and overloaded assignment operator are provided. 3. A Complex number class is implemented with operator overloading for arithmetic operations and type conversions from integer/double to complex and complex to double. Stream insertion and extraction operators are also overloaded.

Uploaded by

Rahul Verma
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
43 views

OOPs Practical Final

1. The document describes C++ classes for vectors and matrices with static members and methods. It includes a friend function for multiplying matrices and vectors. 2. A Matrix class is implemented with dynamic memory allocation using new/delete. Proper constructor, destructor, copy constructor and overloaded assignment operator are provided. 3. A Complex number class is implemented with operator overloading for arithmetic operations and type conversions from integer/double to complex and complex to double. Stream insertion and extraction operators are also overloaded.

Uploaded by

Rahul Verma
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 27

1.

Design C++ classes with static members, methods with default arguments, and friend

functions. (for example, design matrix and vector classes with static allocation, and a

friend function to do matrix-vector multiplication)


#include <iostream>

#include <vector>

class Vector {
private:
std::vector<double> elements;
public:
// Constructors
Vector() = default; // Default constructor
Vector(std::initializer_list<double> init) : elements(init) {}
// Method to get the size of the vector
static size_t size(const Vector& v) {
return v.elements.size();
}
// Friend function for matrix-vector multiplication
friend Vector matrixVectorMultiply(const Matrix& mat, const Vector& vec);
};
class Matrix {
private:
std::vector<std::vector<double>> elements;
public:
// Constructors
Matrix() = default; // Default constructor
Matrix(std::initializer_list<std::initializer_list<double>> init) : elements(init) {}
// Method to get the number of rows of the matrix
static size_t rows(const Matrix& mat) {
return mat.elements.size();
}
// Method to get the number of columns of the matrix
static size_t columns(const Matrix& mat) {
return (mat.elements.size() > 0) ? mat.elements[0].size() : 0;
}
// Friend function for matrix-vector multiplication
friend Vector matrixVectorMultiply(const Matrix& mat, const Vector& vec);
};
// Friend function implementation for matrix-vector multiplication
Vector matrixVectorMultiply(const Matrix& mat, const Vector& vec) {
// Check if the matrix and vector dimensions are compatible
if (Matrix::columns(mat) != Vector::size(vec)) {
std::cerr << "Error: Incompatible dimensions for matrix-vector multiplication." << std::endl;
return Vector(); // Return an empty vector in case of error
}
Vector result;
// Perform matrix-vector multiplication
for (size_t i = 0; i < Matrix::rows(mat); ++i) {
double sum = 0.0;
for (size_t j = 0; j < Matrix::columns(mat); ++j) {
sum += mat.elements[i][j] * vec.elements[j];
}
result.elements.push_back(sum);
}
return result;
}

int main() {
// Example usage
Matrix matrix({{1, 2, 3}, {4, 5, 6}});
Vector vector({2, 3, 4});
// Perform matrix-vector multiplication
Vector result = matrixVectorMultiply(matrix, vector);
// Display the result
std::cout << "Result of matrix-vector multiplication:" << std::endl;
for (double value : result.elements) {
std::cout << value << " ";
}
std::cout << std::endl;

return 0;
}

Output:
2. Implement matrix class with dynamic memory allocation and necessary methods. Give

proper constructor, destructor, copy constructor, and overloading of the assignment

operator.

#include <iostream>
#include <stdexcept>
class Matrix {
private:
size_t rows;
size_t columns;
double** elements;
public:
// Constructors
Matrix(size_t rows, size_t columns) : rows(rows), columns(columns) {
// Allocate memory for the matrix
elements = new double*[rows];
for (size_t i = 0; i < rows; ++i) {
elements[i] = new double[columns];
}

// Initialize elements to 0
for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < columns; ++j) {
elements[i][j] = 0.0;
}
}
}

// Destructor
~Matrix() {
// Deallocate memory for the matrix
for (size_t i = 0; i < rows; ++i) {
delete[] elements[i];
}
delete[] elements;
}

// Copy constructor
Matrix(const Matrix& other) : rows(other.rows), columns(other.columns) {
// Allocate memory for the new matrix
elements = new double*[rows];
for (size_t i = 0; i < rows; ++i) {
elements[i] = new double[columns];
}
// Copy elements from the other matrix
for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < columns; ++j) {
elements[i][j] = other.elements[i][j];
}
}
}
// Overloading the assignment operator
Matrix& operator=(const Matrix& other) {
if (this != &other) {
// Deallocate existing memory
for (size_t i = 0; i < rows; ++i) {
delete[] elements[i];
}
delete[] elements;
// Copy size
rows = other.rows;
columns = other.columns;
// Allocate new memory
elements = new double*[rows];
for (size_t i = 0; i < rows; ++i) {
elements[i] = new double[columns];
}
// Copy elements from the other matrix
for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < columns; ++j) {
elements[i][j] = other.elements[i][j];
}
}
}
return *this;
}
// Method to get the number of rows
size_t getRows() const {
return rows;
}
// Method to get the number of columns
size_t getColumns() const {
return columns;
}
// Method to access elements
double& at(size_t row, size_t col) {
if (row >= rows || col >= columns) {
throw std::out_of_range("Matrix indices out of range");
}
return elements[row][col];
}
// Method to display the matrix
void display() const {
for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < columns; ++j) {
std::cout << elements[i][j] << " ";
}
std::cout << std::endl;
}
}
};
int main() {
// Example usage
Matrix mat1(2, 3);
mat1.at(0, 0) = 1.0;
mat1.at(0, 1) = 2.0;
mat1.at(0, 2) = 3.0;
mat1.at(1, 0) = 4.0;
mat1.at(1, 1) = 5.0;
mat1.at(1, 2) = 6.0;
Matrix mat2 = mat1; // Copy constructor
Matrix mat3(3, 2);
mat3 = mat1; // Overloaded assignment operator
// Display matrices
std::cout << "Matrix 1:" << std::endl;
mat1.display();
std::cout << "Matrix 2 (copy of Matrix 1 using copy constructor):" << std::endl;
mat2.display();
std::cout << "Matrix 3 (assigned from Matrix 1 using overloaded assignment operator):" << std::endl;
mat3.display();
return 0;
}

Output:
3. Implement complex number class with necessary operator overloading and type

conversions such as integer to complex , double to complex, complex to double etc.

#include <iostream>
#include <cmath>

class Complex {
private:
double real;
double imag;

public:
// Constructors
Complex() : real(0.0), imag(0.0) {}
Complex(double r, double i) : real(r), imag(i) {}

// Getter functions
double getReal() const { return real; }
double getImag() const { return imag; }

// Operator overloading for basic arithmetic operations


Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}

Complex operator-(const Complex& other) const {


return Complex(real - other.real, imag - other.imag);
}

Complex operator*(const Complex& other) const {


return Complex((real * other.real) - (imag * other.imag),
(real * other.imag) + (imag * other.real));
}

Complex operator/(const Complex& other) const {


double denominator = (other.real * other.real) + (other.imag * other.imag);
if (denominator == 0.0) {
// Handle division by zero
throw std::invalid_argument("Division by zero");
}
return Complex(((real * other.real) + (imag * other.imag)) / denominator,
((imag * other.real) - (real * other.imag)) / denominator);
}

// Type conversion from double to complex


Complex(double r) : real(r), imag(0.0) {}
// Type conversion from int to complex
Complex(int r) : real(static_cast<double>(r)), imag(0.0) {}

// Type conversion from complex to double


operator double() const {
return real; // Consideration: You might want to return magnitude or other value.
}

// Stream insertion operator


friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << "(" << c.real << " + i" << c.imag << ")";
return os;
}

// Stream extraction operator


friend std::istream& operator>>(std::istream& is, Complex& c) {
char discard;
is >> discard >> c.real >> discard >> c.imag >> discard;
return is;
}
};

int main() {
// Example usage of the Complex class
Complex c1(3.0, 4.0);
Complex c2(1.5, 2.5);

// Arithmetic operations
Complex sum = c1 + c2;
Complex diff = c1 - c2;
Complex product = c1 * c2;
Complex quotient = c1 / c2;

std::cout << "c1: " << c1 << std::endl;


std::cout << "c2: " << c2 << std::endl;
std::cout << "Sum: " << sum << std::endl;
std::cout << "Difference: " << diff << std::endl;
std::cout << "Product: " << product << std::endl;
std::cout << "Quotient: " << quotient << std::endl;

// Type conversions
double c1AsDouble = static_cast<double>(c1);
int c2AsInt = static_cast<int>(c2);

std::cout << "c1 as double: " << c1AsDouble << std::endl;


std::cout << "c2 as int: " << c2AsInt << std::endl;

// Stream extraction and insertion


Complex inputComplex;
std::cout << "Enter a complex number (e.g., (3.0 + i4.0)): ";
std::cin >> inputComplex;
std::cout << "You entered: " << inputComplex << std::endl;

return 0;
}

Output:
4. Overload the new and delete operators to provide a custom dynamic allocation of

memory.

#include <iostream>
#include <new>

class Matrix {
private:
size_t rows;
size_t columns;
double** elements;

public:
// Overloaded new operator for custom memory allocation
void* operator new(std::size_t size) {
std::cout << "Custom new operator called. Size: " << size << std::endl;
return ::operator new(size);
}

// Overloaded delete operator for custom memory deallocation


void operator delete(void* ptr) noexcept {
std::cout << "Custom delete operator called." << std::endl;
::operator delete(ptr);
}

// Constructors, destructor, and other methods remain the same...

// Method to get the number of rows


size_t getRows() const {
return rows;
}

// Method to get the number of columns


size_t getColumns() const {
return columns;
}

// Method to access elements


double& at(size_t row, size_t col) {
if (row >= rows || col >= columns) {
throw std::out_of_range("Matrix indices out of range");
}
return elements[row][col];
}
// Method to display the matrix
void display() const {
for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < columns; ++j) {
std::cout << elements[i][j] << " ";
}
std::cout << std::endl;
}
}
};

int main() {
// Example usage
Matrix* mat = new Matrix(2, 3);

// Access and modify elements


mat->at(0, 0) = 1.0;
mat->at(0, 1) = 2.0;
mat->at(0, 2) = 3.0;
mat->at(1, 0) = 4.0;
mat->at(1, 1) = 5.0;
mat->at(1, 2) = 6.0;

// Display the matrix


std::cout << "Matrix:" << std::endl;
mat->display();

// Delete the matrix


delete mat;

return 0;
}

Output:
5. Develop C++ class hierarchy for various types of inheritances.

#include <iostream>
// Base class
class Animal {
public:
void eat() {
std::cout << "Animal is eating." << std::endl;
}
void sleep() {
std::cout << "Animal is sleeping." << std::endl;
}
};
// Single Inheritance
class Dog : public Animal {
public:
void bark() {
std::cout << "Dog is barking." << std::endl;
}
};
// Multiple Inheritance
class Bird {
public:
void fly() {
std::cout << "Bird is flying." << std::endl;
}
};
class FlyingDog : public Dog, public Bird {
public:
// Inherits both bark() from Dog and fly() from Bird
};
// Hierarchical Inheritance
class Cat : public Animal {
public:
void meow() {
std::cout << "Cat is meowing." << std::endl;
}
};
// Multilevel Inheritance
class Kitten : public Cat {
public:
void play() {
std::cout << "Kitten is playing." << std::endl;
}
};
int main() {
// Single Inheritance example
Dog myDog;
myDog.eat(); // Inherited from Animal
myDog.sleep(); // Inherited from Animal
myDog.bark(); // Specific to Dog
// Multiple Inheritance example
FlyingDog flyingDog;
flyingDog.eat(); // Inherited from Animal
flyingDog.sleep(); // Inherited from Animal
flyingDog.bark(); // Inherited from Dog
flyingDog.fly(); // Inherited from Bird

// Hierarchical Inheritance example


Cat myCat;
myCat.eat(); // Inherited from Animal
myCat.sleep(); // Inherited from Animal
myCat.meow(); // Specific to Cat

// Multilevel Inheritance example


Kitten myKitten;
myKitten.eat(); // Inherited from Animal
myKitten.sleep(); // Inherited from Animal
myKitten.meow(); // Inherited from Cat
myKitten.play(); // Specific to Kitten

return 0;
}
Output:
6. Design a simple test application to demonstrate dynamic polymorphism and RTTI

#include <iostream>
#include <vector>
#include <typeinfo>

// Base class
class Shape {
public:
virtual void draw() const {
std::cout << "Drawing a shape." << std::endl;
}

virtual ~Shape() = default; // Virtual destructor for proper cleanup


};

// Derived class: Circle


class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a circle." << std::endl;
}
};

// Derived class: Rectangle


class Rectangle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a rectangle." << std::endl;
}
};

int main() {
// Create a vector of shapes using dynamic polymorphism
std::vector<Shape*> shapes;
shapes.push_back(new Circle());
shapes.push_back(new Rectangle());
shapes.push_back(new Circle());
shapes.push_back(new Rectangle());

// Draw each shape using dynamic polymorphism


for (const Shape* shape : shapes) {
shape->draw();
}

// Demonstrate RTTI (Run-Time Type Information)


for (const Shape* shape : shapes) {
// Check the type using typeid and dynamic_cast
if (typeid(*shape) == typeid(Circle)) {
const Circle* circle = dynamic_cast<const Circle*>(shape);
if (circle != nullptr) {
std::cout << "Type: Circle" << std::endl;
}
} else if (typeid(*shape) == typeid(Rectangle)) {
const Rectangle* rectangle = dynamic_cast<const Rectangle*>(shape);
if (rectangle != nullptr) {
std::cout << "Type: Rectangle" << std::endl;
}
}
}

// Cleanup: Delete dynamically allocated shapes


for (Shape* shape : shapes) {
delete shape;
}

return 0;
}

Output:
7. Develop a template of the linked-list class and its methods.

#include <iostream>

template <typename T>


class LinkedList {
private:
// Node structure for the linked list
struct Node {
T data;
Node* next;
Node(const T& value) : data(value), next(nullptr) {}
};

Node* head; // Pointer to the head of the linked list

public:
// Constructor
LinkedList() : head(nullptr) {}

// Destructor
~LinkedList() {
clear();
}

// Function to insert a new element at the beginning of the list


void insertFront(const T& value) {
Node* newNode = new Node(value);
newNode->next = head;
head = newNode;
}

// Function to insert a new element at the end of the list


void insertEnd(const T& value) {
Node* newNode = new Node(value);
if (head == nullptr) {
head = newNode;
return;
}

Node* current = head;


while (current->next != nullptr) {
current = current->next;
}

current->next = newNode;
}
// Function to delete the first occurrence of a value in the list
void remove(const T& value) {
if (head == nullptr) {
return;
}

if (head->data == value) {
Node* temp = head;
head = head->next;
delete temp;
return;
}

Node* current = head;


while (current->next != nullptr && current->next->data != value) {
current = current->next;
}

if (current->next != nullptr) {
Node* temp = current->next;
current->next = current->next->next;
delete temp;
}
}

// Function to display the elements of the list


void display() const {
Node* current = head;
while (current != nullptr) {
std::cout << current->data << " ";
current = current->next;
}
std::cout << std::endl;
}

// Function to clear the entire list


void clear() {
while (head != nullptr) {
Node* temp = head;
head = head->next;
delete temp;
}
}
};

int main() {
// Example usage of the LinkedList template
LinkedList<int> intList;
intList.insertEnd(1);
intList.insertEnd(2);
intList.insertFront(0);
intList.display(); // Output: 0 1 2

intList.remove(1);
intList.display(); // Output: 0 2

// Example with strings


LinkedList<std::string> stringList;
stringList.insertEnd("Hello");
stringList.insertEnd("World");
stringList.display(); // Output: Hello World

return 0;
}

Output:
8. Develop templates of standard sorting algorithms such as bubble sort, insertion sort and

quick sort.

➢ Bubble Sort

#include <iostream>
#include <vector>

template <typename T>


void bubbleSort(std::vector<T>& arr) {
size_t n = arr.size();
for (size_t i = 0; i < n - 1; ++i) {
for (size_t j = 0; j < n - i - 1; ++j) {
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
}
}
}
}

int main() {
// Example usage of bubbleSort template
std::vector<int> intArray = {64, 34, 25, 12, 22, 11, 90};
bubbleSort(intArray);
std::cout << "Sorted array: ";
for (const auto& elem : intArray) {
std::cout << elem << " ";
}
std::cout << std::endl;

return 0;
}

Output:
➢ Insertion Sort

#include <iostream>
#include <vector>

template <typename T>


void insertionSort(std::vector<T>& arr) {
size_t n = arr.size();
for (size_t i = 1; i < n; ++i) {
T key = arr[i];
int j = i - 1;

while (j >= 0 && arr[j] > key) {


arr[j + 1] = arr[j];
--j;
}
arr[j + 1] = key;
}
}

int main() {
// Example usage of insertionSort template
std::vector<int> intArray = {64, 34, 25, 12, 22, 11, 90};
insertionSort(intArray);
std::cout << "Sorted array: ";
for (const auto& elem : intArray) {
std::cout << elem << " ";
}
std::cout << std::endl;

return 0;
}

Output:
➢ Quick Sort
#include <iostream>

#include <vector>

template <typename T>


size_t partition(std::vector<T>& arr, size_t low, size_t high) {
T pivot = arr[high];
size_t i = low - 1;

for (size_t j = low; j < high; ++j) {


if (arr[j] <= pivot) {
++i;
std::swap(arr[i], arr[j]);
}
}

std::swap(arr[i + 1], arr[high]);


return i + 1;
}

template <typename T>


void quickSort(std::vector<T>& arr, size_t low, size_t high) {
if (low < high) {
size_t pivotIndex = partition(arr, low, high);

if (pivotIndex > 0) {
quickSort(arr, low, pivotIndex - 1);
}

quickSort(arr, pivotIndex + 1, high);


}
}
int main() {
// Example usage of quickSort template
std::vector<int> intArray = {64, 34, 25, 12, 22, 11, 90};
quickSort(intArray, 0, intArray.size() - 1);
std::cout << "Sorted array: ";
for (const auto& elem : intArray) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
Output:
9. Design stack and queue classes with necessary exception handling.

➢ Stack Class

#include <iostream>
#include <stdexcept>
#include <vector>

template <typename T>


class Stack {
private:
std::vector<T> elements;

public:
// Push an element onto the stack
void push(const T& value) {
elements.push_back(value);
}

// Pop the top element from the stack


void pop() {
if (empty()) {
throw std::underflow_error("Stack is empty. Cannot pop.");
}
elements.pop_back();
}

// Get the top element of the stack


T& top() {
if (empty()) {
throw std::underflow_error("Stack is empty. Cannot get top.");
}
return elements.back();
}

// Check if the stack is empty


bool empty() const {
return elements.empty();
}

// Get the size of the stack


size_t size() const {
return elements.size();
}
};

int main() {
// Example usage of Stack class
Stack<int> intStack;

try {
intStack.push(10);
intStack.push(20);
intStack.push(30);

std::cout << "Top element: " << intStack.top() << std::endl;


intStack.pop();
std::cout << "Top element after pop: " << intStack.top() << std::endl;

while (!intStack.empty()) {
intStack.pop();
}

// Attempting to pop from an empty stack (exception handling)


intStack.pop(); // This should throw an exception
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}

return 0;
}

Output:
➢ Queue Class

#include <iostream>
#include <stdexcept>
#include <queue>

template <typename T>


class Queue {
private:
std::queue<T> elements;

public:
// Enqueue an element into the queue
void enqueue(const T& value) {
elements.push(value);
}

// Dequeue an element from the front of the queue


void dequeue() {
if (empty()) {
throw std::underflow_error("Queue is empty. Cannot dequeue.");
}
elements.pop();
}

// Get the front element of the queue


T& front() {
if (empty()) {
throw std::underflow_error("Queue is empty. Cannot get front.");
}
return elements.front();
}

// Check if the queue is empty


bool empty() const {
return elements.empty();
}

// Get the size of the queue


size_t size() const {
return elements.size();
}
};

int main() {
// Example usage of Queue class
Queue<int> intQueue;
try {
intQueue.enqueue(10);
intQueue.enqueue(20);
intQueue.enqueue(30);

std::cout << "Front element: " << intQueue.front() << std::endl;


intQueue.dequeue();
std::cout << "Front element after dequeue: " << intQueue.front() << std::endl;

while (!intQueue.empty()) {
intQueue.dequeue();
}

// Attempting to dequeue from an empty queue (exception handling)


intQueue.dequeue(); // This should throw an exception
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}

return 0;
}

Output:
10. Write a C++ program that randomly generates complex numbers (use previously

designed complex class ) and write them two per line in a file along with an operator (+,-

,*, or /). The numbers are written to file in the format (a+ib). Write another program to

read one line at a time from this file.

#include <iostream>
#include <fstream>
#include <cstdlib> // for rand() and srand()
#include <ctime> // for time()
#include <sstream>
#include <vector>
#include <string>

// Complex class definition (assuming you have a complex class)


class Complex {
public:
double real;
double imag;

Complex(double r, double i) : real(r), imag(i) {}

// Overloaded stream insertion operator for formatting


friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
return os << "(" << c.real << "+i" << c.imag << ")";
}
};

// Function to generate a random complex number


Complex generateRandomComplex() {
double realPart = static_cast<double>(rand()) / RAND_MAX * 10.0; // Random real part between 0 and
10
double imagPart = static_cast<double>(rand()) / RAND_MAX * 10.0; // Random imag part between 0
and 10
return Complex(realPart, imagPart);
}

// Function to generate a random operator


char generateRandomOperator() {
char operators[] = {'+', '-', '*', '/'};
return operators[rand() % 4];
}

int main() {
// Seed the random number generator
srand(static_cast<unsigned>(time(nullptr)));
// Open a file for writing
std::ofstream outFile("complex_numbers.txt");

// Write two complex numbers and an operator per line to the file
for (int i = 0; i < 5; ++i) {
Complex c1 = generateRandomComplex();
Complex c2 = generateRandomComplex();
char op = generateRandomOperator();

outFile << c1 << " " << op << " " << c2 << std::endl;
}

// Close the file


outFile.close();

std::cout << "Complex numbers written to file." << std::endl;

return 0;
}

// Complex class definition (assuming you have a complex class)


class Complex {
public:
double real;
double imag;

Complex(double r, double i) : real(r), imag(i) {}

// Overloaded stream insertion operator for formatting


friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
return os << "(" << c.real << "+i" << c.imag << ")";
}
};

// Function to parse a line from the file


void parseLine(const std::string& line) {
std::istringstream iss(line);
Complex c1(0.0, 0.0), c2(0.0, 0.0);
char op;
// Parse the complex numbers and operator from the line
iss >> c1 >> op >> c2;
// Display the parsed values
std::cout << "Complex Number 1: " << c1 << std::endl;
std::cout << "Operator: " << op << std::endl;
std::cout << "Complex Number 2: " << c2 << std::endl;
std::cout << "---------------------------" << std::endl;
}
int main() {
// Open the file for reading
std::ifstream inFile("complex_numbers.txt");
if (!inFile) {
std::cerr << "Error opening file." << std::endl;
return 1;
}
// Read one line at a time and process it
std::string line;
while (std::getline(inFile, line)) {
parseLine(line);
}
// Close the file
inFile.close();
return 0;
}

Output:

You might also like