Showing posts with label Recursion. Show all posts
Showing posts with label Recursion. Show all posts

Saturday, September 21, 2024

The Most Powerful Concepts in Programming Languages: Functional Programming and Lambda Expressions



In this blog, I would like to introduce the concepts that I believe are the most powerful in programming languages, and mastering them will give you solid programming thinking. I will explain them one by one with examples in Java and Python. After introducing the concepts of object orientation, recursion, and concurrency in the first three articles, I will elucidate the concept of functional programming and lambda expressions in this article.



1. Object-oriented
2. Recursion
3. Concurrency

4. Functional Programming and Lambda Expressions

5. Data Structure
6. Software Design Patterns
7. Annotations/Decorators
8. Machine Learning



1. Functional Programming and Lambda Expressions

Functional programming is a paradigm that promotes the use of pure functions and immutable data. In functional programming, you can compose multiple functions and apply them to arguments to perform calculations. A functional program results from the functions’ composition. Pure functions provide the same output for the same inputs. They have no side effects like the functions in the imperative paradigm. Pure functions preserve the definition and logic of a function as defined in mathematics. A functional program is often more concise, intuitive, and more understandable than an imperative due to the sequence of functions.


However, the advantage of a pure function is also its limitation. Indeed, functional programming does not allow data to be saved like an assignment does in imperative languages. Using only a functional language to develop large, traditional applications like desktop or website applications is challenging.


The good news is that functional programming is often combined these days with imperative paradigms in many modern and powerful languages like Java, Python or JQuery. So you can continue to enjoy short, easy-to-understand, and more natural functional code with your working programming language. We now summarize the most important features of functional programming as follows:


Data Immutability: This is the basic feature of functional programming to avoid side effects. The immutability of data means that once it is created, we cannot change it. You can update the data by creating new data structures with the desired changes.


Recursion: It is widely used in functional programming to implement the iterative way to solve similar sub-problems. 


Function composition: Functional programming combines functions to create new ones. This chaining of functions allows you to construct complex code to perform multiple actions at once. The code nevertheless remains intuitive and simple to understand.


Higher-order functions: Functional programming allows functions to take other functions as arguments or return them as results. This feature reinforces code abstraction.


Data transformation: Functional programming promotes data transformation pipelines. It transforms data from one state to another.


Lambda expression: It is also called an anonymous function and is unnamed with a short form. It can take any number of arguments, but must only have one expression. Lambda expressions are useful in situations that allow for quick calculations where complete functions (such as functions defined with the ‘def’ keyword in Python) would not be necessary. Note that the lambda expression can be implemented in another complete function.


2. Example in Java

We provide some examples of the functional programming style in Java to show how the functional code is more concise and easier to understand. 


2.1. Example 1 in Java

In this first example, let’s assume that we have a ‘User’ class with some basic fields, constructors, and getters:


import java.sql.Date;

public class User {

    private String firstName;

    private String lastName;

    private String email;

    private Date birth;

    public User(String firstName, String lastName, String email, Date birth) {

        this.firstName = firstName;

        this.lastName = lastName;

        this.email = email;

        this.birth = birth;

    }

    public String getFirstName() {

        return firstName;

    }

    public String getLastName() {

        return lastName;

    }

    public String getEmail() {

        return email;

    }

    public Date getBirth() {

        return birth;

    }

}


To create data, we provide the following code to populate a list of users:


public static List<User> getList() {

        List<User> users = new ArrayList<>();

        users.add(new User("Jack", "Sparrow", "Jack@gmail.com", 

Date.valueOf("1980-12-25")));

        users.add(new User("Anakin", "Skywalker", 

"Anakin@gmail.com", Date.valueOf("1985-05-15")));

        users.add(new User("Indiana", "Jones", "Indiana@gmail.com", 

Date.valueOf("1970-01-03")));

        users.add(new User("Sarah", "Connor", "Sarah@gmail.com", 

Date.valueOf("1983-08-14")));

        users.add(new User("Ellen", "Ripley", "Ellen@gmail.com", 

Date.valueOf("1990-07-04")));

        users.add(new User("James", "Bond", "James@gmail.com", 

Date.valueOf("1975-02-17")));

        users.add(new User("Forrest", "Gump", "Forrest@gmail.com", 

Date.valueOf("1995-09-21")));

        users.add(new User("Harry", "Potter", "Harry@gmail.com", 

Date.valueOf("1984-11-07")));

        return users;

}


Now we want to sort this list. It can’t be better than using functional programming in Java to do it. You can imagine any sorting scenario and achieve it in very few lines of code. We describe this by creating a method that sorts the list of users by ‘lastname’, then by ‘firstname’, then by date of birth in descending order! The method contains a few lines of code:


public static List<User> UsersSorting(List<User> users) {

        Comparator<User> compareByLastName =

 Comparator.comparing(User::getLastName);

        Comparator<User> compareByFirstName =

 Comparator.comparing(User::getFirstName);

        Comparator<User> compareByBirth = Comparator.comparing(User::getBirth);

        Comparator<User> compareByNameBirth =

compareByLastName.thenComparing(

compareByFirstName.thenComparing(compareByBirth.reversed()));

        Collections.sort(users, compareByNameBirth);

        return users;

}


In this method, we create three comparators. The first compares users’ last names, the second compares their first names, and the last compares their dates of birth.


Subsequently, we combine these three comparators into one to ensure sorting our list in the desired order: last name in ascending order, first name in ascending order, and date of birth in descending order.


Finally, we apply this comparator to the list of users with the help of the static ‘sort’ method of the ‘Collections’ class to get the sorted list which we return in the last line of the ‘UsersSorting’ method. We can use this method inside a main method like this:


public static void main(String[] args) {

        List<User> users = getList();

        List<User> sortedUsers = UsersSorting(users);

        sortedUsers.forEach(x->{

            System.out.println(x.getFirstName() + " " + x.getLastName() + " " + x.getBirth());

        });

}


2.2 Example 2 in Java

Lambda expressions are supported in Java. They are linked to the concept of functional interface. In Java, a functional interface is an interface that defines only one abstract method. A functional interface is used to represent a function and is the context in which lambda expressions can be defined. A lambda expression in Java can have the following declaration:


(parameter1, .., parametern) -> expression


Now let’s show how to use lambda expressions to define anonymous functions in Java. In the code below, we define a class we named ‘Main’. Within the main method of the ‘Main’ class, we define ‘binaryOperator’ as a functional interface. The latter is an instance of the ‘IntBinaryOperator’ class that we import at the beginning of the code. 


The code of the ‘binaryOperator’ interface will be defined using lambda expression which takes two parameters ‘x’ and ‘y’ of type ‘int’ and returns their sum. We call the ‘applyAsInt’ method of ‘binaryOperator’ with some actual integer parameters (25 and 105) and display the result.


We can also define a custom function interface, which we call ‘OpInterface’ here. It contains a single abstract ‘opMethod’ method with three string parameters. Any implementation of this method should return a result of type String. 


In the main method code, we declare ‘opLam’ as a functional interface of the ‘OpInterface’ class. It takes three string parameters and returns their concatenation. We call the ‘opMethod’ method of the ‘opLam’ interface with some actual string parameters ("Hello", " ", "World!") and display the result.


import java.util.function.IntBinaryOperator;


interface OpInterface {

    String opMethod(String x, String y, String z);

}

public class Main {

    public static void main(String[] args) {

        IntBinaryOperator binaryOperator = (int x, int y) -> x + y;

        System.out.println("Result of addition: " + binaryOperator.applyAsInt(25, 105));


        OpInterface opLam = (String x, String y, String z) -> x + y + z;

        String result = opLam.opMethod("Hello", " ", "World!");

        System.out.println("Result of concatenation: " + result);

    }

}


Running the code above gives us the following result:


Result of addition: 130

Result of concatenation: Hello World!


3. Example in Python

Python is a language that provides many tools to effectively apply functional programming principles. In this section, let’s take some examples to illustrate the use of the functional programming paradigm in this language.


We import the ‘reduce’ function from the ‘functools’ module as a helpful function that we will explain how to use later. We define a high-order function ‘apply_op’ that accepts two parameters: ‘func’ which should be a function and ‘nums’ as a list of numbers. This method iterates over the ‘nums’ list to apply the ‘func’ function to each element of this list and saves the obtained result in the ‘result’ list.


from functools import reduce


def apply_op(func, nums):

    result = []

    for num in nums:

        result.append(func(num))

    return result


def square(x):

    return x ** 2


def double(x):

    return x * 2


list_nums = [10, 15, -25, -44, 50]


squared_nums = apply_op(square, list_nums)

print("List of squared numbers:", squared_nums)


double_nums = apply_op(double, list_nums)

print("List of doubled numbers:", double_nums)


To execute this high-order function,  we define some functions such as ‘square’ and ‘double’ to pass them as parameters to the ‘apply_op’ function. As their names suggest, the ‘square’ method squares a number, and the ‘double’ method is defined to double a number. 


Before applying the higher-order function ‘apply_op’ to ‘square’ and ‘double’, we need a list of numbers which we define and call ‘list_nums’. We first apply ‘apply_op’ to ‘square’ and ‘list_nums’. We then store the result in a ‘squared_nums’ list which we print out in the next statement. 


In the last two statements, we use the higher-order function ‘apply_op’ with the function ‘double’ and list ‘list_nums’ to get a new list of doubled numbers ‘double_nums’ which we print in the last statement.


We continue this example and we define a lambda function to compute the addition of two numbers. As expected, the lambda function has a short form. It takes two parameters and returns their sum. Next, we use the ‘reduce’ function, which is a high-order function, to calculate the sum of the numbers in the ‘list_nums’ list.


add = lambda x, y: x + y

summed_nums = reduce(add, list_nums)

print("Sum of numbers:", summed_nums)


We can define another function ‘is_even_num’ to check if a number is even. So, the ‘is_even_num’ function returns true if the number is true and false otherwise. We can use the predefined ‘filter’ function in Python to filter out the even numbers from the list ‘list_nums’ and print them in the third statement of the code.


is_even_num = lambda x: x % 2 == 0

even_nums = list(filter(is_even_num, list_nums))

print("Even numbers:", even_nums)


Another functional programming code in Python is to use the ‘map’ function. The latter applies a function to all the elements of a list to obtain a new list containing the results of the applied function. 


Here we apply ‘map’ to a lambda function which returns the cube of a number and the list ‘list_nums’. We use ‘list’ to get the application result as a list stored in the ‘cubed_nums’ list which we print in the last statement.


cubed_nums = list(map(lambda x: x**3, list_nums))

print("Cubed numbers:", cubed_nums)


The execution of the code above gives us the following output:


List of squared numbers: [100, 225, 625, 1936, 2500]

List of doubled numbers: [20, 30, -50, -88, 100]

Sum of numbers: 6

Even numbers: [10, -44, 50]

Cubed numbers: [1000, 3375, -15625, -85184, 125000]

The Most Powerful Concepts in Programming Languages: Recursion



In this blog, I would like to introduce the concepts that I believe are the most powerful in programming languages, and mastering them will give you solid programming thinking. I will explain them one by one with examples in Java and Python. After introducing the object-oriented concept in the first article, I will explain the recursion concept in this article.



1. Object-oriented

2. Recursion

3. Concurrency
4. Functional Programming and Lambda Expressions
5. Data Structure
6. Software Design Patterns
7. Annotations/Decorators
8. Machine Learning



1. What is Recursion?

Recursion is the characteristic of a process, algorithm, or function to call itself in its definition. It is a powerful technique to solve problems with similar structures. That means you break down the problem into chunks of similar sub-problems and apply the same solution to each of them iteratively until you reach the base case, which is solved immediately.


It’s a strong concept that a developer should master to write solid and short programs. It strengthens code reusability and allows you to think in an abstract and high-level way. It helps detect patterns by observing systems and extracting similar sub-problems.


Recursion is a powerful technique in tree traversal algorithms and graph data structures. It is also used in some search or sorting algorithms. However, recursion can lead to an infinite loop or out-of-memory issues if you don’t program it correctly. 


A recursive function or algorithm usually follows the subsequent steps:

- Define the base case: This is the simplest form of the problem and is solved without recursion. It plays the role of stopping the recursion.

- Define the recursive case: This is the case where you break down the problem into small sub-problems of the same nature.


When implementing a recursive function, you should consider the following:

- Invoke the recursive function: You have to determine exactly where to reference the function or algorithm in its definition to solve the entire problem.

- Combine the result if necessary: Combine the results you get from each recursive call if solving the problem requires it.


Let’s explain recursion and the above steps with the help of a famous mathematical example, namely the factorial function:

factorial(n):

    if n == 0:

        return 1

    else:

        return n * factorial(n – 1)


Factorial is a function defined only for positive integers (i.e. n>=0). The base case in the factorial function is when ‘n’ is equal to 0. It is mathematically proven that the factorial of 0 is 1 and does not require any recursion for its calculation. Now let’s define the recursive case. We know that the factorial of ‘n’ is calculated using the formula:

n! = n * (n-1) * (n-2) * .. * 1 or simply n! = n * (n-1)!


In other words, the factorial of ‘n’ is the result of multiplying ‘n’ and the factorial of ‘(n-1)’. Thus, the cases ‘n’ and ‘(n-1)’ can be calculated similarly. 


2. Example of Recursion in Java

Java supports recursion and provides the necessary built-in syntax to implement it. In this section, we will show how Java implements recursion using two examples.


2.1. Example 1

A small imperative code to implement the factorial function can look like this:


public static int factorial(int n) throws Exception {

    if (n<0) throw new Exception("Factorial cannot be calculated for a negative number: " + n);

    if (n==0) return 1;

    return n*factorial(n-1);

}


If the argument ‘n’ of the function whose factorial we want to calculate is negative, we raise an exception with a message. If the number is equal to 0, the function returns 0, which stops the iteration. Otherwise, the function returns the expression ‘n*factorial(n-1)’, which consists of the number itself multiplied by the factorial of ‘(n-1)’.  For a demo, you can call this function for the number 5 and -8:


public static void main(String[] args) {

     try {

           System.out.println("fact 5: " + factorial(5));

           System.out.println("fact 6: " + factorial(-8));

     } catch (Exception e) {

           e.printStackTrace();

     }

}


That will return the following result:

fact 5: 120

java.lang.Exception: Factorial cannot be calculated for a negative number: -8

at recursive.app.Factorial.factorial(Factorial.java:30)

at recursive.app.Factorial.main(Factorial.java:23)


2.2. Example 2

Let’s assume that each apprentice has completed a series of training courses. Each schooling has two dates, a start and an end. To do this, we create a ‘Schooling’ class as follows:


import java.time.LocalDate;

import java.util.Objects;

public class Schooling {

    private Integer id;

    private String name;

    private Integer apprenticeId;

    private LocalDate start;

    private LocalDate end;

    

public Schooling(Integer id, String name, Integer apprenticeId, LocalDate start, LocalDate end) {

        this.id = id;

        this.name = name;

        this.apprenticeId = apprenticeId;

        this.start = start;

        this.end = end;

    }

    public Integer getId() {

        return id;

    }

    public String getName() {

        return name;

    }

    public Integer getApprenticeId() {

        return apprenticeId;

    }


    public LocalDate getStart() {

        return start;

    }

    public LocalDate getEnd() {

        return end;

    }


    @Override

    public boolean equals(Object o) {

        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) return false;

        Schooling schooling = (Schooling) o;

        return Objects.equals(id, schooling.id)

                && Objects.equals(apprenticeId, schooling.apprenticeId);

    }

    @Override

    public int hashCode() {

        return Objects.hash(id, name, apprenticeId, start, end);

    }

}


Let’s create some data: 


public static List<Schooling> init() {

        List<Schooling> detailedSchooling = new ArrayList<>();

        detailedSchooling.add(new Schooling(100, "Java", 500, 

LocalDate.parse("2023-08-14"), LocalDate.parse("2023-08-27")));

        detailedSchooling.add(new Schooling(101, "Python", 500, 

LocalDate.parse("2023-08-28"), LocalDate.parse("2023-09-17")));

        detailedSchooling.add(new Schooling(102, "JS", 500, 

LocalDate.parse("2023-09-18"), LocalDate.parse("2023-10-01")));

        detailedSchooling.add(new Schooling(103, "Scrum", 500, 

LocalDate.parse("2023-10-08"), LocalDate.parse("2023-10-22")));

        detailedSchooling.add(new Schooling(100, "Java", 501, 

LocalDate.parse("2023-08-14"), LocalDate.parse("2023-08-27")));

        detailedSchooling.add(new Schooling(101, "Python", 500, 

LocalDate.parse("2023-08-28"), LocalDate.parse("2023-09-17")));

        return detailedSchooling;

}


We notice in these data that the apprentice with ‘id=500’ started new schooling after the end of another. We want to merge two courses into a single object when the last course succeeds the first. In other words, the start date of one instance occurs one day after the end date of another course. If we analyze the problem, we figure out that it is naturally recursive.


We take two objects from a list, compare them and when they can be merged, we delete them from this list to add instead the merged object to the list. Then, we repeat the same operation for the obtained list.


We cannot merge two instances of ‘schooling’ class and store the result in one. Additionally, only dates count for us, regardless of the type of school. So we create a new class with ‘apprentice id’, ‘start’, and ‘end’ as fields:


import java.time.LocalDate;

import java.util.Objects;


public class ApprenticeSchooling {

    private Integer apprenticeId;

    private LocalDate start;

    private LocalDate end;

    public ApprenticeSchooling(Integer apprenticeId, LocalDate start, LocalDate end) {

        this.apprenticeId = apprenticeId;

        this.start = start;

        this.end = end;

    }

    public Integer getApprenticeId() {

        return apprenticeId;

    }

    public LocalDate getStart() {

        return start;

    }

    public LocalDate getEnd() {

        return end;

    }

    @Override

    public boolean equals(Object o) {

        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) return false;

        ApprenticeSchooling that = (ApprenticeSchooling) o;

        return Objects.equals(apprenticeId, that.apprenticeId) && Objects.equals(start, that.start) && Objects.equals(end, that.end);

    }

    @Override

    public int hashCode() {

        return Objects.hash(apprenticeId, start, end);

    }

}


public static void main(String[] args) {

        List<Schooling> detailedSchooling = init();

        List<ApprenticeSchooling> appSchooling = detailedSchooling.stream()

.map(s->new ApprenticeSchooling(s.getApprenticeId(), s.getStart(), s.getEnd()))

                                .collect(Collectors.toList());

        List<ApprenticeSchooling> mergedSchooling =

 mergeSchoolingByDate(appSchooling).stream()

                                .sorted(Comparator.comparing(ApprenticeSchooling::getApprenticeId)

                                            .thenComparing(ApprenticeSchooling::getStart))

                                            .collect(Collectors.toList());


        for (ApprenticeSchooling s: mergedSchooling){

            System.out.println(" Schooling: " + s.getApprenticeId() + " >> " 

+ s.getStart() + " >> " + s.getEnd());

        }

}


After creating some data and saving them in the list ‘detailedSchooling’, we convert them to instances of class ‘ApprenticeSchooling’ by using ‘map’ in the statement to retain only the properties we need: ‘apprentice Id’, ‘start’ and ‘end’:


List<ApprenticeSchooling> appSchooling = detailedSchooling.stream().map(s→

new ApprenticeSchooling(s.getApprenticeId(), s.getStart(), s.getEnd()))

                                .collect(Collectors.toList());


To merge the instances, we call the ‘mergeSchoolingByDate’ method, which we define as follows:


public static List<ApprenticeSchooling>

 mergeSchoolingByDate(List<ApprenticeSchooling> appSchooling) {

        for (ApprenticeSchooling a: appSchooling){

            for (ApprenticeSchooling b: appSchooling){

                ApprenticeSchooling ab = mergeSchooling(a, b);

                if (ab!=null){

                    appSchooling = removechooling(appSchooling, a);

                    appSchooling = removechooling(appSchooling, b);

                    appSchooling.add(ab);

                    return mergeSchoolingByDate(appSchooling);

                }

            }

        }

        return appSchooling;

}


We take two instances from the list that contains all instances of the ‘ApprenticeSchooling’ class and then compare them individually. If we find a match, we remove these two instances from the list and add the result of their merge to the list. We call the function again with the updated list to repeat the same process until no more merge is found. Finally, when there is no merge possible, in the last statement we return the list. 


3. Example of Recursion in Python

It’s easy to implement recursive functions in Python. Consider directory tree traversal, which is a naturally recursive problem. We want to list all the sub-folders and files in a folder. To do this, we import the ‘os’ module to define a function we name ‘get_directory_content’. 


import os


def get_directory_content(direct, n):

    for elem in os.listdir(direct):

        elem_path = os.path.join(direct, elem)

        if os.path.isfile(elem_path):

            print(f'{n*ws}File:  {elem_path}')

        elif os.path.isdir(elem_path):

            print(f'{n*ws}Directory:  {elem_path}')

            get_directory_content(elem_path, n+1)



ws = '  '

get_directory_content("C:\\", 0)


In the code for this function, we use the ‘listdir’ function in the ‘os’ module to retrieve all direct sub-directories and files inside the ‘direct’ directory. The second parameter ‘n’ is just a number that indicates which directory level we are at.


We iterate over the obtained list and get the full path for each ‘elem’ element in that list by joining the ‘direct’ directory to the element itself to get ‘elem_path’ the element’s absolute path (or the full name). Next, we check if the element is a file to display it. 


We note that ‘ws’ is a global variable holding a whitespace. The expression {n*ws} prints ‘n’ spaces. That allows directory paths to be printed as a tree. If ‘elem_path’ is a directory, we print it out and then recursively call the function for that sub-directory with ‘n+1’ as a second parameter. At some point, we reach sub-folders that only contain files and the recursion ends.

Blog Posts

Enhancing Performance of Java-Web Applications

Applications built with a Java back-end, a relational database (such as Oracle or MySQL), and a JavaScript-based front-end form a common and...