Saturday, September 21, 2024

The Most Powerful Concepts in Programming Languages: Machine Learning



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-oriented, recursion, concurrency, functional programming, data structure, and design patterns, and annotations in earlier articles, I will elucidate the concept of machine learning 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 and Decorators

8. Machine Learning



1. Machine Learning

Machine learning (ML) has become essential as a software development model. Machine learning is a branch of artificial intelligence (AI) that promotes the development of algorithms and models that allow a computer to learn from provided data to make predictions or decisions.


The computer in this case is not explicitly programmed. In other words, it didn’t receive a program composed of statements to execute to provide a result. Moreover, an ML system can repeatedly improve the accuracy of the results delivered through experience. In other words, every time new data is fed to an ML system, that makes it learns and optimizes its performance.


Machine learning is a new reasoning and computational model. It is a new way of thinking about and writing programs, different from simply giving instructions to the computer to execute them and produce results. We give a summary of key concepts related to machine learning in the following paragraphs.


Data: This is the backbone of ML. ML algorithms need a large amount of data to learn patterns and make decisions and predictions. More data can help ML algorithms achieve better results. The data used by ML algorithms is structured as datasets. A dataset is a collection of rows, where each row is composed of multiple variables. For example, for a dataset composed of students at a university, a row might represent data about a specific student such as name, date of birth, degree, etc.


The data can be labeled or unlabeled. Each row in labeled data is tagged with a known output. On the other hand, unlabeled data means that outcomes are not associated with data.


A dataset is generally divided into three categories: training dataset, validation dataset, and testing dataset. A possible split of the dataset could be 70% of the data for training, 20% for validation, and 10% for testing. This division may vary depending on the size and specifics of the dataset.


Training: ML algorithms analyze input data and learn from it to train ML models. During a training process, the model optimizes its internal parameters to reduce errors and maximize its performance.


Parameter: It is an internal variable of an ML model that is learned during training. The ML model makes adjustments while training the data until the optimization goals are reached. If you take the linear regression model as an example, the parameters are the coefficients of the linear equation.


Hyperparameter: This is a parameter set before the training process and not learned by the model. It is a configuration parameter that determines the behavior of the learning algorithm. Hyperparameters cannot be updated during the training process. The number of hidden layers in a neural network is an example of hyperparameters.


Parameter vs Hyperparameter: The main difference between parameters and hyperparameters can be summarized in the following table based on the three criteria of learning, control, and optimization:



Parameter

Hyperparameter

Learning

It is learned from data during the training phase

It is set before the training phase

Control

It defines the internal behavior of the ML model

It defines the learning process and impacts model performance

Optimization

It is optimized during the training phase

It is tuned to improve the performance of the ML model


ML Model: An ML model is a computer representation that finds patterns or relationships in data. It is the result of training an algorithm on labeled or unlabeled data. Many types of ML models are defined. The choice of model depends on certain factors such as the nature of the problem, the desired result, and the type of data. The most popular category of ML models is regression models, including its simplest form, the linear regression model. The latter is used to predict the value of a dependent variable in terms of one or more independent variables.


Validation and Test: Better training ML models requires validating and testing them on distinct datasets to ensure they can be applied effectively to new data. Validation helps during the training process to improve the trained ML model and tune its hyperparameters. However, the test dataset is used after the model has been fully trained to evaluate the performance and accuracy of the ML model on completely unseen data. In other words, data that the ML model has never seen before.


Deployment: Once ML models are trained and validated, they can be deployed in the production environment to make predictions on unknown data. They can be integrated into applications and used to make decisions and predictions or automate recurring tasks.


Feature: It is a measurable property of data that is used as input to train ML models. It is also called an independent variable or predictor and helps capture relevant information that enables the model to detect patterns and make decisions or predictions. The choice of features has a considerable effect on the performance and accuracy of the outcomes delivered by the model. Features can be, for instance, numeric, text, or image. 


Label: It is a value associated with each row of a dataset or output returned by a model. Labels are also called dependent variables or target variables. Let’s say you want to generate a model to predict the weather. In this case, you need features like temperature, wind speed, etc. The label in this case is the predicted weather value like sunny, cloudy, etc.


Algorithm: ML algorithms are computational techniques that can extract hidden patterns from data to make decisions or predictions. They gain experience by learning on their own. They can thus improve their performance and the accuracy of their predictions or the results obtained. ML algorithms are classified into four common types, namely supervised learning, unsupervised learning, semi-supervised learning, and reinforcement learning.


2. Types of Machine Learning Algorithms

The types of ML algorithms are defined according to their purpose and the commonly defined ones are:


1. Supervised learning

Supervised learning algorithms require external supervision to be trained. It requires labeled datasets to attempt to find patterns and make accurate data predictions or classifications.


As a basic example, we feed an ML model with labeled data, which is images of apples with labels. After training the model with data, we want to apply the model by feeding it with unseen data, i.e. the unlabeled image of an apple. If the model predicts correctly, it will give us the word ‘apple’ as the response.


2. Unsupervised Learning

In this type of ML, a model is trained with unlabeled data. The model will determine correlations and identify patterns in the fed data. These algorithms are useful in cases where even humans don’t know what they are looking for. The outcome of a model after unsupervised training could be grouping data into clusters. 


As a basic example, you feed the ML model with unlabeled data, say with mixed images containing images of apples, bananas, and pears. The model will determine patterns and extract three different categories of fruits based on their similarities.


3. Semi-supervised Learning

In semi-supervised learning, the ML model is fed with labeled and unlabeled data. This combination allows the model to use the labeled data to understand the data to find missing elements by labeling the unlabeled data.


4. Reinforcement Learning

In this type of ML, the model, which is an agent, interacts with its dynamic environment. As it performs actions, it receives feedback in the form of reward or punishment, depending on whether the action taken was good or bad.


So it can add this new learning to its dataset. In reinforcement learning, part of the data is fed into the model externally and part is generated by the model itself during its training phase.


A simple example of reinforcement learning is a walking robot that has to reach a certain point as a goal. If it moves in the right direction and gets close to the target, it will be rewarded. Otherwise, it will be punished.


Application Domains

ML is increasingly being used in various areas such as image recognition, robotics, autonomous cars, medical diagnosis of diseases, cybersecurity, fraud detection, and much more.


Programming

Many ML libraries and frameworks are now available in various programming languages such as Java and Python. Java has libraries such as JavaML, MOA, and Weka. Python has libraries such as TensorFlow, PyTorch, scikit-learn, and Keras that provide the necessary tools to develop ML models.


Linear Regression

To put you in the picture and better understand ML technically, we would like to introduce you to the simplest and best-known model, the linear regression model. Linear regression is a statistical modeling technique used to find the correlation between a dependent variable and one or more independent variables. This model assumes that the dependent variable changes proportionally as the independent variable changes. The general formula of the linear model is represented as follows:


y = m0*x0 + m1*x1 + .. mn*xn + c


Where:

- y is the dependent variable we want to predict.

- x0, x1, ..., xn are the independent variables or features that effect the dependent variable.

- m0, m1, ..., mn are the regression coefficients or model parameters.

- c is the error or bias, which accounts for variability not explained by the model.


The linear regression equation is usually represented graphically. We will get a line where x0, .., xn are represented on the x-axis and y is represented on the y-axis. The coefficients m0, .., mn are the slope of the regression line which tells us how much we expect the y value to change when we change the x value by one unit. The constant c, also called the y-intercept, is the point where the regression line crosses the vertical y-axis.


Linear regression finds the best-fit line where the difference between the observed values and those predicted by the model is the smallest. Once the best-fitting line is found, the slope and intercept coefficients are determined and the linear regression equation is known, and ready to be used to make predictions.


2. Example in Java

Java has many libraries and frameworks to perform ML programming. We can use the ‘Apache Commons Math’ library to program the ML linear regression algorithm.

For this, we need a dataset. The dataset we will use is an example containing data on the baby’s growth. This dataset is composed of two tables representing the baby’s weight as the independent variable (X) and the baby’s length as the dependent variable (Y).


import org.apache.commons.math3.stat.regression.SimpleRegression;


public class LinearRegressionExample {


public static void main(String[] args) {

     childGrowthRegRegression();

}


public static void childGrowthRegRegression() {

     double[] weightX = {2.6, 3.5, 4.5, 5.2, 5.8}; //Kg

     double[] lengthY = {46.4, 50.9, 55.1, 57.9, 60.2}; //Cm


     //Create a SimpleRegression instance

     SimpleRegression childGrowthReg = new SimpleRegression();


     //Add the data points to the regression model

     for (int i = 0; i < weightX.length; i++) {

          childGrowthReg.addData(weightX[i], lengthY[i]);

     }


     //Get the regression intercept and slope

     double intercept = childGrowthReg.getIntercept();

     double slope = childGrowthReg.getSlope();


     //Display the regression equation

     System.out.println("Regression equation is: y = " + slope + " * x + " + intercept);


     //Use the obtained equation to make prediction

     double newWeightX = 8.0;


     //Calculate the predicted length for a new weight using the regression model

     double predictedLengthY = childGrowthReg.predict(newWeightX);

    

     System.out.println("Predicted Length for X=" + newWeightX + " is Y=" + predictedLengthY);

    }

}


In this example, we import the ‘Apache Commons Math’ library to perform the linear regression. As commented in this code, we can use this library to create an instance of a simple regression model, which we named ‘childGrowthReg’. 


Next, we fed the model with the dataset consisting of two arrays (‘weightX’ and ‘lengthY’). The model is now ready to generate the equation by determining the slope and the intercept coefficients. In the same code, we provide the model with a new weight value and ask the model to make a prediction, which we will print later. 


The result of the execution of this code is: 


Regression equation is: y = 4.287869643934824 * x + 35.576403138201556

Predicted Length for X=8.0 is Y=69.87936028968015

Now we want to visualize the data points and regression line in Java. For that, certain libraries are available to developers. For instance, we can use the ‘JFreeChart’ library in Java to plot the regression line. So, in a separate example, we show how to do this.


This code of the main method of the code below creates a graphical user interface (GUI) application using the ‘JFreeChart’ library. It creates an object called ‘frame’ as an instance of the ‘JFrame’ class and sets its properties. 


Next, it creates a dataset containing the points to plot on the graph (data points and regression line) using the ‘createDataset’ method. It then called the ‘createChart’ method to create a chart using the dataset. The next statement creates a chart panel to display the chart. We can customize the chart panel to have a specific size. After that, we add the chart panel to the frame, pack the frame to adjust its size to fit the content, and display the frame.


import org.apache.commons.math3.stat.regression.SimpleRegression;

import org.jfree.chart.ChartFactory;

import org.jfree.chart.ChartPanel;

import org.jfree.chart.JFreeChart;

import org.jfree.chart.plot.PlotOrientation;

import org.jfree.chart.plot.XYPlot;

import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;

import org.jfree.data.xy.XYDataset;

import org.jfree.data.xy.XYSeries;

import org.jfree.data.xy.XYSeriesCollection;


import javax.swing.*;

import java.awt.*;


public class LinearRegressionPlot {


    private static XYDataset createDataset() {


        double[] weightX = {2.6, 3.5, 4.5, 5.2, 5.8}; //Kg

        double[] lengthY = {46.4, 50.9, 55.1, 57.9, 60.2}; //Cm


        //Create a SimpleRegression instance

        SimpleRegression childGrowthReg = new SimpleRegression();


        //Add the data points to the regression model

        for (int i = 0; i < weightX.length; i++) {

            childGrowthReg.addData(weightX[i], lengthY[i]);

        }


        //Get the regression intercept and slope

        double intercept = childGrowthReg.getIntercept(); // intercept

        double slope = childGrowthReg.getSlope(); // slope


        //Create a series of data items in the form (x, y)

        XYSeries series = new XYSeries("Babies Growth");

        for (int i = 0; i < weightX.length; i++) {

            series.add(weightX[i], lengthY[i]);

        }

        //Create a collection of series to be used as a dataset

        //Add the data points to the dataset

        XYSeriesCollection xyData = new XYSeriesCollection(series);


        //Create a series that contains the regression line

        XYSeries babyWeight = new XYSeries("Weight and Length");

        //Get the regression line using the intercept and slope to calculate the first and last x pairs (x, y)


        //Get the first point of the line pair of (x, y)

        double x = series.getDataItem(0).getXValue();

        babyWeight.add(x, slope * x + intercept);


        //Get the last point of the line pair of (x, y)

        x = series.getDataItem(series.getItemCount() - 1).getXValue();

        babyWeight.add(x, slope * x + intercept);


        //Add the regression line to the collection

        xyData.addSeries(babyWeight);


        //xyData contains now the data items and the regression line

        return xyData;

    }


    private static JFreeChart createChart(final XYDataset dataset) {


        //Create an instance of JFreeChart

        JFreeChart chart = ChartFactory.createScatterPlot("Babies Growth", "Weight (Kg)", "Length (cm)",

                dataset, PlotOrientation.VERTICAL, true, false, false);


        //Get the XYPlot object

        XYPlot plot = chart.getXYPlot();

        //Create the renderer that creates the line

        XYLineAndShapeRenderer render = (XYLineAndShapeRenderer) plot.getRenderer();


        //Make the regression line visible

        render.setSeriesLinesVisible(1, Boolean.TRUE);

        return chart;

    }


    public static void main(String[] args) {

        //Create new frame

        JFrame frame = new JFrame();

        //Set frame properties

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Get the dataset that contains the points to be plotted

        XYDataset dataset = createDataset();


        //Create the chart

        JFreeChart chart = createChart(dataset);

        //Create the chart panel with the inside and set the size

        ChartPanel chartPanel = new ChartPanel(chart) {

            @Override

            public Dimension getPreferredSize() {

                return new Dimension(600, 450);

            }

        };

        

        //Add the chart panel to the frame

        frame.add(chartPanel);


        //Display the frame

        frame.pack();

        frame.setLocationRelativeTo(null);

        frame.setVisible(true);

    }

}


In the ‘createDataset’ method, we create a dataset for a linear regression model. It defines two arrays of data points, ‘weightX’ and ‘lengthY’ (the babies’ weight and length measurements), and uses them to fit a linear regression model. It then creates a series of data items in the form of (x, y) pairs from ‘weightX’ and ‘lengthY’ and adds them to the ‘xyData’ collection. It adds a regression line using the regression equation to the ‘xyData’ collection and returns it as the dataset.


In the ‘createChart’ method, we create a scatter plot using the ‘JFreeChart’ library as an instance of the ‘JfreeChart’ class. The ‘chart’ object takes as input a ‘dataset’ object of the ‘XYDataset’ class, which contains the data points and the regression line. We specify the title and the axis labels when creating ‘chart’. We get the plot of ‘chart’ and its render to make the line regression visible. Finally, we return the chart as a result.


Executing this code gives us the following frame:





3. Example in Python

Python is one of the most popular programming languages with several powerful libraries for implementing machine learning algorithms. Let’s look at three of these libraries, namely ‘numpy’,  ‘scikit-learn’, and ‘ matplotlib’ to show how easy it is to implement regression models in Python.


Note that ‘numpy’ is a library that helps and simplifies working with numerical data in Python. The ‘scikit-learn’ library implements machine learning algorithms including linear regression. Finally, the ‘matplotlib’ library is used to create graphic representations of data. 


The following example is about looking for the relationship as a linear regression between the weight of babies and their lengths. Let’s explain in detail this code. First, we import ‘LinearRegression’ from ‘sklearn’, and ‘pyplot’ from ‘matplotlib’ to visualize the model.


We prepare now the data that are two arrays ‘weight’ and ‘length’ by converting them to ‘numpy’ arrays ‘X’ and ‘y’. But, ‘X’ should be reshaped to a matrix with 1 row and ‘n’ columns, where ‘n’ is the size of ‘X’. This is necessary to apply the linear regression. After printing ‘X’ and ‘y’, we create an instance ‘child_growth_model’ of the ‘LinearRegression’ class. Then, we fit the linear regression model to the data using the ‘fit’ method. So we get the intercept and slope of the regression line with the help of the ‘intercept_’ and ‘coef_’ attributes of ‘child_growth_model’.


import numpy as np

from sklearn.linear_model import LinearRegression

import matplotlib.pyplot as plt



weight = [.6, 3.5, 4.5, 5.2, 5.8]

length = [46.4, 50.9, 55.1, 57.9, 60.2]


# Sample data, babies weight in Kg

X = np.array(weight).reshape(-1, 1)


# Sample data, babies height in Cm

y = np.array(length)


# Print the sample data

print('X: ', X)

print('y: ', y)


# Create a linear regression model

child_growth_model = LinearRegression()


# Fit the model to the data

child_growth_model.fit(X, y)


# Get the regression coefficients

intercept = child_growth_model.intercept_

slope = child_growth_model.coef_[0]


# Print the regression equation

print(f"Regression equation is: y = {slope:.2f} * x + {intercept:.2f}")


# Predict a new value using the regression model

newX = np.array([8.0]).reshape(-1, 1)

predictedY = child_growth_model.predict(newX)


# Print the predicted value Y

print(f"Predicted Y for X = {newX[0, 0]}: {predictedY[0]:.2f}")


# Plot the data points and the regression line using a scatter plot

plt.scatter(X, y, label='Babies Weight and Height')


# Draw the regression line

plt.plot(X, child_growth_model.predict(X), color='red', label='Regression Line of Babies Growth')


# Add axis labels

plt.xlabel('Weight (Kg)')

plt.ylabel('Height (Cm)')


# Add a legend

plt.legend()


# Show the plot

plt.show()


We use the obtained attributes to display the regression equation. As our model is ready to predict values, we run it for a new value ‘8.0’ using the ‘predict’ method of ‘child_growth_model’ and print the result in the next statement. Finally, we draw the data points and the regression line using a scatter plot of the ‘matplotlib’ library.


Running the example above gives us the following result:


X:  [[0.6]

 [3.5]

 [4.5]

 [5.2]

 [5.8]]

y:  [46.4 50.9 55.1 57.9 60.2]

Regression equation is: y = 2.62 * x + 43.82

Predicted Y for X = 8.0: 64.80








The Most Powerful Concepts in Programming Languages: Annotations and Decorators



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-oriented, recursion, concurrency, functional programming, data structure, and design patterns in earlier articles, I will elucidate the concept of annotations and decorators 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 and Decorators

8. Machine Learning



1. Annotations and Decorators

It’s a strong concept that allows you to think on a more abstract and higher level. Annotations reduce boilerplate code so that you don’t need to write it explicitly but implement it using annotations.


Not all programming languages support this concept. Sometimes they also use different terminology to perform a similar implementation of this concept. In Java, this is called an annotation, but in Python, a similar mechanism is called a decorator.


When an element (class, method, etc.) is annotated, the corresponding annotation does not affect or modify the annotated code. Annotations are used to produce informative data or documentation. They add new resources or new code that will be processed by the compiler and executed at compile or run time.


For instance, the Java annotation ‘@Override’ processes that a method is an override of another method in one of its super-classes or implemented interfaces. If this is not the case, the compiler throws an error.


In some programming languages like Java, the contents of an annotation can be retrieved at runtime using the provided reflection mechanism. The latter helps review code elements, including annotations. As you save data in annotations, you can use this data to make decisions and adapt your code as you wish.


Frameworks like Spring make extensive use of annotations, which help developers, when they want to implement a new behavior, to wrap it in an annotation. They don’t need to write explicit code for this. An example of this behavior is the ‘@Autowired’ annotation, which annotates a field declaration in a class.


We assume that we have a class ‘Service’ and inside this class, we need to declare an object of a class named ‘Product’. The ‘@Autowired’ annotation, which is applied to the product variable, allows the product instance to be automatically created when the ‘Service’ is created. You don’t need to instantiate the ‘Product’ class using explicit code.


class Service {

@Autowired

private Product product;

//...

}


2. Example in Java

To confirm the robustness of the concept of annotations, let’s take an example of Hibernate, which is a Java framework that heavily uses annotations. Hibernate is an object-oriented mapping (ORM) that maps Java persistent objects to database entities (tables).


Instead of performing different operations like retrieving rows from the database or saving new ones to the database, you can achieve the same goal by performing these operations on Java objects. They will have the same effect on the corresponding entities in the database.


Suppose we have a table ‘USER’ with columns, ‘FIRST_NAME’, ‘LAST_NAME’,  ‘EMAIL’, and the ‘BIRTH’. The first three columns are of type ‘VARCHAR’ and the ‘BIRTH’ column is of type ‘DATE’. To use hibernate, you must set up a configuration between your database and Java application. 


But here, we only focus on how to map between a Java class and a database table to show the strength of the annotation concept. We create the ‘User’ class to represent the ‘USER’ table. We can achieve this goal using ‘@Entity’ and ‘@Table’ annotations at the class level.


Using the ‘@Entity’ annotation persists instances of the annotated class into the database by creating a mapping from a Java class to a database table. To add more details to this mapping, you can use the ‘@Table’ annotation to specify the table name, its schema, and other information.


When you use these annotations, Hibernate will automatically generate the SQL statements necessary to perform the various operations on the corresponding table. The ‘@Id’ annotation placed on a field is used to mark the annotated field as a primary key of the entity.


The ‘@GeneratedValue’ annotation describes how the primary key is generated. In our example, this is set to ‘GenerationType.AUTO’. This means that Hibernate decides the most appropriate primary key generation strategy depending on the type of database used. That could be an auto-incrementing column strategy in the case of MySQL databases.


To map between a database column and an entity field, we need the ‘@Column’ annotation. This annotation includes many attributes to add additional detail to this column-field relationship, including the column name. In our example, the field name and column name are identical.


However, they can be different, and specifying the column name is required. Another attribute you can specify is ‘nullable’. If this attribute is set to false, the corresponding database column cannot have null values.


import jakarta.persistence.*;

import java.sql.Date;

import java.time.LocalDate;

import java.time.Period;


@Entity

@Table(name="USER")

public class User {


    @Id

    @GeneratedValue(strategy= GenerationType.AUTO)

    private Long id;


    @Column(name="FIRST_NAME", nullable=false)

    private String firstName;


    @Column(name="LAST_NAME", nullable=false)

    private String lastName;


    @Column(name="EMAIL", nullable=false)

    private String email;


    @Column(name="BIRTH", nullable=false)

    private Date birth;


    @Transient

    private int age;


    @PostLoad

    public void calculateAge() {

        age = Period.between(birth.toLocalDate(), LocalDate.now()).getYears();

    }

}


You can also define additional fields that you need, but these are not columns in the database. For example, to store temporary values or values calculated from the column values. To do this, you can use the ‘@Transient’ annotation. In our example, we use this annotation to declare a new ‘age’ field that does not correspond to any column in the ‘USER’ table.


Suppose we need to calculate the age value whenever an instance of the ‘User’ class is created. That is possible if a row of the ‘USER’ table is loaded from the database and mapped to this instance. The ‘@PostLoad’ annotation allows us to execute the annotated method (the ‘calculateAge’ method in our example) immediately after creating a user object to calculate the age as the difference between the date of birth retrieved from the database and Today.


3. Example in Python

Decorators in Python allow you to extend or change the behavior of a function or method without explicitly modifying its code. A decorator is implemented using a function that takes another function as input and returns a new function with a modified code of the input function. 


The decorator is a powerful concept that maintains the single responsibility principle, meaning that a class should only perform one function and do it well. Decorators ensure that code unrelated to the decorated function remains isolated from that function's code. Some common tasks you can use decorators for are logging and authentication. Another benefit of decorators is code reuse. The same decorator can be used for many functions. Let’s illustrate this with an example.


A good example of using decorators in Python is to calculate the execution time of a function. Instead of writing such code in each function or method, we can define a function as a decorator for all functions whose execution time we want to calculate. So, we define ‘exec_time’ as a decorator function that takes another function ‘func’ as input.


The decorator code consists of a nested function that surrounds the ‘func’ function. It will perform certain actions before and after executing the ‘func’ function. The action we perform before calling ‘func’ is to start counting the execution time of the function. Here we use ‘time.perf_counter()’ to do this.


Then, we execute the function and get the result. After the execution of the ‘func’ function is finished, we stop the time and get the difference between the end time and start time, which is the execution time of the wrapped function. We display the obtained time with a few information about the function and return its result in the final statement.


import time

from functools import wraps

from functools import reduce



def exec_time(func):

    @wraps(func)

    def exec_time_wrapper(*args, **kwargs):

        start_time = time.perf_counter()

        result = func(*args, **kwargs)

        end_time = time.perf_counter()

        total_time = end_time - start_time

        print(f'Function {func.__name__}{args} {kwargs} executed in {total_time:.2f} seconds')

        return result

    return exec_time_wrapper


We can apply this decorator to several functions. Let’s apply it to two different functions ‘calc_sum’ and ‘calc_average’. The first one returns the sum of all numbers squared in a range of ‘n’ numbers. The ‘calc_average’ returns the average of a ‘ls’ list of numbers. 


Using the ‘@’ symbol, we add ‘exec_time’ as a decorator for both functions. Let’s now call the functions with different parameters and see what will happen. The decorator will intercept the execution of each function and run its own code.


@exec_time

def calc_sum(n):

    total = sum((x * x for x in range(0, n)))

    return total



@exec_time

def calc_average(ls):

    return reduce(lambda a, b: a + b, ls) / len(ls)


# Define a list

lst = [15, 9, 55, 41, 35, 20, 62, 49]


# Calculate the average of the elements of lst

avg = calc_average(lst)


# Printing average of the list

print("Average of the list =", int(avg))


# Printing the result of calling the function ‘calc_sum’ with different parameters

print("Sum of 3 squared items =", calc_sum(3))

print("Sum of 10 squared items =", calc_sum(10))

print("Sum of 5000 squared items =", calc_sum(5000))

print("Sum of 10000 squared items =", calc_sum(10000))


Running the above code gives us the following result:


Function calc_average([15, 9, 55, 41, 35, 20, 62, 49],) {} executed in 0.00 seconds

Average of the list = 35

Function calc_sum(3,) {} executed in 0.00 seconds

Sum of 3 squared items = 5

Function calc_sum(10,) {} executed in 0.00 seconds

Sum of 10 squared items = 285

Function calc_sum(5000,) {} executed in 0.00 seconds

Sum of 5000 squared items = 41654167500

Function calc_sum(10000,) {} executed in 0.00 seconds

Sum of 10000 squared items = 333283335000

The Most Powerful Concepts in Programming Languages: Software Design Patterns



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-oriented, recursion, concurrency, functional programming, and data structure in earlier articles, I will elucidate the concept of software design patterns 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. Software Design Patterns

A design pattern is a reusable, universal solution to a common problem that occurs during software design and development. It is like a pre-made blueprint or template that you can accommodate to solve repeating design problems in software code. A simple example of a design pattern is the ‘Undo’ operation in text editors.


Design patterns provide best practices for structuring code and organizing classes. Using design patterns allows developers to work at a high level because they work at the abstract block level and do not have to worry about details that are already clearly defined when the design pattern is created.


When a team member says that we will use MVC (Model-View-Controller) in this project, all team members will understand what it is and the team will save a lot of time to explain it. In short, design patterns contribute to code reuse, modularity, flexibility, and maintainability.


But design patterns are not one-size-fits-all solutions. So don’t force yourself to use design patterns when you don’t need them. You must be sure that your problem and its application context require the use of a specific design pattern.


Classification of Design Patterns

We can classify design patterns based on their degree of complexity, level of abstraction, and scale of applicability of the software under design. If you take the scale of applicability as a categorization factor, you get two types: idioms and architectural patterns.


While idioms are low-level patterns to tackle implementation-specific situations in a particular programming language, architectural patterns are high-level general and independent of any programming language. All design patterns are classified according to their purpose. Three main patterns are distinguished.


Creational Patterns

You can use these patterns to create objects. That makes code easier to reuse and increases flexibility. Examples of these patterns are Singleton, Factory Method, and Builder.


Singleton: This pattern ensures that a single class instance is created and makes it available to the rest of the application through a global point.


Factory Method: This pattern aims to encapsulate the object creation logic. This pattern helps hide details and reuse code. New objects can be created using a common interface. However, the creation of instances occurs at the subclass level.


Builder: This pattern helps you build complex objects in stages. A complex object is an object with many fields that must be initialized. The builder pattern can help you do it step-by-step.


Structural Patterns

You can use these patterns to compose objects into larger structures. As examples of these patterns, we can mention Composite, Facade, and Proxy.


 - Composite: This pattern allows you to compose objects into a tree structure and then deal with the structure as if it were a single object. The tree structure is made up of objects descending to the common base type.


Proxy: This pattern is a replacement of an object. The purpose of a proxy is to control access to the original object, for example for security reasons. You verify the sent request in the proxy before redirecting it to the original object to ensure that it is correct and secure.


Facade: This pattern helps you create a simpler interface for accessing a complex collection of classes.


Behavioral Patterns

You can use these patterns to manage communication between objects and the flow in a system. An example of this pattern is Observer.


Observer: This pattern allows objects (called observers) to subscribe to an object they are observing (called an observable) and be notified when an event occurs on that observable or its state changes.


State: This pattern enables an object to change its behavior when its internal state changes.


Memento: This pattern allows saving and restoring an object’s previous state, as well as hiding its implementation. The ‘Undo’ operation in a text editor exemplifies this pattern.


Architectural Patterns

Architectural patterns are general, high-level patterns that are independent of programming languages. They concern the design structure of systems.


Model-View-Controller (MVC): This pattern is used in web-application and aims to separate the code logic into three interconnected elements. The model holds the data. The view represents the user interface and the controller is the connection between the model and the view.


Model-View-ViewModel (MVVM): This pattern is similar to MVC but defined for GUI (Graphical User Interface). It promotes the separation of the GUI from the model and defines business logic for it.


Layered Architecture: This pattern breaks down an application into sub-tasks. Each sub-task is implemented in a distinct layer of a specific level of abstraction. The layers interact with each other. The four common layers are the presentation layer, the application layer, the business logic layer, and the data access layer.


Concurrency Patterns

These patterns contribute to solving the common problems in the multi-threaded programming paradigm. 


Producer-Consumer: This pattern coordinates the exchange of data between the threads which produce data (producers) and those which consume them (consumers).


Mutex: This pattern synchronizes access to a shared resource in a concurrent application. It allows only one thread to use that resource at a time.


Read-Write Lock: This pattern is similar to mutex, except that it allows multiple readers or a single writer to access a shared resource.


2. Example in Java

Here we provide a simple example of a design pattern in Java, which is the singleton. This pattern ensures that only one instance is created. This is useful in cases where your application only needs a single object to act as a coordinator.


To implement this pattern in Java, we need to declare the constructor as private to prevent new instances from being created. But we need to define a method (‘getInstance’ in this case) to create an instance of the ‘Singleton’ class and return it.


public class Singleton {

    private static Singleton instance;

    private Singleton() {

    }


    public static Singleton getInstance() {

        if (instance == null) {

            instance = new Singleton();

            System.out.println("Singleton created");

        }

        return instance;

    }

}


We can call this instance anywhere inside the application and use it. 


public class MainApp {

    public static void main(String[] args) {

        Singleton singleton = Singleton.getInstance();

    }

}


Let’s check if the ‘Singleton’ class only creates one instance. To do this, we create two instances of this class and then compare them in the following code:


public class MainApp {

    public static void main(String[] args) {

        Singleton singleton1 = Singleton.getInstance();

        Singleton singleton2 = Singleton.getInstance();

        System.out.println(singleton1.equals(singleton2));

    }

}


The output of the execution of this code is:


Singleton created

true


That confirms that only one instance of the ‘Singleton’ class is created.


3. Example in Python

Python has the built-in syntax to implement different design patterns. We show in this section how Python implements the singleton pattern. For this, we define a class that we want to be singleton and override its ‘__new__’ method. 


Remember that the static method ‘__new__’ is part of any class and is called each time to create a new object of the corresponding class. But, before that, we declare a new field which we bane ‘single_instance’ to hold the unique instance of the class. 


The ‘__new__’ method has at least the class as parameter. Inside the code of ‘__new__’ method, we verify whether ‘single_instance’ is already defined or not. If it is not defined, we call ‘__new__’ method of the super-class to create an instance and store it in ‘single_instance’. At the end of the code, we return this instance accordingly.


class Singleton:

    single_instance = None


    def __new__(cls):

        if cls.single_instance is None:

            print("Creating instance")

            cls.single_instance = super().__new__(cls)

        return cls.single_instance


To make sure that this code only creates a single instance at the beginning and returns it every time you want to create an object of this class, you can add the following code:


s1 = Singleton()

s2 = Singleton()

print(s1 == s2)


In this code, we create two instances of the ‘Singleton’ class and indicate whether these two instances are equal or not. The result of running this code is:


Creating instance

True


The message ‘Creating instance’ is displayed only one time and the two instances are equal.

The Most Powerful Concepts in Programming Languages: Data Structure



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, concurrency, and functional programming in the earlier articles, I will elucidate the concept of data structure 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. Data Structure

A data structure is a way to save data to be processed by a program. A data structure allows the organization of data so that the program can access and manage it efficiently. Data structures provide built-in support for operations such as reading, inserting, deleting, and updating. Many types of data structures are defined in computer science to implement these operations in different ways to deal with various real-life situations based on criteria like the kind of required operations (insertion, deletion, update, and read), efficiency, and memory constraints. That’s why you should use a suitable data structure for each situation depending on the requirements of the problem at hand. Learn well the logic behind each data structure to use in the right context and take full advantage of its purpose. For clarity on this topic, we mention here some well-known data structures.


Array: It is a sequential collection of items saved in contiguous memory locations. You can access any element of the array using its index. In general, the length of an array is permanently fixed at the time of its initialization.


List: It is an ordered collection of elements. You can insert elements wherever you want. In other words, you can add one or many elements at the beginning, at the end, or after a specific element or position. You can access an element using its index or any other criteria (for example, identifier if your elements are objects). The size of the list is not fixed. A list can contain any number of elements.


Set: It’s an unordered data structure that implements the concept of the set as defined in mathematics. Each element in a set can only have one occurrence.


Queue: You typically use this data structure in situations where first come is always first served. Queues store data sequentially. You can add an item only to the end of the queue (enqueue) and remove only the first item from the queue (dequeue). Queues implement what we call the FIFO (First-In-First-Out) principle.


Stack: This data structure follows the Last-In-First-Out (LIFO) principle. That means that the last element added to the stack is the first that can be removed. In other words, you can only remove elements from the top. The main operations you can apply to a stack are ‘Push’, ‘Pop’, and ‘Top’ (sometimes called ‘Peek’). ‘Push’ consists of adding an element to the top of the stack. ‘Pop’ is used to remove the top element from the stack. The ‘Top’ operation consists of reading the top item of the stack without removing it. Stacks are essential for compilers to evaluate arithmetic expressions.


Key-Value Pairs: In key-value pair data structures, each key is associated with a single value. Keys are unique and allow values to be retrieved quickly. Hashing is a typical technique for implementing this type of data structure. The ‘Map’ data structure in Java is one of the implementations of key-value pairs. On the other hand, Python has the concept of ‘Dictionary’ which also implements the structure of key-value pairs.


2. Example in Java

Data structures are not only a way to store data but also a way to organize it and perform the necessary operations to implement your strategy to effectively solve a problem.


Let’s take an example of a queue in Java in this section. Queue is a data structure that follows the FIFO (First-In-First-Out) principle. That means that when you want to delete an item, it must be the first one you add to the queue. It reflects the principle of a queue, where a group of people wait in order of their arrival to buy a coffee at a coffeehouse. Programmatically, Java provides a ‘Queue’ interface that you can implement in different ways depending on your requirements.


However, it can also have consequences in terms of performance when executing some operations like reading, inserting, and deleting. Some implementations of the ‘Queue’ interface are ‘PriorityQueue’ and ‘LinkedList’. 


‘PriorityQueue’ implements a queue by keeping elements in a specific order, which can be natural order or custom order, such as alphabetical order of strings. You can use this implementation if you have requirements like storing tasks with priorities.


On the other hand, if you want to keep the same order in which you inserted your items, you can use ‘LinkedList’. This data structure contains elements in blocks linked to each other using pointers. The list has a link to the first element and each block (containing an element) stores the data and a link to the previous and next block in the list. ‘LinkedList’ allows you to perform efficient operations such as reading, updating, and deleting. Let’s take an example to show how to use ‘LinkedList’ to implement a queue:


import java.util.LinkedList;

import java.util.Queue;


public class LinkedListExample {

    public static void main(String[] args) {

        //Create a Queue using LinkedList

        Queue<String> queue = new LinkedList<>();

        //Add elements to the queue

        queue.add("E-1");queue.add("E-2");queue.add("E-3");

        //Display the content in the queue

        System.out.println("Queue: " + queue);

        //Show the first element of the queue

        System.out.println("First element: " + queue.peek());

        //Remove the first element from the queue

        System.out.println("Removed element: " + queue.poll());

        //Display the content of the queue after a deletion

        System.out.println("Queue after deletion: " + queue);

    }

}


In this example, we create a ‘queue’ instance by instantiating the ‘LinkedList’ class. We can add some elements to the queue using the ‘add’ method of the ‘LinkedList’. We can use the ‘peek’ method of the ‘LinkedList’ to display the elements in the queue and the ‘poll’ method to remove the first element from the front of the queue. The execution of the example above provides the following output:


Queue: [E-1, E-2, E-3]

First element: E-1

Removed element: E-1

Queue after deletion: [E-2, E-3]


3. Example in Python

Python offers many types of data structures that help you organize, store, and use your data efficiently. Find the best-suited data structure among those available to help you solve your problem. In other words, don’t take any type and struggle to adapt it to your specific use case. But choose the data structure that implements the necessary operations and fits your needs.


Now let’s introduce some of the most popular data structures in Python, namely lists and dictionaries.


3.1. Example 1: Lists

Lists are ordered and mutable collections of items of different types (integers, strings, ..). You can legitimately define a list like this:


mixed_list = [1, "Hello", None, -15.20]


Let’s take a simple example of a list called ‘int_list’ which contains elements of the same type (integer). The code below will be commented out and show some basic operations that can be applied to a list. These basic operations include accessing list elements using indexes and iterating over list elements. Other operations you can perform are adding, removing, or updating items from a list. You can add an item to the end of the list or add it to a specific index. You can also sort and reverse the list.


# Create a list of integers

int_list = [10, 25, -50, 27, 105]

print("Original list:", int_list)


# Access an element of the list using its index

# Get the first element

print("First element:", int_list[0])

# Get the second element

print("Second element:", int_list[1])

# Get the last element

print("Last element:", int_list[-1])


# Iterate over the elements of the list

print("Elements of the list:")

for num in int_list:

    print(" Element:", num)



# Iterate over the indexes of the list

print("Index-value of the list:")

for i in range(len(int_list)):

    print("  Element at index", i, "is", int_list[i])


# Sorting the list

int_list.sort()

print("Sorted list:", int_list)


# Reverse the list

int_list.reverse()

print("Reversed list:", int_list)


# Append an element to the end of the list: add here the number 280

int_list.append(280)

print("List after adding an element:", int_list)


# Update elements of the list: update here the third element

int_list[3] = 38

print("Updated list:", int_list)


# Insert an element at a specific position in the list: insert the number 35 at the index 2

int_list.insert(2, 35)

print("List after insertion:", int_list)


# Remove element from the list: remove here the element 25

int_list.remove(25)

print("List after removal:", int_list)


# Get the length of the list

print("Length of the list:", len(int_list))


# Check if an element exists in the list

if 15 in int_list:

    print("List contains the number 15")

else:

    print("List does not contain the number 15")


Running the code above gives us the following result:


Original list: [10, 25, -50, 27, 105]

First element: 10

Second element: 25

Last element: 105

Elements of the list:

 Element: 10

 Element: 25

 Element: -50

 Element: 27

 Element: 105

Index-value of the list:

  Element at index 0 is 10

  Element at index 1 is 25

  Element at index 2 is -50

  Element at index 3 is 27

  Element at index 4 is 105

Sorted list: [-50, 10, 25, 27, 105]

Reversed list: [105, 27, 25, 10, -50]

List after adding an element: [105, 27, 25, 10, -50, 280]

Updated list: [105, 27, 25, 38, -50, 280]

List after insertion: [105, 27, 35, 25, 38, -50, 280]

List after removal: [105, 27, 35, 38, -50, 280]

Length of the list: 6

List does not contain the number 15


3.1. Example 2: Dictionaries

Dictionaries are data structures made up of key-value pairs that enable efficient key-based search. They can contain values of different data types such as lists, nested dictionaries, or other objects.


Here we take a code example that shows how we apply certain operations to a dictionary named ‘employee’. These operations include accessing dictionary values using keys or extending the dictionary by adding new key-value pairs. 


You can also update dictionary values or delete keys from it. You can check whether a key exists in the dictionary or not. This is very useful because updating a value and deleting a key from the dictionary can result in an exception if the key does not exist. By checking the existence of the key, such exceptions can be avoided. You can certainly iterate through keys and values.


# Create a dictionary of employee

employee = {

    "firstname": "Mary",

    "age": 30,

    "position": "Junior Software Developer",

    "address": "Markt 100, Vienna Austria"

}


# Print the dictionary

print("Original dictionary:", employee)


# Access values in the dictionary using keys

print("First name:", employee["firstname"])

print("Age:", employee["age"])

print("Position:", employee["position"])

print("Address:", employee["address"])


# Update values in the dictionary

# Update the age

employee["age"] = 31

# Update the position

employee["position"] = "Senior Software Developer"

print("Updated dictionary:", employee)


# Add new key-value pairs to the dictionary

employee["lastname"] = "Smith"

# Add a value as a list to the key email

employee["email"] = ["mary@gmail.com", "marysmith@yahoo.com"]


# Nested dictionary

employee["task_priority"] = {"Task 1": 4, "Task 2": 3, "Task 3": 1}

print("Dictionary after adding new items:", employee)


# Remove key-value pairs from the dictionary:

# Remove the 'address' key

del employee["address"]

print("Dictionary after removing an item:", employee)


# Get the length of the dictionary

print("Length of the dictionary:", len(employee))


# Check if a key exists in the dictionary

if "lastname" in employee:

    print("Last name:", employee["lastname"])

else:

    print("Last name does not exist")


# Iterate over the key-value pairs of the dictionary and show them

print("Key-value pairs:")

for key, value in employee.items():

    print(key, ":", value)


# Get a list of keys and values from the dictionary

keys = list(employee.keys())

values = list(employee.values())

print("Keys:", keys)

print("Values:", values)



Executing the code above gives us the following output:


Original dictionary: {'firstname': 'Mary', 'age': 30, 'position': 'Junior Software Developer', 'address': 'Markt 100, Vienna Austria'}

First name: Mary

Age: 30

Position: Junior Software Developer

Address: Markt 100, Vienna Austria

Updated dictionary: {'firstname': 'Mary', 'age': 31, 'position': 'Senior Software Developer', 'address': 'Markt 100, Vienna Austria'}

Dictionary after adding new items: {'firstname': 'Mary', 'age': 31, 'position': 'Senior Software Developer', 'address': 'Markt 100, Vienna Austria', 'lastname': 'Smith', 'email': ['mary@gmail.com', 'marysmith@yahoo.com'], 'task_priority': {'Task 1': 4, 'Task 2': 3, 'Task 3': 1}}

Dictionary after removing an item: {'firstname': 'Mary', 'age': 31, 'position': 'Senior Software Developer', 'lastname': 'Smith', 'email': ['mary@gmail.com', 'marysmith@yahoo.com'], 'task_priority': {'Task 1': 4, 'Task 2': 3, 'Task 3': 1}}

Length of the dictionary: 6

Last name: Smith

Key-value pairs:

firstname : Mary

age : 31

position : Senior Software Developer

lastname : Smith

email : ['mary@gmail.com', 'marysmith@yahoo.com']

task_priority : {'Task 1': 4, 'Task 2': 3, 'Task 3': 1}

Keys: ['firstname', 'age', 'position', 'lastname', 'email', 'task_priority']

Values: ['Mary', 31, 'Senior Software Developer', 'Smith', ['mary@gmail.com', 'marysmith@yahoo.com'], {'Task 1': 4, 'Task 2': 3, 'Task 3': 1}]

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: Concurrency



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 and recursion in the first two articles, I will elucidate the concept of concurrency 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 Concurrency?

Concurrency is the ability of a program to perform multiple tasks simultaneously. Concurrency promotes efficient use of resources and increases program responsiveness and performance. However, concurrency is challenging due to potential synchronization issues. Tasks running in parallel are not entirely independent of each other. They often share data and modify it.


Changing the shared data by multiple tasks simultaneously can corrupt data and lead to inconsistent states or problems such as deadlocks. That’s why you should define synchronization mechanisms to ensure correctness and avoid such issues.


Many programming languages such as Java or Python allow concurrent programming with various concepts, models, and the corresponding syntax. Java provides mechanisms for thread management and synchronization. On the other hand, Python also has threading and multiprocessing modules.


Concepts such as thread, task, and process are commonly used in concurrent programming. Here are some common approaches to concurrency that you should master to get ready to develop concurrent programs.


Process: It is an independent program instance that executes in a separate memory space. A process uses its own resources. It can run concurrently with other processes and communicate with them. Known mechanisms allowing inter-process communication are message passing, pipes, and shared memory.


Thread: It is an execution unit or a worker inside a process. Threads of the same process share its memory space. They can run concurrently, communicate, and synchronize with each other.  


Task: it is a piece of work to be done. You can use a thread to accomplish a task.


Asynchronous Programming: It is a programming model that enables your program to run tasks concurrently without waiting for each other. During the execution of the tasks, the program is not blocked and is ready to respond to other events. Asynchronous programming is very useful in event-driven systems or I/O-bound applications.


Parallelism: When we talk about concurrency, we say that tasks run simultaneously, but this is still theoretical. From an implementation perspective, the situation is slightly different. It is not the case of true concurrency when multiple tasks are running on a single processor. The processor will use the shared time to run one task at a time. 


We speak of true concurrency or parallelism when multiple tasks are executed on multiple processors or cores. In a distributed system, the tasks are carried out by several processors or computers at the same time. That is much more efficient. Thanks to the enormous advancement and availability of hardware, parallelism became affordable.


Concurrency Control: As mentioned earlier, concurrent tasks share resources. They often access and modify the same variables, making their values unpredictable. That is where concurrency control comes to the rescue. 

It is the procedure that provides mechanisms for managing shared resources between concurrent tasks. Techniques such as locks, semaphores, and atomic operations are defined and used to guide and administer access to the shared resources and avoid conflicts.


2. Example of Concurrency in Java

We will demonstrate in this section a concurrent program in Java using a small example of threads. For this reason, we first define a sub-class of the ‘Thread’ class as follows:


public class MyThread extends Thread {

    private String name;

    private int step;

    private int sleepTime;


    public MyThread(String name, int step, int sleepTime) {

        this.name = name;

        this.step = step;

        this.sleepTime =  sleepTime;

    }


    @Override

    public void run() {

        System.out.println(name + " - Start");

        for (int index = 0; index < step; index++) {

            System.out.println(" > " + name + " - Step: " + index);

            try {

                Thread.sleep(sleepTime);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

        System.out.println(name + " - End");

    }

}


The ‘MyThread’ class must extend the ‘Thread’ Java class to run concurrently. We declare three fields ‘name’ (thread name), ‘step’ (loop execution number), and ‘sleepTime’ (waiting time). ‘MyThread’ class needs to replace the ‘run’ method with new code to perform the actual work. In our case, it’s simply a code where we display the start and the code of the task. Then we run a loop between them ‘step’ times. The statement inside this loop consists of printing a message. For each execution of the loop, we wait for ‘sleepTime’, and continue the execution.


Let’s take a simple example of creating and starting instances of ‘MyThread’ in Java:


public class ThreadExample {

    public static void main(String[] args) {

        System.out.println("Main Thread - Start");

        // Create two threads

        Thread thread1 = new MyThread("Thread 1", 4, 500);

        Thread thread2 = new MyThread("Thread 2", 4, 700);

        // Start the execution of the two threads

        thread1.start();

        thread2.start();

        // Main thread finished its execution

        System.out.println("Main Thread - End");

    }

}


In the main method of the ‘ThreadExample’ class, we create two instances of the ‘MyThread’ class. We can distinguish them by their names. After that, we call the ‘start’ method for each one of them. That means the ‘run’ method is executed for each thread. Both threads and the main thread run concurrently. Let’s inspect the output of executing this code:


Main Thread - Start

Main Thread - End

Thread 2 - Start

Thread 1 - Start

 > Thread 1 - Step: 0

 > Thread 2 - Step: 0

 > Thread 1 - Step: 1

 > Thread 2 - Step: 1

 > Thread 1 - Step: 2

 > Thread 2 - Step: 2

 > Thread 1 - Step: 3

Thread 1 - End

 > Thread 2 - Step: 3

Thread 2 - End


According to the result of the code, the threads  ‘Thread 1’ and ‘Thread 2’, and the main thread run concurrently and are independent of each other. The main thread completes before the two threads even begin.


3. Example of Concurrency in Python

Python allows concurrent programming and supports the concept of ‘Thread’. In this example, we show how to create and start threads in Python by using the ‘threading’ module:


import threading

import time


def my_thread_func(thread_name, step, sleep_time):

    for i in range(step):

        print(f"{thread_name} - Start")

        print(f"{thread_name} - Step: {i}")

        time.sleep(sleep_time)

        print(f"{thread_name} - End")


print("Main Thread - Start")

# Create two threads

thread1 = threading.Thread(target=my_thread_func, args=("Thread 1", 3, 1))

thread2 = threading.Thread(target=my_thread_func, args=("Thread 2", 3, 1))

# Start the threads

thread1.start()

thread2.start()

# Main thread continues its execution

print("Main Thread - End")


In this example, we create a method ‘my_thread_func’ that implements dummy code and prints information such as the thread name at runtime to track of the order of execution. Then we create two objects of the ‘Thread’ class defined in the ‘threading’ module. We pass the ‘my_thread_func’ method with the necessary parameters as the code of each thread.


Next, we use the ‘start’ method to start the threads, which triggers the execution of the target function for each thread. These two threads and the main thread run concurrently and independently of each other. The execution of this code gives us the following result:


Main Thread - Start

Thread 1 - Start


Thread 1 - Step: 0

Thread 2 - StartMain Thread - End

Thread 2 - Step: 0


Thread 2 - End

Thread 1 - EndThread 2 - Start

Thread 2 - Step: 1


Thread 1 - Start

Thread 1 - Step: 1

Thread 1 - End

Thread 1 - Start

Thread 1 - Step: 2

Thread 2 - End

Thread 2 - Start

Thread 2 - Step: 2

Thread 2 - EndThread 1 – End

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...