Wednesday, April 30, 2025

Introduction to Python

 Introduction to Python

The Python programming language is very similar to scripting languages of the early days, such as Perl, Unix shell scripts, awk, etc. But, it is much more than such languages to support larger programs. It is capabable of completing tasks with fewer lines of code (shorter programs) when compared to dominant programming languages such as C++/Java. Programs written in Python are very compact, less verbose and also readability at its best compared to C++/Java. 

Python Interpreter

Python command line with interpreter is a point of start for Python programming. If Python is not installed on your system, and if your computer has the Internet connection, then connect to Python.org to launch the interactive shell. You will find the command prompt as follows after launching the shell.

>>>
Start learning it as your calculator.
>>> 5+5
10
>>> 30-2*2
26
Do not hesitate to test and verify BODMAS rules. Now, try to use the command line to test small programs. Let us write code to add two numbers.
>>> a = 100
>>> b = 200
>>> c = a+b
>>> c
300
Now, write programs to multiply, divide, subtract and many more operations. But to carry out operations, you need operators. Do you know the operators in the Python programming language? If not, read the next topic and continue working with the Python command line interpreter.

Operators

The list of operators with their intended operations is listed below. All these are arithmetic operators with precedence levels from highest to lowest (recall BODMAS). Some of the operators are new; they are described after the table. Other operators of Python will be introduced in appropriate sections.
____________________________________________________________
Operation                                Operator            Example             Result
____________________________________________________________
Exponet                                        **                    2**4                    16
mod                                               %                    5%2                       1
Integer division                             //                     9//2                        4
(floor division)
Division                                        /                       9/2                       4.5
Multiplication                               *                      5*5                       25
Addition                                        +                     5+5                       10
Subtraction                                    -                      5-5                        0
____________________________________________________________

The modulus operator % computes the remainder of the division, whereas the integer division operator // computes the integer result by neglecting the fractional part. The exponent operator computes the power. Examples are provided in the table itself.  You know about the operators in Python now. But what about data? On what type of data do they operate on? 

Basic data types

There are three basic data types in Python: integer, floating point, string. Let us learn about each one. 

Integer (int): This is well known to all of us. Examples are,
        -98, 399, 0, 33, 933333, 333999, etc

Floating point numbers (float): They are real numbers with a fractional part included.
        -45.88, -0,45, 0.01, 56.90, 33433.0, 45555.3355644, etc

Strings (str): These are a sequence of characters enclosed within single or double quotes.
        'r', 'rr', "r", "Rrr",  "value", 'location', "133", "34.56", "abc123", etc.

The example "133" is a string of characters,  not an integer number 133. Operations on integer numbers and floating point numbers are as known to us from school. But string operations are new with strange mathematical operations. Let us learn them now.

Operations on strings

There are two prominent operations on strings. They are referred to as

Concatenation (+)
Replication (*)

Concatenation: Let us learn along with Shell.
>>> "one"+"two"
onetwo
>>> "three"+"4"
three4
>>> 'they'+ "are" + 'new"
theyarenew
>>> 'they'+' ' +are+' '+'new'
they are new

and so on. Concatenation is adding strings in sequence to create new strings. Both operands must be of string type for the string concatenation operation. The following in the Shell results in error

>>> "one"+100
Interpreter says an error in the instruction/statement.

Replication (*): Let us learn with Shell.
>>> 'one' * 3
oneoneone
>>> 'two ' * 3              # blank space included in the string "two "      
two two two

The replication operation on strings is a string repetition operation. You can't apply * operator on two different strings. The following statement is wrong and interpreter never executes the statement.

>>> 'one' * 'two'            # wrong usage of replication operator

String operations in Python is a big topic and will be discussed in detail later. Now, we know about data and operators to operate on data. As we know from math, we need variables to assign values to compute something. For example, to compute the area of a rectangle using the equation, 
                                    
                                                    A = length * width 
Three variables are necessary to do the computation (A, length, and width). Let us learn about variables in Python.

Variables

A value is assigned to the variable using the assignment operator (=) as shown below.

length = 2
width  = 4

Then the following statement computes the area of the rectangle.

A = length*width

But how to name the variables in Python PL? Any rules for naming variables? Yes. There are some rules for naming variables

Rules for naming variables: 
1. Use only alphabets (a-z, A-Z), numerals (0-9) and underscore (_) to name variables.
2. The variable name should not start with a numeral.
3. Variable names are case sensitive (a and A are distinct)

Some valid variable names are listed as follows.
length, LENGTH, Length, length_rect, length1, _length

Some invalid variable names are as follows.
3length        # should not start with a numeral
length$,      # special characters are not allowed

Variable naming conventions: There are some standard ways (not rules) to name variables. These conventions are necessary to improve the readability of programs and to follow some standard naming practices. As I observed, the following conventions are as follows in Python PL. 

1. Separate the words using an underscore if the name consists of more than one word
2. Use only lowercase to name the variables (uppercase is also used but for different identifiers)
3. Underscore is used as a beginning letter to name some special variables (internal use)
    
Ex: length_rect, length_rect_cm, 

Guidelines for naming variables:  The names of variables should be very close to their real participation in programs.  For example, to compute the area of the rectangle variables can be named in the following different ways.
1.  a, l, w
2. area, length, width
3. rect_area, rect_length, rect_width
4. rect_area_cm, rect_length_cm, rect_width_cm
and many more ways.

The 4th one is understandable (readable) to anyone using your program (or reading). This is about a rectangle, computing its area and the units are in cm.. 

Some introductory functions in Python

Functions are like block boxes. You need to understand its inputs and outputs. It does some job for you. You need to know what it does, not necessarily how it does the job.

Functions to convert data type from one to another (typecasters:int(), float(), str())

There are three basic data types in Python: int, float and str. Sometimes, it is necessary to convert from one type to another while writing code for some tasks. The type conversion from one to another with the help of code snippets is given below. 

int():  The int() function converts other data types (float/str) into an integer type as follows.

string_value = "187"
int_value = int(string_value)   # int_value = 187

The int function converts a string into an int only if it has numerals in the string form. If the string_value = "18A", the int() throws error.

float_value = 839.98
int_value = int(float_value)    # int_value = 839

The int function truncates the fractional part of the floating-point number.

float() function: This function converts data from other types (int, str) to the float type. The following code lines help you to understand the same.

int_value = 123
float_value = float(int_value)        # float_value = 123.0

string_value = "123.234"
float_value = float(string_value)        # float_value = 123.234

The string variable should have only numerals with a decimal point within quotes. Any other character in the string, the float() throws an error (for ex. string_value = "123.234f")

str() function: This function converts other types of data (for ex: int and float) to string data type. The following code snippet helps you to understand the same.

int_value = 123
float_value = 567.43
int_string = str(int_value)                # int_string = "123"
float_string = str(float_value)          # float_string = "567.43"

These typecasters are useful in many places during program development. One such situation is explained in the next section.

Functions for input and output of programs

Python has plenty of facilities for input and output of programs or applications. Here, only two functions are discussed: one for output (print()) and another for input (input()).

print() function: This function is useful to print messages on the terminal. Some examples with their output is as follows.

print("Hello world!")              # hello world!

length = 100
print("length = ", length)        # length = 100

print("Hello", end='_')             # separator argument end with underscore (_)
print("world!")                        # print on the terminal: Hello_world!

The print function automatically moves to the new line after its execution. It can be forced to stay on the current line using the separator parameter named end of the print function. It is illustrated in the code snippet shown above.

input() function: The input() function is useful to read a string from the keyboard. However, it is often used to read integer and float values also with the help of type casters. The following code snippets help you to learn reading a string, an integer and a float value from the keyboard.

# Reading a string:
string_value = input()            # waits for the user to enter the string
print(string_value)                 # verify the input function

# reading an integer
int_value = int(input())           # input() reads a string, type caster converts the string into int
x = int_value+10                     # verify that  int_value is integer now   
print(x)                                

# code to read a float value
string_value = input()                        # read a string from keyboard
float_value = float(string_value)        # convert string into float
print(float_value)                                # print its value 

# input function can also be used with user interface statement
int_value = int(input("enter and integer"))
float_value = float(input("enter a float value"))
string_value = input("enter a string")

Let us write some programs using the learnt operators, about variables names and functions.

Programs

Let us write a small program to read (input), compute and output (print). Read the student's details such as name, USN, and marks in a few subjects. Find the total marks and percentage. Print details of the student on the terminal.

# The comment line begins with #
# input: Read the details of the student
print("Enter the detials of the student ")
name = input("Enter the name of the student ")
USN = input("Enter the USN of the student ")
marks1 = int(input("Enter the marks of the first subject "))
marks2 = int(input("Enter the marks of the second subject "))
marks3 = int(input("Enter the marks of the third subject "))

# computation
total_marks = marks1+marks2+marks3
percentage = total_marks/3

#output: print results on the screen
print()
print("The output of the program is as follows")
print("The student Name: ", name)
print("USN: ", USN)
print("Total marks = ", total_marks)
print("Percentage = ", percentage)

Enter the program in a file and name the file appropriately (ex, StudentMarks.py). Run the file. If there are any Python syntactical errors, it stops at that point and displays a message (compiler errors). Otherwise, executes the program but stops if there are errors, which hampers the computation. 
For example, if the int keyword used for typecasting is typed as in, it results in a compiler error. If average_marks is typed as average_mar, no compiler error, but an interpreter error (while running the code). Make some errors in the source file and test. This program gives you the following result.

Enter the detials of the student 
Enter the name of the student = John
Enter the USN of the student =  MRT100
Enter the marks of the first subject = 65
Enter the marks of the second subject = 65
Enter the marks of the third subject = 89

The program output is as follows
The student Name:  John
USN:  MRT100
Total marks =  219
Percentage =  73.0








Tuesday, April 29, 2025

Control structures

FLOW CONTROL 

The flow control statements alter the execution flow of the program based on certain conditions while running the program. The control flow statements of Python can be classified as follows.
1. Selection statements
2. Looping statements
3. Jumping statements

SELECTION STATEMENTS

Selection statements are useful for the selection of one set of statements from many sets of statements for execution while the program is running. The following statements of Python are referred to as selection statements.
1. if statement
2. if-else statement
3. elif statement
4. match-case statement

The Python language has a boolean type with True and False as values. Also, it treats any non-zero value result  as True and a zero result as False in control flow conditional tests

if statement

The if statement syntax is as follows.

if condition:
    statement_1
    ...
    statement_n

If the condition evaluation results in True, the statements 1 to n are executed; otherwise, not. For example 

name = 'John'
if name=='John'
    print("Welcome John")
    
In this case, the test condition results in True, the if block print statement gets executed to print the welcome message.

if-else statement

The syntax of the if-else statement is as follows.

if condition:
    statement_1
    ...
    statement_n
else:
    statement_a
    ....
    statment_x

If the condition evaluation results in True, the if block statements from 1 to n are executed; otherwise, the else block statements from a to x are executed. For example,

a = 100
b = 200
if a==b:
    print(" a and b are equal")
else:
    print("a and b are not equal")

In this case, the condition evaluation (a==b) results in False, and hence the else block statement is executed to print the message "a and b are not equal".

elif (else if) statement

The elif statement is useful to select one group of statements from many groups of statements. The syntax of the elif statement is as follows.

if condition1:
    statements
elif condition2:
    statements
elif condition3:
    statements
else:
    statements

The conditions are tested sequentially from the beginning (condition1) to the end (condition3) until any one of the conditions results in True. The block of statements that corresponds to the condition which evaluated to True is executed, and the program control moves out of the elif statement. If none of the condition results in True, then the else block (default) statements are executed. The else block in the elif statement is optional. For example, 

a = 100
b = 200
if a==b:
    print("a and b are equal")
elif a>b:
    print("a is greater than b")
else:
    print("a is less than b")

The condition (a==b) is false, and the next condition (a>b) is also false, and hence the default else block of statement is executed to print  "a is less than b".  If

a = 300
b = 300

the first condition is True and hence executes the corresponding block of statements to print "a and b are equal". The elif statement terminates after that without testing any other condition.

match-case statement

The match-case statement is similar to the switch statement in C/C++/Java, but the control flow is different in Python. The syntax of this statement is as follows.

match value:
    case v1:
        print("value is matching with cas v1")
    case v2:
        print("value is matching with case v2")
    case v3:
        print("value is matching with case v3")
    case _:
        print("value is not matching any case value")

The keywords used in this control structure are match and case. The control flow is as follows. The value is compared with the first case value (v1); if it matches, it executes the statements belonging to the case and then terminates the match-case. Otherwise, comparison continues with the next case (v2) and so on.  If the value does not match any of the case values, it executes the  (default) case that is represented by an underscore (wild card). The default case (_) is optional and always appears at the end of the match-case statement.

Let us consider an example.
value = True
match value:
    case 10:
        print("It is an integer")
    case True:
        print("It is a boolean")
    case "string":
        print("It is a string")
    case _:
        print("It is default", value)

The value does not match the first case, but with the second case. The statement belongs to the case

print("It is a boolean")

is executed and then terminates the statement.

LOOP STATEMENTS

Python has two statements for iterative control of program execution. They are for loop and while loop.  The for loop is used when the number of iterations is fixed at the time of writing code, whereas the while loop is preferred if the number of iterations is not known.

for loop

The syntax of the for loop is as follows.

for var in [list_of_values]:
    statements

The for loop statements are executed for each value in the list_of_values.  A value from the list is assigned sequentially to the variable var for each iteration.

Let us learn the for loop with some examples.

for i in [1,3,5]:
    print(i, end='    ')

This for loop statement prints: 1    3    5

The loop iterates three times, with a value of i equal to 1, 3, and 5 during the first, second, and third iterations respectively. The loop prints the value of i in each iteration.

for i in ['one', 'John', 'location']:
    print(i)
   
The list has strings as values for i. The values for i are the strings 'one', 'John', and 'location' during the first, second, and third iterations, respectively. The output of the loop is as follows.

one
John
location

The list need not have the values of the same type as shown below. It has integer, string and boolean type values.
 
for i in [1,'Ram', True]:
    print(i, end=',')

The loop prints the following.
1,3,5

for loop with range function: The range function in Python has three arguments: start, stop and step. The default values for start and step arguments are 0 and 1 respectively. It returns the list of values depending on these three arguments. The function signature is as follows.

range (start=0, stop, step=1)

For example,

for i in range(5):
    print(i, end=',')

The arguments of the above range function are start=0, step=1 and stop=5. It returns the list [0 1 2 3 4]. The range is only up to the stop value, but not including the stop value. Hence, the for loop iterates five times with i values of 0,1,2,3,4. Let us consider another example to understand the range function.

for i in range(2,6):
    print(i, end=',')

The arguments of the above range function are start=2, stop=6 and step=1(default). The function returns the list [2,3,4,5]. The loop iterates 4 times with values 2,3,4 and 5 for i. An example with all three arguments is as follows.

for i in range (3, 15, 2):
    print(i, end=',')

The range function with the arguments start=3, stop=15 and step = 2 returns the list [3,5,7,9,11,13]. The for loop iterates over the values in the list and prints the following.

3,5,7,9,11,13

for loop with else: The for loop has an optional else part, which will be executed always after completing all iterations. The syntax is as follows.

for var in list_of_values:
    statements
else:
    statements

For example,

for i in range(5):
    print(i, end=',')
else:
    print(i)

This for loop prints the following: 0,1,2,3,4,4    

while loop

The syntax of the while loop is as follows.

while loop_condition:
    statements

The while loop iterates as long as the loop_condition is True. It gets terminated once the loop condition becomes False. The loop condition is evaluated for every iteration before executing its block of statements. Any non-zero numeric value or a boolean True value is True for the while loop. The numeric zero or a boolean False is False. Some examples with results are as follows.

while True:
    print(True, end='.')

This while loop iterates infinite number of times and during each iteration, it prints True on the screen as follows.
True.True.True........
Use Ctrl-C to terminate the code.

while False:
    print(False)
This while loop never executes the print statement (never enters the while block).

while -0.01:                    # any non-zero numeric value is True
    print("always true")

while 0.0:                      # numeric zero is False; never enters the while block
    print("always false)

There is no do-while loop in Python. The do-while loop guarantees at least one iteration. The same can be achieved using a while loop using loop_condition and its block statements. One instance to achieve the same is as follows.

x = True
while  x:            # guarentees first iteration
    block statments can change x to stop the iterations

while loop with else:  The else part of the while loop is optional and when used always executed after completing all iterations.  For example,

x = 5
while x<10: 
    x = x+1
else:
    print("end of while loop")

The while loop iterates and at the end prints the message: end of while loop.

Jumping (branching) statements

 In Python, there are two branching or jumping statements for loops: the break statement and the continue statement. The syntax is as follows.

break          # just use the  keyword
continue     # just use the keyword


break statement: The break statement breaks the loop (the innermost loop if nested) and exits the for/while loop as soon as it is executed. The following examples illustrate the break statement.

for i in range(5):
    print(i, end=',')
    if i==3:
        break
This prints 0,1,2,3 on the screen; the if statement condition is True when the i value is 3, which executes the break statement and terminates the for loop.

for i in ['one', 'two', 'three']:
    print(i)
    for j in range(1,3)
        print(i)
        if i==1:
            break

The output is as follows.
one
1
two
1
three
1
The outer for loop iterates three times to print the strings 'one', 'two', and 'three'. The inner for loop has to iterate two times for every iteration of the outer for loop, but it iterates only once because of the break statement. Observe that the break statement breaks only the inner for loop.

continue statement: The continue statement in the for/while loop suspends the remaining statements of the current iteration when executed inside the loop. It starts the next iteration after checking the loop condition.  Let us learn the continue statement with a while loop. The following while loop is written to print from 1 to 10, but can print only odd numbers because of the continue statement.

i=0
while i<11:
    i = i+1
    if i%2 == 0:
        continue
    print(i, end=',')

The output of the program is: 1,3,5,7,9

The if statement condition is True if the value of i is even, which in turn executes the continue statement. The continue statement skips the remaining statements of the current iteration. Then it immediately moves the program control to the top of the while loop to check the while condition.

Nesting of control flow statements

 Any control flow statement can be nested with any other control flow statement. The following code snippets show some examples.

if condition:                    # the for loop is nested under the if statement 
    for i in range(5):    
        print(i)

for i in range(5):             # for loop block has a while statement nested and while has an if block nested
    while i<3:
        if condition:
            break
        print(i)


Programs

Let us write a program to generate the Fibonacci numbers sequence of length n. A Fibonacci number is the sum of the previous two numbers in the series. The first two Fibonacci numbers are 0 and 1.

f1=0                                                    # first fib number
f2=1                                                    # second fib number
n = input("enter the length of the Fib sequence = ")    # ask the user to enter the length of the seq
n = int(n)    # read n type is string, convert it to int
next = 0
print(f1,f2,end=',')                             # print first two numbers of fib sequence
for i in range(n-2):                            # first two numbers are defined; generate remaining 
    next = f1 + f2   # next fib number is the sum of the previous two numbers
    print(next, end=',')          # new fib number added to the sequence
    f1=f2 # redefine the previous two numbers to generate the next number
    f2=next  

The program is self-explanatory for the reader with knowledge of the for loop and library functions (range, input, int). The comments on the line help you to understand the program.

Let us write another program to illustrate control flow statements.  This time to use selection statements (if-else statement). The person is senior if he crosses 60 years. Let us find whether a person is senior or not based on his birth year.

# program to find whether a person is senior or not

current_year = int(input("Enter the current year "))        # program input
name = input("Enter the name of the person ")
birth_year = int(input("Enter the birth year of the person "))

age = current_year - birth_year            # program logic
if age>=60:
    print("The person is senior ", age)            # program output tied with logic
else:
    print("The person is not senior ", age)        # program output tied with logic

The output of the program is as follows.
Enter the current year 2025
Enter the name of the person John
Enter the birth year of the person 1985
The person is not senior  40

Run again to feed different input and observe the input
Enter the current year 2025
Enter the name of the person Peter
Enter the birth year of the person 1965
The person is senior  60


   



 







 








    


    









Monday, April 28, 2025

Functions

 Functions

Functions are developed as a part of programs to handle the following scenarios.
1. Modular programming: It is difficult to write the entire application/large programs in one file and by one programmer. Hence, the application's big/huge task is divided into smaller tasks, which are implemented as functions. They can all be written in separate files by different programmers and finally made into one program/application.
2. Repetitive use of a piece of code: The program/application may use a small piece of code repetitively. Such code segments are written separately as functions and called when necessary for the program. This improves the readability of the program and also saves memory. 
3. Library functions: Functions are developed by the language developer and provided to users as part of the library. These functions are used by program writers as and when needed for program development. For example, all math functions (sqrt, sin, cos) are available for users.
4. Sharing domain expertise: Domain experts produce library functions in their respective domains and make them available to the users as third-party packages.

Python program/script with functions

Function with no arguments: The function fn1 in the following program has no arguments. The program is useful to understand function definition and function call.

def fn1():                        # function definition starts here
    print("This is a function with no arguments")            # function definition ends here

fn1()                                # function call

Read the program with comments. The function definition starts with the keyword def, followed the name of the function (fn1) and its parameters within the brackets. In this function, there are no parameters. The function is called by the following line of code in the program.

fn1()

The function call is just the name of the function and the arguments within the brackets. In this program, there are no arguments to pass to the function.

Function with arguments: The function fn2() in the following program has two parameters, a and b. The function simply prints the values of a and b.

def fn2(a,b):
    print(a,b)

v1 = 100
v2 = 200
fn2(v1,v2)
fn2(300,400)

The fn2 is called with arguments v1 and v2 by the line of code

fn2(v1,v2)

The values of v1 and v2 are assigned to a and b, respectively, and they are printed when executed. The function fn2 is called again, but with the values directly as arguments (fn2(300,400)).

Function with default arguments: The function fn3() in the following program has default values for some of its parameters. When the function is called without these arguments, then the default values are used as arguments for the function.

def fn3(a, b=100, c=200):
    print(a,b,c)

v1 = 10
v2 = 20
v3 = 30
fn3(v1,v2,v3)                # function is called with all three arguments
fn3(v1)                     # function is called with only one argument; 2nd and 3rd uses  default values

The function call with only one argument v1 is assigned to the first parameter a, and the values of b and c are the default values 100 and 200, respectively. The function can also be called with two arguments as follows.

fn3(v1, v2)

The function call assigns v1 to a, v2 to b, but the value of c is the default value (200)

Different ways of passing arguments: The arguments to the functions may be passed in the following ways. For example, consider the range function.

range(start=0, stop, step=1)

The function has three parameters with names start, stop, and step. It may be called in the following ways

range(1,5,2)                     # passing arguments (coder knows the parameters and their order) range(start=1,stop=5,step=2)      # passing arguments along with names
range(stop=5, step=2, start=1)    # passing arguments with names but without following the order  

All three cases are syntactically correct. It depends on the programmer's awareness of the function parameters. In the third case, the coder is aware of the names of the parameters of the function but lacks the knowledge of their order. Also,

range(5)        # start and step are assigned with default values, the argument is assigned to stop
range(1,5)    # only the parameter step is assigned with the default value

But while using default values, the order of parameters must be followed.
 
A program with a function: Now, let us write a program to compute the factorial of a given number. The task of computing the factorial is written as a function.

def factorial(n):                    # function definition starts here
    answer = 1
    if n==0 or n==1:               # factorial of 0 and 1 is 1
        return answer
    for i in range(2, n+1):        # for example; 5! = 2*3*4*5; range function returns [2,3,4,5]
        answer = i*answer
    return  answer                # functions definition ends here

number = input("enter the number to find its factorial ")
number = int(number)
result = factorial(number)            # call the function factorial  with number as an argument
print("The factorial of the number is ", result)
   
In this Python script, 
name of the function: factorial,
parameter: n,
argument passed: number
function return value: answer

The program is self-explanatory for the reader with knowledge of functions, library functions (input() and int()).  The input() is used to read the number to find the factorial. But, the read number is in str type and hence typecasted to convert it into int type.

Scope of variables

The scope of variables in Python scripts may be local or global. Variables declared inside any of the functions are local to that function. They are not available anywhere outside that function. Whereas the variables declared outside the functions are global and available everywhere in the script for use, including functions. But, functions can't modify the global variables without declaring them as global inside the function using the keyword global. The following code script illustrates the scope of variables.

def fn1():   
    v1 = 100            # v1 is local to the function fn1()
    print(v, v1)

def fn2():                # second function starts here
    global v
    v2 = 200            # v2 is local to the function fn2()
    v = v2                # valid; function is entitled to modify v
    print(v, v2)    

v = 300                # v is global variable
print(v)
fn1()
fn2()                    # script ends here

The output of the script is 
300
300 100
200 200

The variables v1 and v2 are local to the functions fn1() and fn2(), respectively. Their scope and accessibility are restricted to the respective functions. On the other hand, the variable v is a global variable and is accessible by both functions fn1() and fn2(). But,  the fn1() can use the value of the global variable v without modifying it. To modify the value of any global variable inside any of the functions, it must be declared global inside that function as shown in fn2(). 

global v       

This statement inside the function fn2() makes the variable v available for modification.

  Exception handling / Error handling

Programs/applications are expected to run 24/7 if necessary. They should not get terminated by the accidental errors that occur while running and/or errors introduced by the users of the application. Python handles the errors that occur while running the program using the try-except statement. The syntax of the try-except statement is as follows.

try:
    statements that may result in an error, which terminate the program
except NameOfTheError:
    error handling code

The coder must identify the statements that may produce errors and place them in the try block.  It is also the coder's responsibility to identify the kind of error the try block code produces. If the code inside the try does not produce any error, then the except block is not touched. The program control will simply be transferred to the next statement in the program. If the try block code produces the error, then the except block will be executed. The program control will then be transferred to the next statement of the program to continue execution. After executing the except block code, the program control never goes back to the try block code.

List of some errors: 
ZeroDivisionError    -> happens when dividing a number by zero
ValueError                -> happens when passing the wrong value to a function
IndexError                -> happens when out of range index is used

The following Python script illustrates ZeroDivisionError.

numerator = 100
denominator = 0

try:
    value = numerator/denominator
except ZeroDivisionError:
    print("Divide by zero error occurred")

print("program runs 24/7")

The coder must know that the try block code produces ZeroDivisionError (not IndexError) and it must accompany the except keyword. If the except keyword accompanies IndexError, the error handling fails.

    Programs

Let us write a program to find out a binomial coefficient given n and r. 

binomial co-efficient =  n! / r! (n-r)!

To find the binomial coefficient, the program has to compute factorials three times. Hence, it is good to write a function to compute the factorial and call it three times with appropriate arguments.

# Program to compute binomial coefficients
# start with a function to compute factorial
def factorial(n):                    # function definition starts here
    answer = 1
    if n==0 or n==1:               # factorial of 0 and 1 is 1
        return answer
    for i in range(2, n+1):        # for example; 5! = 2*3*4*5; range function returns [2,3,4,5]
        answer = i*answer
    return  answer                # functions definition ends here

n = int(input("Enter the value of n = "))
r = int(input("Enter the value of r = "))

n_fact = factorial(n)
r_fact = factorial(r)
n_r_fact = factorial(n-r)

binomial_coefficient = n_fact / (r_fact*n_r_fact)
print("binomieal_coefficient = ", binomial_coefficient)

Enter the value of n = 6
Enter the value of r = 4
binomieal_coefficient =  15.

Users of the program should know that the value n must be greater than r. Othewise, if n and r are 4 and 6, the results are wrong. 

You can update this program to reject any such attempts (n<r) by the user. Also, make your factorial function to reject any negative input (n-r is negative n<r)


Sunday, April 27, 2025

Lists

 


        Lists Data Type

        List is a compound data type used to store data items as a ordered collection. The data items may be of single data type (int/float/str/etc.) or multiple data types which includes int, float, str, another list, etc. It is a widely used data structure for arranging and  accessing data items in many different ways (eg. stack, queue, heirarchical structures). Unlike strings, they can be modified after the creation and hence referred as mutable data type. Most of the time they are used as a data structure with data items of a single data type. Some examples of lists are as follows 

list1 = ['apple', 'carrot', 'mint']     # list of strings

list2 = [45,56,43]                                               # list of integers

lt3 =       ['apple', 45, True, 56.56]                # list with different data type elements

lt4 =       [list1, list3]                                          # list of lists

lt5 =       [[1,2,3],[4,5,6,7,8]]                          # list of unnamed lists

operations on lists  

1.       Indexing (accessing an element of the list)

 Any element of the list can be accessed using indexing. The first item of the list is at index zero, the second element at index 1, and so on (referred as forward indexing). There is also another way of indexing: backward indexing. The last item is referred using the index -1, last but one with -2 and so on.

        Forward Indexing:  The following code snippet shows the creation of the list named as PLs_list and then accessing its elements using indexing to print on the terminal. 

PLs_list = ['C', 'C++', 'Java', 'Python']

print(PLs_list[0])                  # Output: C     ; accessed first element

print(PLs_list[3])                  # Output: Python         ; accessed last element

The first item ('C') is accessed using index 0, and fourth item  ('Python') is accessed using index 3.

 Backward Indexing: Python also supports backward (negative) indexing, which allows you to access elements from the end of the list. The last element is at index -1, the second-to-last at -2, and so forth.

PLs_list = ['C', 'C++', 'Java', 'Python']

print(PLs_list[-1])              # Output: Python       ; accessed the last element

print(PLs_list[-2])              # Output: Java                  ; accessed second element from right

print(PLs_list[-4])              # Output: C   ; accessed the first element

It is a very convenient way to access end of the list elements without knowing its length.

2. Slices (accessing more than one element of a list)

       Slicing is used to extract a sublist (a portion of the list) from the list. The syntax of slice is list_name[start:stop:step] where start indicates from which index to start extraction, stop indicates where to stop (stop-1) and step is used to indicate the increment between elements. The default value of the step is 1.    

a_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(a_list[2:5])    # Output: [2, 3, 4] (elements from index 2 up to but not including 5)

print(a_list[:4])     # Output: [0, 1, 2, 3] (elements from the beginning up to but not including 4)

print(a_list[6:])     # Output: [6, 7, 8, 9] (elements from index 6 to the end)

print(a_list[1:8:2])  # Output: [1, 3, 5, 7] (elements from index 1 to 8 with a step of 2)

print(a_list[:])      # Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (a copy of the entire list)

print(a_list[::-1])   # Output: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] (reversing the list)

print(a_list[-1:-5:-1]) # output: [9, 8, 7, 6] (excldes -5)

Slicing is a powerful way to work with subsets of lists.

 3. Mutability

        This property allows you to change the elements of the list after creation. Use indexing to modify a single item or slice to modify a sublist of the list.

PLs_list = ['C', 'C++', 'Java', 'Python']

PLs_list[0] = 'Go'                                    #changing the element at index 0

PLs_list[2] ='Oracle Java'                      # changing the element at index 2

print(PLs_list)                                    # ouput: ['Go','C++', "Oracle Java', 'Pyhton']


numbers = [10, 20, 30, 40, 50, 60]

numbers[1:3] = [200, 300]            # Changing a slice of elements; index 1 and 2

print(numbers)                                 # Output: [10, 200, 300, 40, 50, 60]

 This mutability allows for dynamic modification of list contents as illustrated using the code snippet. Read the code with comments to understand the output.

 4. Concatenation

        The operator + is used to concatenate two lists to create a unified list. Any number of lists can be concatenated to create a combined new list.

 PLs_list1 = ['C', 'C++', 'Java']

PLs_list2 = ['R', 'Python', "Julia']

PLs_combined_list = PLs_list1 + PLs_list2

print(PLs_combined_list)             # Output: ['C', 'C++', 'Java','R', 'Python', "Julia']

 Concatenation creates a new list containing all the elements from the original lists in the order they appear as illustrated in the code listing above. 

        5. Replication

        The * operator is used as replication operator. It repeats the original list specified number of times to create a new list.

PLs_list = ['Python']

PLs_repeated_list = PLs_list * 3

print(PLs_repeated_list)                                              # Output: ['Python', 'Python', 'Python']

numbers = [0.98, 1.56]

rpt_numbers = numbers * 2

print(rpt_numbers)                                                        # Output: [0.98,1.56,0.98,1.56]

Replication is useful for quickly creating lists with repeated elements or patterns.

 6. `del` Statement for Lists

        The del statement can be used to delete a particular item of the list by specifing its index. It is also possible to delete the entire list or sublist. The following code snippet illustrates the same.

PLs_list = ['C', 'C++', 'Java','R', 'Python', "Julia']

del PLs_list[1]       # Deleting the element at index 1

print(PLs_list)       # Output: ['C', 'Java','R', 'Python', "Julia']

del PLs_list[1:3]    # Deleting a slice of elements

print(PLs_list)       # Output: ['C', 'Python', 'Julia']

num_list = [10, 20, 30]

del num_list    # Deleting the entire list

print(num_list) # Trying to access num_list here would result in a NameError

 The      del       statement permanently removes elements or the list itself from memory.

 7. Growing Lists

        Lists can be grown dynamically after their creation. Common methods for adding elements include      append()       (adds an element to the end),      insert()       (inserts an element at a specific index), and      extend()       (adds multiple elements from another iterable to the end).

my_list = ['apple', 'banana']

my_list.append('orange')

print(my_list)       # Output: ['apple', 'banana', 'orange']

my_list.insert(1, 'grape')

print(my_list)       # Output: ['apple', 'grape', 'banana', 'orange']

another_list = ['kiwi', 'mango']

my_list.extend(another_list)

print(my_list)       # Output: ['apple', 'grape', 'banana', 'orange', 'kiwi', 'mango']

These methods provide flexible ways to add elements to existing lists.

  8. `for` Loop with Lists

        The `for` loop is commonly used to iterate over the elements of a list, allowing you to perform an action on each item.

PLs_list = ['Java','Python']

for PL in PLs_list:

                print(PL, " is a powerful PL")

This loop will iterate through each string in the `fruits` list and print a message.

   9. `in` and `not in` Operators with Lists

        The in operator is used to verify that a specific item exists in the list. Whereas not in operator is used to verify a particular item is not present in the list. The result is True or False depending on the presence or absence of the specified element in the list. The following code snippet illustrates the same.  

PLs_list = ['C', 'C++', 'Java', 'Python',]

print('C++' in PLs_list)    # Output: True

print('R' in PLs_list)     # Output: False

print('Matlab' not in PLs_list) # Output: True

print('Python' not in PLs_list)  # Output: False

The in and not in operators are useful for checking membership in a list.

10. Multiple Assignment

        Python supports multiple variables assignment using the same assignment operator =. The condition is left side variables and right values for assignment must be in same number. The same is illustrated in the following code example.

PLs_list = ['C', 'Java', 'Python']

embedded, banking, data_science = PLs_list

print(embedded, banking, data_science) # Output: C Java Python

Multiple assignment provides a concise way to unpack elements from a list into individual variables.

  

Common List Functions

 List is an object, and it comes with several built-in functions (or methods) that allow you to manipulate and interact with the list's contents. Some of the commonly used functions of list include index(), insert(), append(), sort, and many more. Some of them are illustrated in the following lines.

         1. index()

        The      index()       function is used to find the index of the first occurrence of a specified value in the list.

         The function can be directed to search from the specified starting point (index).  

PLs_list = ['C', 'C++', 'Java','Python', 'C++']

index_C++ = PLs_list.index('C++')                            # index of C++

print(index_C++)                                             # output: 1

 index_C++ = PLs_list.index('C++',2)                         # search index for 'C++' starting from index 2

print(index_C++)                                             # output: 4

 # erroneous code

index = PLs_list.index('Matlab')                 # search index for the value 'Matlab'

print(index)                                        # output: ValueError

          

 If the specified value is not found in the list, a      ValueError       will be raised. 

    2. append()

        The append() function adds a single element to the end of the list. It is helpful to grow the list dynamically       

PLs_list = ['C', 'C++', 'Java','Python']

PLs_list.append('R')                        # 'R' is added to end of the list

print(PLs_list)                                    # output: ['C', 'C++', 'Java','Python','R']                  

    The append() method modifies the original list directly.

  3. insert()

        The insert() function inserts an element at a specified index in the list. It takes two arguments: the index where the element should be inserted and the element itself. This function is also helpful to grow the list dynamically

numbers = [1, 3, 4]

numbers.insert('R', 2)

print(numbers) # Output: Numbers after insert: [1, 2, 'R', 4]

When you insert an element, the existing elements from that index onwards are shifted to the right.

4. remove()

        The      remove()       function removes the first occurrence of a specified value from the list.

 numbers = [100, 200, 300, 200, 400]

numbers.remove(200)

print(numbers) # Output: Numbers after remove: [100, 300, 200, 400]

print(numbers.remove(600))                     # results in ValueError (The element 600 not present in the list)

 Similar to      index()      , if the specified value is not found, a      ValueError       will be raised.

 5. sort()

 The      sort()       function sorts the elements of the list in place (modifies the original list). By default, it sorts in ascending order.

numbers = [3, 1, 4, 1, 5, 9, 2, 6]

numbers.sort()

print(numbers)    # Output: [1, 1, 2, 3, 4, 5, 6, 9]

 PLs_list = ['C', 'R', 'Java', 'C++', 'Python']

PLs_list.sort()      # sorted according to ASCII values of A-Z

print(PLs_list)                                    # output: ['C', 'C++', 'Java','Python','R']

PLs_list = ['C', 'R', 'java', 'C++', 'python']                 # lowercase j and p

PLs_list.sort()    # lower case alphabets value higher compared to upper case

print(PLs_list)                                    # output: ['C', 'C++', 'R','java','python']

  The sort() method modifies the list directly and returns None.

  6. sort() in Reverse Order

        To sort a list in descending order, you can pass the reverse=True argument to the sort() function. The default value of reverse argument is False.

numbers = [30, 10, 40, 10, 50, 90, 20, 60]

numbers.sort(reverse=True)

print(numbers)                                 # output: [90, 60, 50, 40, 30, 20, 10, 10]

PLs_list = ['C', 'R', 'Java', 'C++', 'Python']

PLs_list.sort(reverse=True)           # sorted according to ASCII values of A-Z

print(PLs_list)                                    # output: ['R', 'Python', 'Java', 'C++, 'C']

PLs_list = ['C', 'R', 'java', 'C++', 'python']                 # lowercase j and p

PLs_list.sort()    # lower case alphabets value higher compared to upper case

print(PLs_list)                                    # output: ['python', 'java', 'R', 'C++', 'C']

 This is a simple way to get a descending sorted list. Note that the default sort for strings is case-sensitive. If you need a case-insensitive sort, you can use the key argument with str.lower: my_list.sort(key=str.lower)

           Functions with list as an argument

         Some of the Python functions that take lists as arguments, enabling powerful operations and manipulations: 

         1. len()

         The      len()       function returns the number of items in a list. 

 a_list = [10, 20, 30, 40, 50,60]

 list_length = len(a_list)

 print(list_length)  # Output: 6

2. max()       

          The      max()       function returns the largest item in a list. 

numbers = [5, 10, 3, 18, 1,90]

max_value = max(numbers)

print("The maximum value is: ", max_value)  # Output: The maximum value is: 90

  3. min()     

          The      min()       function returns the smallest item in a list. 

numbers = [10, 20, 5, 15, 30, -60]

min_value = min(numbers)

print("The minimum value is: ", min_value)  # Output: The minimum value is: -60

 4. sum()     

          The      sum()       function returns the sum of all elements in a list. 

 numbers = [10, 20, 30, 40, -20]

 total = sum(numbers)

 print("The sum of the numbers is: ", total)  # Output: The sum of the numbers is: 80

  5. sorted()     

  The      sorted()       function returns a new sorted list from the items in iterable. 

numbers = [6, 9, 3, 1, -45]

sorted_numbers = sorted(numbers)

print("Sorted list: ", sorted_numbers)  # Output: Sorted list: [-45, 1, 3, 6, 9]

 6. list()     

          The      list()       function converts an iterable (like a tuple) to a list. 

a_tuple = ('C', 'C++', 'Java')

a_list = list(a_tuple)

print("Converted list: ", a_list)  # Output: Converted list: ['C', 'C++', 'Java']

 7. any()       

         The      any()       function returns      True       if any element of the iterable is true. 

list1 = [True, False, False]

result = any(list1)

print("Any is True: " , result)  # Output: Any is True: True

 8. all()       

          The      all()       function returns      True       if all elements of the iterable are true. 

list1 = [True, True, True]

result = all(list1)

print("All are True: ", result)  # Output: All are True: True

 9. enumerate()     

          The      enumerate()       function adds a counter to an iterable and returns it as an enumerate object. 

a_list = ['C', 'C++', 'Java']

for index, PL in enumerate(a_list):

     print("Index ", index, PL}")

                     # Output:

                     # Index 0 'C'

                     # Index 1 'C++'

                     # Index 2 'Java'

   10. zip()       

           The      zip()       function aggregates elements from each of the iterables. 

names = ['John', 'Peter', 'Steve']

ages = [45, 54, 33]

for name, age in zip(names, ages):

    print(f"{name} is {age} years old.")

                     # Output:

                     # John is 45 years old.

                     # Peter is 54 years old.

                     # Steve is 33 years old.

    11. reversed()     

          The      reversed()       function returns a reverse iterator. 

a_list = [1, 2, 3, 4, 5,6]

reversed_list = list(reversed(a_list))

print("Reversed list: ", reversed_list)  # Output: Reversed list: [6, 5, 4, 3, 2, 1]

 12. copy.copy()     

copy.copy()       creates a shallow copy of a list. 

import copy

original_list = [10, 20, [30, 40]]

copied_list = copy.copy(original_list)

copied_list[2][0] = 900

print("Original list: ", original_list)  # Output: Original list: [10, 20, [900, 40]]

print(f"Copied list: {copied_list}")      # Output: Copied list: [10, 20, [900, 40]]

13. copy.deepcopy()     

copy.deepcopy()       creates a deep copy of a list. Use deepcopy() to copy nested lists. 

import copy

original_list = [10, 20, [30, 40]]

copied_list = copy.copy(original_list)

copied_list[2][0] = 900

print("Original list: ", original_list)  # Output: Original list: [10, 20, [30, 40]]

print("Copied list: ", copied_list)      # Output: Copied list: [10, 20, [900, 40]]

Passing Lists as Arguments to Functions (Pass by reference)     

         In Python, you can pass lists as arguments to functions, just like any other data type. When you pass a list to a function, the function receives a reference to the original list. This means that if the function modifies the list, those changes will be reflected in the original list outside the function. 

 Modifying a List Inside a Function     

 def add_element(data_list, element):

     """Adds an element to the end of a given list."""

     data_list.append(element)

     print(f"List inside function: {data_list}")

     my_numbers = [10, 20, 30]

     add_element(my_numbers, 40)

     print(f"List outside function: {my_numbers}")

                    # Output:

                    # List inside function: [10, 20, 30, 40]

                    # List outside function: [10, 20, 30, 40]

         As you can see, the      append()       operation inside the      add_element       function directly modifies the      my_numbers       list that was passed as an argument. 

 Creating a New List Inside a Function (local list) 

  def square_num(number_list):

      """Creates a new list containing the squares of numbers in the input list."""

      squared_list = []

      for number in number_list:

          squared_list.append(number ** 2)

          return squared_list

 original_numbers = [10, 20, 30, 40]

 squares = square_num(original_numbers)

 print(f"Original list: {original_numbers}")

 print(f"Squared list: {squares}")

                    # Output:

                    # Original list: [10, 20, 30, 40]

                    # Squared list: [100, 400, 900, 1600]

    In this case, the function creates and returns a new list without altering the original list passed as an argument. 

              Tuples     

      Tuples: The Essentials     

         Tuples are another fundamental sequence data type in Python, similar to lists. But, unlike strings they are immutable. Here are the key characteristics: 

Creation: Tuples are created by placing comma-separated values inside parentheses      ()      . However, even a single-item tuple requires a trailing comma. 

a_tuple = (1, 2, 3)

single_item_tuple = (5,)

empty_tuple = ()

print(a_tuple)

print(single_item_tuple)

print(empty_tuple) 

              Immutability: The most significant difference from lists is that tuples are immutable. Once a tuple is created, you cannot change its elements (add, remove, or modify). 

a_tuple = (1, 2, 3)

# The following would raise a TypeError:

                    # a_tuple[0] = 10

                    # del a_tuple[1]

                    # a_tuple.append(4)

   Indexing and Slicing:     

         Like lists, tuples support zero-based indexing and slicing to access elements or sub-sequences. 

  a_tuple = ('C', 'C++', 'Java')

  print(a_tuple[0])   # Output: C

  print(a_tuple[1:3]) # Output: ('C','C++')

  print(a_tuple[-1])  # Output: 'Java;

Concatenation and Replication:  Tuples support concatenation using the      +       operator and replication using the      *       operator, similar to lists. These operations create new tuples. 

tuple1 = (10, 20)

tuple2 = ('aa', 'bb')

combined_tuple = tuple1 + tuple2

print(combined_tuple)  # Output: (10, 20, 'aa', 'bb')

repeated_tuple = tuple1 * 3

print(repeated_tuple)  # Output: (10, 20, 10, 20, 10, 20)

 in and not in Operators : You can use the      in       and      not in       operators to check for the presence of an element in a tuple, just like with lists.

a_tuple = ('C', 'C++', 'Java')

print('C' in a_tuple)    # Output: True

print('Python' not in a_tuple) # Output: True

 Comparing Tuples and Lists :   

          the key differences between tuples and lists: 

       

          Feature                                     List                                               Tuple             

Mutability        Mutable (can be changed after creation)                Immutable 

Syntax              Created using square brackets      []                      Created using parentheses      ()      

Use Cases        Collections of items that might need to be modified    representing fixed records) Methods           append(),insert(), remove(), sort()                        count(), index() 

         Choosing between lists and tuples depends on whether you need a mutable or an immutable sequence of items.