I selected the Funny String practice from HackerRank. Given a string one has to reverse the characters and place them in a second string. Then traverse both comparing the absolute differences among consecutive characters. If the differences match then the function should return the string “Funny”; otherwise it should return “Not Funny”.

As usual I try to develop a simple and well documented function.

The code for the function follows:

// Complete the funnyString function below. static String funnyString(String s) { // **** r holds the reverse of s **** String r = new StringBuilder(s).reverse().toString(); // **** build associated arrays with byte values **** byte[] sba = s.getBytes(); byte[] rba = r.getBytes(); // **** compare the values of the adjacent bytes **** for (int i = 0; i < (sba.length - 1); i++) { // **** difference in sba **** int sbaDiff; if (sba[i] > sba[i + 1]) { sbaDiff = sba[i] - sba[i + 1]; } else { sbaDiff = sba[i + 1] - sba[i]; } // **** difference in rba **** int rbaDiff; if (rba[i] > rba[i + 1]) { rbaDiff = rba[i] - rba[i + 1]; } else { rbaDiff = rba[i + 1] - rba[i]; } // **** check if they do NOT match **** if (sbaDiff != rbaDiff) return "Not Funny"; } // **** differences match **** return "Funny"; }

If you are interested in taking a look at the entire solution, you may find it in my GitHub repository.

As usual, if you have comments or questions regarding this or any other post in this blog, please leave me a comment bellow.

Keep on learning, practicing and having fun!

John

Please follow me on Twitter: **@john_canessa**

I took a look at the next Hacker Rank challenge for strings in Java. If interested you may find the description here.

It seems like John (pure coincidence) has a set of rocks. Rocks may have different minerals. For some reason if any mineral is present in all rocks then they become a gem. Not sure how they arrived to such conclusion. A set of coal piles is still coal, not a gem. Notwithstanding my last comment; your mission, if you do accept, is to find how many gems are in John’s sets of rocks.

Seems like we need to find the count of which characters representing minerals are found in all the strings representing the rocks. I decided to preserve the metaphor in my solution which follows:

// Complete the gemstones function below. static int gemstones(String[] arr) { final String minerals = "abcdefghijklmnopqrstuvwxyz"; int gems = 0; // **** loop through the minerals **** for (int i = 0; i < minerals.length(); i++) { // **** get the current mineral **** char mineral = minerals.charAt(i); // **** loop through the rocks (arr) looking for this mineral **** int mineralInRock = 0; for (int j = 0; j < arr.length; j++) { // **** check if this mineral is in this rock **** if (arr[j].indexOf(mineral) != -1) mineralInRock++; } // **** count this gem (if found in all rocks) **** if (mineralInRock == arr.length) gems++; } // **** total number of gems **** return gems; }

The code passed the battery of tests and was accepted.

If you wish to see the entire code, you may find it in my GitHub repository.

As usual, if you have a comment or question in this post or any other post in my blog, please do not hesitate and leave me a note.

Keep on learning, experimenting and having fun;

John

Please follow me on Twitter: **@john_canessa**

My wife left with our neighbor shopping. They should be back around noon. We are planning on making some ravioli from scratch. On workdays my wife cooks, but on weekends we both do. Have some red wine from last week which we did not finish. It should be a nice and cozy lunch.

I looked for the next challenge on Strings in Java at HackerRank. If interested you can look it up here. The challenge deals with subsequences. They picked up the string “hackerrank” to be searched for in the provided strings, hence the title of the challenge.

I developed the solution using the Eclipse IDE.

For simplicity when I work on the solution I changed the following line:

BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(System.getenv("OUTPUT_PATH")));

To:

BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));

That allows me to run tests in my environment using as much code as possible from HackerRank.

The function for my solution follows:

static String hackerrankInString(String s) { final String hr = "hackerrank"; // **** perform some sanity checks **** if (s.length() < hr.length()) return "NO"; // **** traverse the hackerrank string **** int startIndex = 0; for (int i = 0; (i < hr.length()) && (startIndex <= s.length()); i++) { // **** get the next character from "hackerrank"**** char ch = hr.charAt(i); // **** start at this point in s **** String ss = s.substring(startIndex); // **** get the index for this character in the substring **** int index = ss.indexOf(ch); // **** this character not found **** if (index == -1) return "NO"; // **** update the start index **** startIndex += (index + 1); } // **** hackerrank found **** return "YES"; }

If you are interested in the entire solution you can find it here in my GitHub repository.

As usual, if you have comments or questions, please leave me a note bellow.

Keep on learning and practicing;

John

Please follow me on Twitter: **@john_canessa**

The idea is to check if a password entered by a user follows the requirements stipulated in the challenged for a strong password. If it does not, one needs to return the number (not types) of characters missing to make current password specified by the user strong.

My solution for the challenge follows:

// Return the minimum number of characters to make the password strong static int minimumNumber(int n, String password) { // **** **** final String numbers = "0123456789"; final String lowerCase = "abcdefghijklmnopqrstuvwxyz"; final String upperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; final String specialCharacters = "!@#$%^&*()-+"; // **** number of possible missing characters **** int m = 4; int mask = 0x0f; // **** traverse the password **** for (int i = 0; (i < password.length()) && (m > 0); i++) { // **** current character **** char c = password.charAt(i); // **** check in numbers (mask = 0x01) **** if ( (((mask & 0x01) == 1) ? true : false) && (numbers.indexOf(c) != -1)) { m--; mask &= 0x0e; } // **** check lower case letters (mask = 0x02) **** if ( (((mask & 0x02) == 2) ? true : false) && (lowerCase.indexOf(c) != -1)) { m--; mask &= 0x0d; } // **** check upper case letters (mask = 0x04) **** if ( (((mask & 0x04) == 4) ? true : false) && (upperCase.indexOf(c) != -1)) { m--; mask &= 0x0b; } // **** check special characters (mask = 0x08) **** if ( (((mask & 0x08) == 8) ? true : false) && (specialCharacters.indexOf(c) != -1)) { m--; mask &= 0x07; } } // **** check the password length **** if ((n < 6) && ((n + m) < 6)) m = 6 - n; // **** missing count **** return m; }

Simple and seems to work; at least it was accepted.

The code traverses the password. It stops traversing it if it matches the requirements (number of characters and types). I used a mask to check when a type of characters is encountered so we can skip the check on future loops.

We also check the number of characters at the end. The reason for this is to check for the case when a user has entered a long enough password but it may only contain one type of characters (e.g., “1111111111”). The user may also enter a password with all the required types of characters, but it may be short (e.g., “0aA!”).

If you are interested in getting the entire code for the solution you can find it in my GitHub repository.

Keep on learning, experimenting and having fun.

If you have comments or questions regarding this or any other post in this blog; please leave me a note below.

John

Please follow me on Twitter: **@john_canessa**

This post is based on an optional assignment for the last course I completed on Coursera. The original assignment was long and I made modifications and enhancements so it just got bigger. If you are interested in Machine Learning (ML) you will need some refreshing on linear algebra. If you have little experience with Python or Numpy you will need some practice. This post should provide some refreshing and practice.

I will go over the cells in my Jupyter notebook. Now and then I might provide output for a cell. If you are interested, my suggestion is to visit GitHub and pull the notebook and experiment with it. Most of the operations and functions covered will be useful when you start working with logistic regression. That is one of the first topics in ML. With that out of the way, let’s dive into the notebook.

Cell #1 is used to display a line of text.

# **** print a message **** test = "Hello World !!!" print("test: " + test)

Cell #2 is similar to the previous one. In this case we will print a value. That works when not accompanied by some text as illustrated in the third line. Given that we initiate the print() statement with a string, we need to convert the double value in the variable val to a string.

# **** print a variable **** val = 1.2 print(val) print("val: " + str(val))

In the next cell #3 we import the math package because the math.exp() method will be used in the implementation of the basic_sigmoid() function. To learn more about the sigmoid function you may do it here. Sigmoid functions are used in ML due to the fact that the output is always in the range <0.0, 1.0>. We will learn more about it when we deal with logistic regression in a future post.

Once we define the basic_sigmoid() function we test it. You could try a value of 0 and the result should be 0.5.

import math # **** basic sigmoid function definition **** def basic_sigmoid(x): s = 1 / (1 + math.exp(-x)) return s # **** test the sigmoid function **** val = basic_sigmoid(3) #val = basic_sigmoid(0) print("val: " + str(val))

As you know, most algorithms in ML require a considerable amount of data. A typical way of presenting data to ML models is via arrays with one, or more dimensions. Let’s try our function with an array as illustrated in cell #4:

# **** define a vector (list) **** x = [1, 2, 3, 4] #x = np.array([1, 2, 3, 4]) #x = np.array([[1, 2, 3, 4]) print("x: " + str(x)) # **** try the basic_sigmoid function **** val = basic_sigmoid(x)

The output of this cell follows:

x: [1, 2, 3, 4] --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-4-dcba755c75e9> in <module> 4 5 # **** try the basic_sigmoid function **** ----> 6 val = basic_sigmoid(x) <ipython-input-3-897a4c6e5ae2> in basic_sigmoid(x) 3 # **** basic sigmoid function definition **** 4 def basic_sigmoid(x): ----> 5 s = 1 / (1 + math.exp(-x)) 6 return s 7 TypeError: bad operand type for unary -: 'list'

As you can see things did not go well. We wrote the function to support individual values. We need a new version that will support vectors. For an example on how Numpy handle vectors we can do something like:

# **** now let's try it with Numpy **** import numpy as np # **** define a numpy vector (array) **** x = np.array([1, 2, 3, 4]) #x = np.array([[1, 2, 3, 4]]) print("x: " + str(x)) print("x.shape: " + str(x.shape)) print() # **** compute the exponential of each element in the array **** val = np.exp(x) print("val: " + str(val)) print("val.shape: " + str(val.shape))

We first need to import Numpy. We define an array and we compute an array in which all entries represent the exponential of the first set of values. If you get a chance to run this cell you will be able to see the shapes of the input and output vectors. You can check more about shape() and reshape() in a previous post.

In cell #6 we will add a constant to each element in vector x. We do it with and without saving the result to a new vector. Something to think about is how four entries in the array can add with a single value. This is called broadcasting. You can read more about it here.

# **** add a constant to each element in the array **** print("x + 3: " + str(x + 3)) # **** save the results in an array **** val = x + 3 print("val: " + str(val)) print("val.shape: " + str(val.shape))

If you are at any point interested on the definition or arguments for a numpy call / method, you can go to an empty cell in the notebook, type in the call followed by a question mark. A description will appear. You can expand it to a separate window in your web browser. This is illustrated in cell #7.

# **** get quick access to documentation on a numpy function **** np.exp?

In this case, we are interested in learning more about the np.exp() function / method we used in a previous cell.

In cell #8 we define the sigmoid() function. In this case the function is written to support and use Numpy arrays. After the definition we run a test.

# **** sigmoid function using Numpy definition **** def sigmoid(x): s = 1 / (1 + np.exp(-x)) return s # **** try the sigmoid function **** print("x: " + str(x)) val = sigmoid(x) print("sigmoid(x): " + str(val)) print("val.shape: " + str(val.shape))

Looking into the future, cell #9 defines and tests the sigmoid_derivative() function. When we deal with logistic regression we will make use of this or a very similar function.

# **** sigmoid_derivative function definition **** def sigmoid_derivative(x): s = sigmoid(x) ds = s * (1 - s) return ds # **** try the sigmoid_derivative function **** x = np.array([1, 2, 3, 4]) print("x: " + str(x)) val = sigmoid_derivative(x) print("sigmoid_derivative(x): " + str(val)) print("val.shape: " + str(val.shape))

In cell #10 we are going to perform some vector operations similar to what we do with actual images. Images displayed on a computer device typically use three values to describe each pixel. One value is for RED, the second for GREEN and the last for BLUE. In this case all the RED components are in the first [3, 3] matrix. The GREEN are in the second matrix, and the BLUE are on the third one. In this case the image would take 3 x 3 == 9 pixels. Not even a single character uses 9 pixels. Not with standing vector graphics, a typical matrix of 7 x 9 == 63 pixels would be used to describe a single numerical digit. The idea is to illustrate how we would deal with an actual image. In ML competitions images are typically 64 x 64 x 3 or larger.

# **** define an extremely small RGB image **** #image = np.random.randn(3, 3, 2) image = np.array([[[ 0.67826139, 0.29380381], [ 0.90714982, 0.52835647], [ 0.4215251 , 0.45017551]], [[ 0.92814219, 0.96677647], [ 0.85304703, 0.52351845], [ 0.19981397, 0.27417313]], [[ 0.60659855, 0.00533165], [ 0.10820313, 0.49978937], [ 0.34144279, 0.94630077]]]) print("image:\n" + str(image)) print() print("image.shape: " + str(image.shape)) print("image.shape[0]: " + str(image.shape[0])) print("image.shape[1]: " + str(image.shape[1])) print("image.shape[2]: " + str(image.shape[2]))

In cell #11 we define the image2vector() function. We pass in an image and the function returns a columnar vector holding all the pixels for the RED, GREEN and BLUE component for all pixels. In a future post we will see how this concept is used with actual images in order to create a model that would differentiate pictures containing cats or dogs.

# **** image2vector function definition **** def image2vector(image): v = image.reshape(image.shape[0] * image.shape[1] * image.shape[2], 1) return v # **** try the image2vector function **** x = image2vector(image) print("image2vector(image):\n" + str(x)) print("x.shape: " + str(x.shape))

In cell #12 de define and implement a function used to normalize rows. The reason for this is that when you are working in a ML project, we do not want to have a feature that is given more weight than another based on the units. For example, the weight (no pun intended) of an adult in pounds, depending on gender may be around 160 pounds. The height of an average person may be around 65 inches. In this case weight is about 2.5 larger than the height. To avoid giving more emphasis to weight, we can normalize both.

# **** normalizeRows function definition **** def normalizeRows(x): # **** compute the norm per row **** x_norm = np.linalg.norm(x, ord = 2, axis = 1, keepdims = True) print("x_norm:\n" + str(x_norm)) print("x_norm.shape: " + str(x_norm.shape)) x_norm_0 = np.sqrt((x ** 2).sum(axis = 1, keepdims = True)) print("x_norm_0:\n" + str(x_norm_0)) x = x / x_norm return x # **** define an array to normalize **** x = np.array([[0, 3, 4], [1, 6, 4]]) print("x.shape: " + str(x.shape)) print() val = normalizeRows(x) print() print("normalizeRows(x):\n" + str(val))

Moving on, on cell #13 we define a softmax() function. To read more about it you can find the subject in Wikipedia. You know the routine. We define the function, then we define a matrix for testing and we generate results.

# **** define softmax function **** def softmax(x): # **** apply exp() element-wise to x **** x_exp = np.exp(x) # print("x_exp.shape: " + str(x_exp.shape)) # **** create a vector x_sum that sums each row of x_exp **** x_sum = np.sum(x_exp, axis = 1, keepdims = True) # print("x_sum.shape: " + str(x_sum.shape)) # **** compute the softmax(x) by dividing x_exp by x_sum (broadcasting in action) **** s = x_exp / x_sum return s # **** define an array to compute the softmax **** x = np.array([[9, 2, 5, 0, 0], [7, 5, 0, 0, 0]]) val = softmax(x) print() print("softmax(x):\n" + str(val)) print("val.shape: " + str(val.shape))

Note that I left some print() statements inside the function. I used them to better understand how the function operates. Do not forget how easy it is to get help on Numpy functions (e.g., np.sum?)

Now we will move on to implement some vector operations using loops. Loops in any programming language take time to execute. The idea is to demonstrate how long a set of operations take when using loops and compare to the same operations when implemented with a single method using SIMD.

# **** to time operations **** import time # **** arrays to use for testing vectorization **** #x1 = np.random.randn(15000) #x2 = np.random.randn(15000) x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0] x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0] print("type(x1): " + str(type(x1))) print("len(x1): " + str(len(x1))) #x1 = np.array([9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]) #x2 = np.array([9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]) #print("type(x1): " + str(type(x1))) #print("x1.shape: " + str(x1.shape)) # **** clasic dot product of vectors **** tic = time.process_time_ns() dot = 0 for i in range(len(x1)): dot += x1[i] * x2[i] toc = time.process_time_ns() print(" dot: " + str(dot)) print("time: " + str(toc - tic) + " ns") print() # **** classic outer product of vectors **** tic = time.process_time_ns() #print("len(x1): " + str(len(x1))) #print("len(x2): " + str(len(x2))) outer = np.zeros((len(x1), len(x2))) #print("outer.shape: " + str(outer.shape)) for i in range(len(x1)): for j in range(len(x2)): outer[i, j] = x1[i] * x2[j] toc = time.process_time_ns() print("outer:\n" + str(outer)) print("time: " + str(toc - tic) + " ns") print() # **** classic elementwise multiplication **** tic = time.process_time_ns() mul = np.zeros(len(x1)) for i in range(len(x1)): mul[i] = x1[i] * x2[i] toc = time.process_time_ns() print("mul:\n" + str(mul)) print("time: " + str(toc - tic) + " ns") print() # **** classic general dot product implementation **** W = np.random.randn(3, len(x1)) print("W:\n" + str(W)) tic = time.process_time_ns() gdot = np.zeros(W.shape[0]) for i in range(W.shape[0]): for j in range(len(x1)): gdot[i] += W[i, j] * x1[j] toc = time.process_time_ns() print("gdot:\n" + str(gdot)) print("time: " + str(toc - tic) + " ns") print()

You can see that I tried defining some rather large arrays with random values. The issue is that when you look at some of the results it is quite difficult to check them and to follow. For this reason smaller arrays with specified values are used. The drawback is that timing is useless. If you run the code you will understand what I am saying. For time comparisons you should just use large arrays.

# **** vectorized dot product **** tic = time.process_time_ns() dot = np.dot(x1, x2) toc = time.process_time_ns() print(" dot: " + str(dot)) print("time: " + str(toc - tic) + " ns") print() # **** vectorized outer product of vectors **** tic = time.process_time_ns() outer = np.outer(x1, x2) toc = time.process_time_ns() print("outer:\n" + str(outer)) print("time: " + str(toc - tic) + " ns") print() # **** vectorized elementwise multiplication **** tic = time.process_time_ns() mul = np.multiply(x1, x2) toc = time.process_time_ns() print("mul:\n" + str(mul)) print("time: " + str(toc - tic) + " ns") print() # **** vectorized general dot product **** tic = time.process_time_ns() gdot = np.dot(W, x1) toc = time.process_time_ns() print("gdot:\n" + str(gdot)) print("time: " + str(toc - tic) + " ns") print()

This last cell shows the same operations, this time in vectorized form using SIMD. If you have a chance to run the code you will be able to verify that both produce the same results. To learn more about dot product, outer product and / or vector multiplication please follow one of the links.

In cell #16 we define a loss function and test it with some random values.

# **** define L1 loss function **** def L1(yhat, y): loss = np.sum(np.abs(y - yhat)) return loss # **** test the L1 loss function *** yhat = np.array([.9, 0.2, 0.1, .4, .9]) y = np.array([1, 0, 0, 1, 1]) val = L1(yhat, y) print("L1: " + str(val))

Finally in cell #17 de define and test a second loss function. Loss functions will become clear in a future post.

# **** define the L2 loss function **** def L2(yhat, y): # loss = np.sum(np.square(y - yhat)) loss = np.sum(np.dot(y - yhat, y - yhat)) return loss # *** test the L2 loss function **** yhat = np.array([.9, 0.2, 0.1, .4, .9]) y = np.array([1, 0, 0, 1, 1]) val = L2(yhat, y) print("L2: " + str(val))

Note that the loss can be computed in different ways. In our code we have two approaches. If you have a chance to experiment with the code, you will find out, at least it happened on my machine, that the results are very similar, yet one implementation seems to be more accurate that the other. In addition, from a software engineering point of view, one line of code is easier to understand than the other. Let me know your thoughts.

I have posted this Jupyter notebook on GitHub.

As usual, if you have comments or questions regarding this or any other post in this blog, please leave me a note bellow.

Keep on learning, experimenting and enjoying;

John

Please follow me on Twitter: **@john_canessa**

I am also a morning person. In general I wake up before 05:00 AM. As a fallback I have a 7-day alarm set in my phone for exactly 05:00 AM. Today I woke up and decided it was time to get up. Reached to my phone and it showed 04:59 AM. A second or two later the alarm went off. I just killed it and started my daily routine. As I was walking to the bathroom I smiled as I recalled the opening scene from Red.

For work, I am developing a reduced set of API calls in C++ for the storage server. I figured out that, given that streams are implemented in different ways and use slightly different syntax on different languages, that I would generate this post.

A stream is a sequence of bytes which can be finite or not. For example a stream used to read the contents of a file in disk is finite, while a stream reading the weather conditions (i.e., temperature, humidity, pressure) from a weather station constitutes an infinite stream. To read more about this topic you may look here.

In general we have iostream for input / output that handles cin, cout and cerr, fstream for files that handles ifstream and ofstream and stringstream for strings. Of course these are just the basic types of streams in C++.

Let’s start with writing a string to the output stream cout. We could have removed the ‘\n’ from the string and append a << endl. We could also have used a string and write the string. All three approaches are valid and seem to work well.

The next statements write one byte / character per call. Note how the exclamation points are written to the output stream one byte at a time.

Finally we write a greeting in two steps and flush the output stream. I am running this code on Windows 10. As you can see if you execute the code, the words appear as soon as they are written to the console. There is no need to flush the output stream.

// **** writing to the cout I/O stream **** const string greeting = "Hello World!!!"; cout << "Hello World!!!\n"; cout << "Hello world!!!" << endl; cout << greeting << endl << endl; // **** writting to cout one byte / character at a time **** cout.put('J'); cout.put('o'); cout.put('h'); cout.put('n'); cout.put('!').put('!').put('!'); cout.put('\n'); cout << endl; // **** flusing the stream buffer **** cout << "Hello"; Sleep(1000); cout << " John !!!\n"; Sleep(1000); cout << flush; Sleep(1000);

We then prompt the user to enter their first name. We write the input stream to the firstName variable and then display it. As you should be able to tell only the first set of contiguous characters are read. Try experimenting by entering trailing spaces and perhaps the full name.

After the first name is extracted we clear the input stream. This is done to prevent reading left over characters that might have been entered by accident (e.g., user entered her full name).

The code the prompts for and reads the full name of the user which will be composed of multiple sets of continuous characters (e.g., James T. Kirk). The name is read as an entire line and displayed.

Finally we enter a loop prompting for full names which are read entirely per line.

string firstName; // **** just read the first name, e.g., John Canessa **** cout << "please enter your first name: "; cin >> firstName; cout << "your name is ==>" << firstName << "<==\n"; // **** clear cin (e.g., discard " Canessa" **** cin.ignore(INT_MAX, '\n'); // **** reads the entire full name, e.g., John Canessa **** string fullName; cout << "please enter your full name: "; getline(cin, fullName); cout << "your full name is ==>" << fullName << "<==\n"; // **** now in a loop **** for (int i = 0; i < 3; i++) { cout << "please enter your full name: "; getline(cin, fullName); cout << "your full name is ==>" << fullName << "<==\n"; }

The next pass was an attempt to read one character at a time. This did not go well. I tried it on Linux and Windows. The code follows:

// **** loop reading from cin one character at a time (ctrl-z to end) **** char c; cout << "please enter some text: "; cin >> c; cout << "\nblocked until \\n was entered\n"; do { cout << c; cin >> c; } while (!cin.eof()); cout << endl;

The results on Linux:

please enter some text: Hello World^Z Hello World all done !!!

And the results on Windows:

C:\Users\John\Documents\Visual Studio 2017\Projects\tidbits4\Debug>tidbits4 please enter some text: hello world^Z^Z blocked until \n was entered helloworld ^Z

Looked up what control-z does and it seems to return 0x1A. The logic of the code seems to work but it does not. The input stream is buffered until a ‘\n’ is found when the user presses the <Enter> key. The code follows:

// **** now in C **** char c; printf("please enter some text: "); for (bool done = false; !done; ) { c = fgetc(stdin); if (c == 0x1A) { done = true; continue; } printf("%c", c); } printf("\nall done !!!\n");

Now we declare the Person class and illustrate how a method that converts the members to a string is implemented in two different ways. The idea is to show the string stream. Not sure if one approach is better than the other. I personally lean for the toString() method.

// **** **** Person person = Person("John", "C", "Canessa", 21); cout << "person: " << person.toString() << endl; cout << endl; cout << "person: " << person.tostring() << endl;

The header for the Person class:

#pragma once #include "pch.h" using namespace std; class Person { // **** members **** private: string firstName; string middleName; string lastName; int age; public: // **** constructor **** Person(string firstName, string middleName, string lastName, int age); // **** destructor **** ~Person(); // **** to string **** string toString(); // **** to string using sstream **** string tostring(); };

The implementation for the Person class:

#include "pch.h" #include "Person.h" using namespace std; // **** constructor **** Person::Person(string firstName, string middleName, string lastName, int age) { this->firstName = firstName; this->middleName = middleName; this->lastName = lastName; this->age = age; } // **** destructor **** Person::~Person() { } // **** to string **** string Person::toString() { return firstName + " " + middleName + " " + lastName + " " + to_string(age); } // **** to string using a string stream **** string Person::tostring() { stringstream stm; stm << firstName << " " << middleName << " " << lastName << " " << age; return stm.str(); }

Finally we have some code that reads a set of grades from a file. The grades are read via a stream. The grades are then written to a string stream to be processed one by one. The loop reads a grade at a time and places it into the grade variable. The grade is used to update to total.

Once the loop ends, we compute the average grade and display the result:

// **** values in file: 100 90 80 70 60 (all grades on a single line) **** const string gradesFileName = "c:\\temp\\grades.txt"; ifstream gradeFile; stringstream grades; int grade, total = 0; // **** open the input file stream **** gradeFile.open(gradesFileName); // **** read the grades into line **** string line; getline(gradeFile, line); cout << "line ==>" << line << "<==" << endl; // **** close the input file stream **** gradeFile.close(); // **** put the grades into the string stream **** grades << line; // **** process the grades **** int i; for (i = 0; true; ) { // **** check if the string stream is empty **** if (grades.rdbuf()->in_avail() == 0) break; // **** **** i++; // **** extract the next grade and process it **** grades >> grade; cout << "grade: " << grade << endl; total += grade; } cout << "i: " << i << endl; // **** compute and display the average **** double average; average = total / i; cout << "average: " << average << endl;

While I was writing this post I decided to load the Boots library into Visual Studio. Will get that completed for the next C++ post.

I pushed the code to my GitHub repository.

If you take a look at the code, you will see that different sections are bracketed by #ifdef _DEBUG and #endif. I use #ifdef CAKE to comment things out and _DBEUG to comment them back in.

If you have comments on this or any other post please leave me a note below.

Keep on learning;

John

Please follow me on Twitter: **@john_canessa**

I will cover the cells in a Jupyter notebook. The notebook is in my GitHub repository. Note that I talk about the results but, if interested you will have to navigate to GitHub and run or download the notebook.

For a more complete description of broadcasting you can find it here.

Now let’s turn to the Jupyter notebook. We will cover cell by cell. I will be using an example from a course I took on Coursera.

In cell #1 we put our only import (Numpy) and then declare a numpy array which is a [3, 4] matrix. Each column represents one of four foods in the order: apples, beef, eggs and potatoes. For each food, in the vertical axis we have values for carbohydrates, protein and fat for that particular food. For example, the first column which represents apples, we have in the first row 56.0 grams of carbohydrates, 1.2 grams of protein and 1.8 grams of fat. In the class we were told that the values are for 100 grams samples. If you take a closer look at the second column which is for beef, 100 grams sample contains 104 grams of protein and 135 grams of fat. That adds to 239 grams. I took a quick look on the web and it seems that, the idea for the labels in this example is just to provide a context. The values seem to be off.

import numpy as np # **** on the x-axis: apples, beef, eggs and potatoes # on the y-axis: Carbs, Protein and Fat **** A = np.array([[56.0, 0.0, 4.4, 68.0], [1.2, 104.0, 52.0, 8.0], [1.8, 135.0, 99.0, 0.9]]) print("A: " + str(A)) print("A.shape: " + str(A.shape))

In cell #2 we sum all the values in the matrix. As expected this is not what we need or want. It just produces the sum of all values which I would have expected to be 400 grams. In this case we get about 530.3 grams.

# **** sums all values in A **** cal = A.sum() print("cal: " + str(cal)) print("cal.shape: " + str(cal.shape))

In cell #3 we specify the direction for the sum operation. We would like to get the sums per column which represent each food. Once we have those values we should be able to get the percentages per row which is what we are after.

# **** sum all colums in A **** cal = A.sum(axis=0) print("cal: " + str(cal)) print("cal.shape: " + str(cal.shape))

Cell #4 shows how we can get a set of percentages. We divided A which is a [3, 4] matrix by val which is a [1, 4] vector. The results in the percentage matrix [3, 4] is what we wanted but we should have used a val matrix with a shape of [3, 4]. Numpy figures out what was needed and broadcasted val so the operation would make sense and could be carries out. That is cool.

# **** compute percentages (every column in A was divided by every column in cal) **** percentage = (A / cal) * 100.0 print("percentage:\n" + str(percentage)) print("percentage.shape: " + str(percentage.shape))

Cell #5 makes the operation somewhat more explicit, but note that val is still a [1, 4] vector.

# **** each column in A was divided by the corresponding column in cal ****

percentage = (A / cal.reshape(1, 4)) * 100.0

print(“percentage:\n” + str(percentage))

print(“percentage.shape: ” + str(percentage.shape))

Let’s now take a look at a different example. In cell # 6 we define a vector B with four values. We then add 100 to the vector. Numpy knew what we wanted and broadcasted 100 to a vector [1, 4] with all values set to 100. It then added them and returned the expected results. Of course, if you wanted to just add 100 to the first element in B, you were out of luck.

# **** a different example **** B = np.array([1, 2, 3, 4]) print("B:\n" + str(B)) print("B.shape: " + str(B.shape)) print() B += 100 print("B:\n" + str(B)) print("B.shape: " + str(B.shape))

In cell #7 we declare B using two brackets [[ on the left and two on the right ]]. This syntax is used to declare a vector array with well defined shape; in this case [1, 4]. The result is a row vector.

# ***** **** B = np.array([[1 ,2, 3, 4]]) print("B:\n" + str(B)) print("B.shape: " + str(B.shape)) B += 100 print("B:\n" + str(B)) print("B.shape: " + str(B.shape))

In cell #8 declare B using a different set of brackets. We declare a columnar vector with the same values. The shape is [4, 1] We then add 100 and like in the previous cell, each value in the vector of a different shape, ends up increased by 100.

# **** **** B = np.array([[1], [2], [3], [4]]) print("B:\n" + str(B)) print("B.shape: " + str(B.shape)) print() B += 100 print("B:\n" + str(B)) print("B.shape: " + str(B.shape))

Let’s now look at a new example. This time we are using a two dimensional matrix. This is illustrated in cell #9. C is declared as a [2, 3] array and D as a [1, 3].

# **** yet one more example **** C = np.array([[1, 2, 3], [4, 5, 6]]) print("C:\n" + str(C)) print("C.shape: " + str(C.shape)) print() D = np.array([[100, 200, 300]]) print("D:\n" + str(D)) print("D.shape: " + str(D.shape))

In cell #10 we sum C and D returning the results in E. Given the results, it is clear that Numpy broadcasted D to a [2, 3] array in order to produce the results in E.

# **** C[2,3] + D[1,3] broadcasted to: C[2,3] + D[2,3] = E[2,3] E = C + D print("E:\n" + str(E)) print("E.shape: " + str(E.shape))

OK, one more example and we are done. We declare a [2, 3] array and populate it with ascending integers starting at 1. This is shown in cell #11.

# **** one last example **** F = np.array([[1, 2, 3], [4, 5, 6]]) print("F:\n" + str(F)) print("F.shape: " + str(F.shape))

In cell #12 we declare a columnar vector with dimensions [2, 1].

G = np.array([[100], [200]]) print("G:\n" + str(G)) print("G.shape" + str(G.shape))

In cell #13 we add F and G to produce H. H ends up being a [2, 3] array. For this to work, Numpy broadcasted G to a [2,3] array and then add the elements.

# **** F[2,3] + G[2,1] broadcasted to: F[2,3] + G[2,3] = H[2,3]**** H = F + G print("H:\n" + str(H)) print("H.shape: " + str(H.shape))

If you wish to take a look at the notebook please click here.

Hope you enjoy it. We will eventually get to a linear regression example for a classifier using images. Perhaps in two or three more posts in this category.

Enjoy and keep on learning.

John

Please follow me on Twitter: **@john_canessa**

The advice here presented, with my additions, came from a course I took on Machine Learning (ML) by Andrew Ng. Hope you find it useful as I did.

I will go over all the cells in a Python notebook. You can find it in my GitHub repository.

In cell #1 I just import Numpy. All operations are performed using Numpy arrays. They are very useful when working in ML using Python.

import numpy as np

In cell #2 we create a numpy array with 5 entries / cells. The array is populated with pseudo random numbers. The array is named ‘a’. There are two things to note in the output for this cell. **First** is the fact that the values are bracketed by a single set of [] brackets. **Second** is the shape of the array. We only have a single dimension. We might have expected [5, 1] or [1, 5]. What we declared was a rank one array.

# **** create a numpy array with 5 values **** a = np.random.rand(5) print("a:\n", str(a)) # **** rank one array (not a row or a column vector) *** print("a.shape: " + str(a.shape))

In cell #3 we compute the transpose of a. Again, the results do not seem to be what we might be expecting. In general a transpose operation swaps the values in the rows into the columns. It seems that the operation made no changes in the array.

# **** create a numpy array with 5 values **** print("a.T:\n" + str(a.T)) at = a.T; print("at.shape: " + str(at.shape))

In cell #4 we display the results of a.T and it seems that we just got a single value.

# **** should be a vector (returns a number)**** print("np.dot(a, at): " + str(np.dot(a, a.T)))

Cell #5 declares an array named ‘b’ and initializes it to a set of five pseudo random numbers. Note that instead of declare a one dimensional array as we did before, we are now explicitly declaring a [5, 1] array. The array, as expected, ended up having one column with five values. The shape is exactly what we were looking for.

# **** instead use: **** b = np.random.randn(5, 1) print("b:\n" + str(b)) print("b.shape: " + str(b.shape))

In cell #6 we generate the transpose of ‘b’ and assign it to ‘bt’. From a row vector we now have a column vector. Life is good.

# **** **** bt = b.T print("bt: ", bt) print("(bt).shape: " + str(bt.shape))

In the next cell, we perform the dot product of ‘b’ [5, 1] and ‘bt’ [1, 5]. As expected the result is a [5, 5] matrix with the proper values.

# **** **** print("np.dot(b, bt):\n" + str(np.dot(b, bt)))

In cell #8 we explicitly declare a [5, 1] column vector and populate it with pseudo random numbers. The shape matches. Note that when the vector is displayed the values are enclosed by ‘[[‘ and ‘]]’. This was not the case when we declared the rank one array.

# **** column vector **** c = np.random.randn(5,1) print("c:\n" + str(c)) print("c.shape: " + str(c.shape)) assert(c.shape == (5,1))

In cell #9 we declare a row vector with five random entries. The shape is as expected. We also added a check in the form of an assert. If the shape of the resulting array does not match our expectation the notebook would thrown an exception. Since all is well, no exception is thrown.

# **** row vector **** c = np.random.randn(1, 5) print("c:\n" + str(c)) print("c.shape: " + str(c.shape)) assert(c.shape == (1,5))

To contrast, cell # 10 shows another example of declaring an array using a single dimension.

# **** do NOT use **** c = np.random.rand(5) print("c:\n" + str(c)) print("c.shape: " + str(c.shape))

We can now check if we got what we desired, but not explicitly asked for it. By checking with an assert, we get an exception. Using this approach will check that we are getting what we expect.

# **** to make sure c has the expected shape *****

assert(c.shape == (5,1))

Finally in cell # 12 we reshape a new array that we declared with a single dimension and six elements. We then reshape the array as a row vector and then as a column vector.

# **** you can also reshape an array **** c = np.random.rand(6) print("c.shape: " + str(c.shape)) print("c: " + str(c)) print() c = c.reshape(1, 6) print("c.shape: " + str(c.shape)) print("c: " + str(c)) print() c = c.reshape(6, 1) print("c.shape: " + str(c.shape)) print("c:\n" + str(c))

The takeaway of this exercise is to always declare vectors the way we need them to be. When in doubt, we can always check for the expected shape and if needed reshape them.

I recall when I was in high school and dealt with vector operations in linear algebra. Given that we are using a programming language, we need to get used to the idiosyncrasies and be able to direct our programs to do what we need to accomplish.

I uploaded my Jupyter notebook to my GitHub repository. You can find it here.

Experiment with the notebook and make sure you clearly understand what needs to be done. By developing good habits from the start, you will not have to change to avoid bad habits.

Keep on learning;

John

Please follow me on Twitter: **@john_canessa**

Before I get to my notes on vectorization, yesterday, I was supposed to make some pizza from scratch for lunch. My wife and I came back from a walk at the Mall of America (MOA), prepared some flavored (mango) green tea, turned on the fireplace and sat down to sip tea and chat. Suddenly it was close to noon and no lunch in the horizon. It takes me about two hours to get from high gluten flour and several other ingredients to make two hot pizzas ready to be devoured. The dough takes about an hour to rise.

OK, decided to go shrimp pasta. The pasta came from a bag. Homemade pasta takes about an hour to prepare. My wife chopped some onions, put them in a pan with some olive oil, chopped some garlic and joined the onions with some spices and salt. When the onions started to caramelize, in went a bag of frozen shrimp and about a cup of white wine.

Meanwhile, on separate burner water, oil and salt was starting to boil in a pan. As soon as it started to boil, in went the pasta. In five minutes we were ready to eat our lunch. Besides getting the ingredients from the pantry and freezer, my wife did all the cooking. My help came in the form of an appetizer. I pulled some salami and sliced it. Got a bag with three different types of cheese, some crackers, and poured some white wine in a couple glasses. We had in the fridge an open bottle left from last weekend.

While we were fixing lunch and enjoying our appetizer, I noticed that the crackers came from England. The set of cheese came from Spain. The salami and the first bottle of wine were products of Italy. It is amazing how the economy works. It seems that most countries in the world import and export different products so we can all share them. During the lunch preparation we had to open a second bottle. I could not find the same type of wine so picked up a French one that I placed in the fridge last week as soon as we open the one that we started with.

OK, it is about 07:00 AM today, I have not finished this post and I am getting hungry for breakfast. For the last couple years, my wife and I just indulge on the same breakfast every day and a relatively late lunch. We completely skip dinner. Earlier this morning, I did some experimentation with the Jupyter notebook so I have all things ready to finish this post. Will break for breakfast and after a warm shower will get in a second block. When done my wife and I will go to the MOA for a walk. I believe my son made some lasagna and will stop for lunch after our walk…

… I am back. While my wife gets ready I will try to finish this post. This post contains a newer post based on this one. I am trying to improve on the presentation of the code for my blog. The associated Jupyter notebook may be found in Github under: JohnCanessa/Numpy-Vectorization

At the core is the feature called SIMD which stands for Single Instruction Multiple Data. There are implementations for CPUs (Central Processing Unit) and GPUs (Graphics Processing Unit). Most modern computers support SIMD.

An example of a SIMD function is the dot product implementation in Numpy. Following is a modified example using the Jupyter Notebook. A few months ago I would invoke the notebook directly from the command line. After the last Anaconda update, a set of tools that include the notebook have been collected and presented under Anaconda Navigator. I opened a Jupyter Notebook and named it vectorization.

The first cell follows:

# **** import libraries we will need **** import numpy as np import time # **** create, initialize with specific values, and print the contents of a numpy array **** a = np.array([1, 2, 3, 4]) print(a) # **** create, initialize with random values, and print the contents of a numpy array **** b = np.random.rand(4) print(b)

We need to import the Numpy and time libraries. Numpy will be used to select and execute a SIMD instruction and time to time it and later compare it against the same operation using a loop. An array is also defined. This is done just to see how to define and populate a small array using Numpy. The results cell follows:

[1 2 3 4] [0.81080152 0.43937166 0.73893081 0.3828071 ]

Now let’s declare a couple numpy arrays. They will hold 1000000 entries each and will be initialized with random numbers. We will then display the shape of the arrays.

# **** make the results repeat **** np.random.seed(123) # **** declare 2 large arrays holding random numbers **** a = np.random.rand(1000000) b = np.random.rand(1000000) # **** display their shape **** print("a.shape: " + str(a.shape)) print("b.shape: " + str(b.shape)) print() # ***** print the first 5 entries of array a **** for i in range(5): print("a: " + str(a[i])) print() # **** print the first 5 entries of array b **** for i in range (5): print("b: " + str(b[i]))

The output follows:

a.shape: (1000000,) b.shape: (1000000,) a: 0.6964691855978616 a: 0.28613933495037946 a: 0.2268514535642031 a: 0.5513147690828912 a: 0.7194689697855631 b: 0.7726979824010617 b: 0.46711505893558836 b: 0.019948827345841025 b: 0.9234407522220117 b: 0.179140987510242

The results illustrate that we have been successful at creating the two arrays. Each array contains a set of 1,000,000 random numbers in the range [0 : 1]

Let’s first try and time multiplying the two arrays in a loop. The Python code follows:

# **** initialize c **** c = 0; # **** populate c with the element multiplication of a and b **** tic = time.time() for i in range(1000000): c += a[i] * b[i] toc = time.time() # **** display the result of the element multiplication and the time the operation took **** print("c: " + str(c)) print("non-vectorized time: " + str(1000 * (toc - tic)) + " ms") timeNV = tic - toc

We write a loop which cycles between 0 and 999999. For each pass we just multiply the associated entries and add them to our accumulator c. Note that we initialize c to zero. This is done to make sure that the answers match. The result and the time the operation took are displayed.

c: 250123.2279705272 non-vectorized time: 1063.934564590454 ms

In this pass, the output took **about** a second (1000 ms) to execute. Not bad for multiplying and adding 100000 floating point numbers. Please note that if you rerun the cell, the results will not change. This is based on the fact that the random numbers in arrays a and b are the same in each pass. This is due to the fact that we seeded the random number generator. Seeding it is a common approach to be able to reproduce the same results on each pass. You may want to comment the seed line in order to experiment with different values.

The time it takes for the code to execute will vary even if you keep the same numbers. This is due to the fact that the operating system is running different unrelated tasks in the background. In general, the rule of thumb is to run an operation five times, discard the fastest and slowest values and average the residual three times.

Now let’s rewrite the code using a single numpy instruction.

# **** clear c **** c = 0 # **** populate c with the element multiplication of a and b (using SIMD) **** tic = time.time() c = np.dot(a,b) toc = time.time() # **** display the result of the element multiplication and the time the operation took **** print("c: " + str(c)) print("vectorized time: " + str(1000 * (toc - tic)) + " ms") timeV = tic - toc print() print("ratio: " + str(timeNV / timeV))

In this example we replaced the loop with a SIMD to calculate c.

c: 250123.22797053587 vectorized time: 46.99587821960449 ms ratio: 22.63889100271415

It seems like the vectorized implementation is faster than the non-vectorized. On my machine it seems that in average is about **21 times faster**.

Now we will declare a two-dimensional array A of 2,000 by 3,000 and will populate it with random values. Next we will declare a 1-dimensional array or vector v with 3,000 values and will initialize it with random variables. We display the shapes of the two arrays. The reason for this is to pay attention to the dimensions when operating with arrays. Mistakes in code can easily be corrected when all the dimensions are as expected.

# **** make the results repeat **** np.random.seed(123) # **** declare a 2-dimensional array and display its shape **** A = np.random.rand(2000, 3000) print("A.shape: " + str(A.shape)) print() # **** declare a vector and display its shape **** v = np.random.rand(3000) print("v.shape: " + str(v.shape))

The associated results follow:

A.shape: (2000, 3000) v.shape: (3000,)

You must multiply A[2000, 3000] * v[3000, 1] = u[2000, 1]. Multiplying v[3000, 1] * A[2000, 3000] if incorrect because the inner dimensions do not match. In this case, we must make sure the proper indices are used for the different arrays.

# **** declare a vector of 2,000 entries and initialize it with 0s **** u = np.zeros((2000)) # **** using 2 loops multiply them (time operation) **** tic = time.time() for i in range(2000): for j in range(3000): u[i] += A[i][j] * v[j] # **** sum all the values in u **** d = 0; for i in range (2000): d += u[i]; toc = time.time() # **** display the shape of u **** print("u.shape: " + str(u.shape)) # **** display the result d and the time the operation took **** print("d: " + str(d) + " time: " + str(1000 * (toc - tic)) + " ms") timeNV = tic - toc

The results follows:

u.shape: (2000,) d: 1476056.2967874217 time: 10245.437622070312 ms

In the previous cell we used a total of 3 loops to get the desired result. Now let’s perform the same operations using SIMD.

# **** initialize u with 0s **** u = np.zeros((2000)) # **** multiply the vector (time the operation) **** tic = time.time() u = np.dot(A, v) # **** sum all the values in u **** d = np.sum(u) toc = time.time() # **** display the shape of u **** print("u.shape: " + str(u.shape)) # **** display the result d and the time the operation took **** print("d: " + str(d) + " time: " + str(1000 * (toc - tic)) + " ms") timeV = tic - toc print() print("ratio: " + str(timeNV / timeV))

Note that the 3 loops are gone.

u.shape: (2000,) d: 1476056.2967874205 time: 77.99482345581055 ms ratio: 131.36048224886437

The code is easier to follow. Most important the second set of operations ran 91 times faster. When writing code in Python for machine learning, it is important to make it as efficient as possible. That way you will be able to cycle much faster from idea, implementation and results. Remember that we are dealing with big data. Learning algorithms tend to perform better when you use large data sets. If code using loops takes an hour to execute, the SIMD version will take less than a minute. Keep in mind that we are using CPUs, not GPUs. If you ran your Python code assisted by GPUs it will run even faster.

Now we will declare more arrays and will perform some more operations on them.

# **** x **** n_x = (64 ** 2) * 3 m = 100; x = np.random.rand(n_x, m) print("x.shape: " + str(x.shape))

In this case we assume we will process a single RBG (Red Green Blue) image. Each image contains 64 x 64 pixels. Given that a pixel has three values (RGB), an image can be represented with a vector of 64 * 64 * 3 = 12,288 values. In this example we will assume we have 100 images that we will process.

Each image sample of 12,288 values will be in a column and all 100 columns will be stacked horizontally one after the other as illustrated by the following diagram:

| | X = | x1, x2, ...xn| | |

w.shape: (12288, 1)

The results displayed on the last cell confirm that the representation in the code matches our intentions.

Lets now declare a vector w which will be populated with random numbers.

As we discussed previously if we wish to multiply A * w we have an issue because the inner dimensions do not match w[12288, 1] * X[12288, 100]. To address this we could use the transpose of w[1, 12288] * X[12288, 100] and obtain a result z[1, 100].

# **** product of w x **** z = np.dot(w.T, x) print("z.shape: " + str(z.shape)) print("z: " + str(z))

The results for the operation follows:

z.shape: (1, 100) z: [[3085.87233026 3046.39114632 3069.22529654 3037.68336421 3058.33295367 3048.61512356 3055.40568348 3067.77208362 3071.0288812 3040.64311368 3015.85687 3060.64383783 3051.68352211 3060.84942322 3070.33082398 3055.49115851 3051.13846139 3071.20439841 3057.68624992 3059.55023719 3098.15648599 3070.49008807 3077.06024315 3075.02658533 3050.35050974 3049.53968612 3097.61192622 3061.58056677 3037.14022029 3068.56109233 3054.7273441 3084.97632227 3048.57225448 3072.07366812 3033.4120418 3030.43838818 3093.95456072 3060.19723373 3063.7943765 3045.72939758 3048.2201513 3073.42435714 3062.0780248 3065.79595085 3067.03146764 3052.12090204 3048.51231941 3083.62884382 3081.75290352 3088.28332183 3064.12340584 3076.68007496 3053.58572801 3040.38150758 3054.20743184 3089.91710773 3032.23661951 3029.67286539 3047.58913599 3071.77737076 3066.56517946 3094.31748563 3045.95454068 3069.01497062 3077.03057345 3088.0165695 3065.47594911 3072.5629795 3045.42227672 3037.85169285 3101.0988355 3053.80059894 3059.63561919 3035.76101002 3041.47629427 3019.10585638 3041.1160453 3081.60127841 3041.2010014 3069.25565361 3087.3403284 3050.12226209 3057.99023889 3062.84120567 3072.84679536 3043.83350924 3043.44208909 3080.75478528 3051.54419506 3081.11957024 3051.50677247 3057.38595603 3056.20378222 3059.67877424 3077.85082191 3090.82622145 3061.30740209 3075.42885329 3044.04411145 3046.31623274]]

As you can see, the shape of the results is as expected.

For a final operation we will declare a random value b and will add it to each element in the z array. This operation can be performed as follows:

# **** add b **** b = np.random.rand() print("b: " + str(b)) z += b print("z: " + str(z))

The results follow:

b: 0.3964574632024054 z: [[3086.26878772 3046.78760378 3069.62175401 3038.07982168 3058.72941113 3049.01158103 3055.80214094 3068.16854108 3071.42533867 3041.03957114 3016.25332746 3061.0402953 3052.07997957 3061.24588068 3070.72728144 3055.88761598 3051.53491886 3071.60085588 3058.08270739 3059.94669465 3098.55294345 3070.88654553 3077.45670061 3075.42304279 3050.7469672 3049.93614359 3098.00838368 3061.97702424 3037.53667775 3068.95754979 3055.12380156 3085.37277974 3048.96871194 3072.47012558 3033.80849927 3030.83484565 3094.35101818 3060.59369119 3064.19083396 3046.12585504 3048.61660876 3073.8208146 3062.47448227 3066.19240832 3067.4279251 3052.5173595 3048.90877687 3084.02530128 3082.14936099 3088.67977929 3064.5198633 3077.07653242 3053.98218547 3040.77796504 3054.6038893 3090.31356519 3032.63307697 3030.06932285 3047.98559345 3072.17382823 3066.96163692 3094.71394309 3046.35099815 3069.41142809 3077.42703091 3088.41302696 3065.87240658 3072.95943696 3045.81873418 3038.24815031 3101.49529296 3054.19705641 3060.03207666 3036.15746748 3041.87275174 3019.50231384 3041.51250277 3081.99773588 3041.59745887 3069.65211107 3087.73678586 3050.51871955 3058.38669635 3063.23766314 3073.24325283 3044.2299667 3043.83854655 3081.15124274 3051.94065252 3081.5160277 3051.90322993 3057.78241349 3056.60023969 3060.07523171 3078.24727937 3091.22267891 3061.70385956 3075.82531076 3044.44056891 3046.7126902 ]]

It seems we added b to each value in z but we did not use a vector to represent b. What happened is that Python implies that what you want is to add b to each element in z and “broadcasts” b so each element in z gets updated z += b. To learn more about broadcasting you may read about the topic here.

The last example computed the following **pseudo code**:

**Z = transpose(w) * X + b**

The last line is not written in Python; it is **just pseudo code**. This represents the **forward** propagation for logistic regression. There is a **backward** propagation pass which I will cover in a future post. Logistic regression, once properly trained, allows us to determine if an image contains (1) or does not contain (0) an object. In the course I took, the idea is to determine if a random picture contains or does not contain a cat.

You can find the Jupyter notebook in Github: **JohnCanessa/Numpy-Vectorization**

My suggestion is to experiment with the code and make sure you understand what is happening. The ideas seem to be simple, but the implementation is open to encounter issues.

If you are interested in ML, I would recommend the Coursera course “Neural Networks and Deep Learning” by Andrew Ng.

Keep on learning;

John

Follow me on Twitter: **john_canessa**

If I am not able to get this done in a block (I practice Deep Work and use 2-hour blocks) I will take a peek at the code this afternoon.

To start we need a formal definition for the diameter of a tree given that the code should produce such number. I also want to note that the article called for a binary tree not a BST but the illustrations and animations are for BSTs. In practice I tend to find more practical uses for BSTs so I will develop a solution using them.

I did an Internet search on binary tree diameter and the top Google results pointed to GeeksforGeeks. The definition there states:

*“The diameter of a tree (sometimes called the width) is the number of nodes on the longest path between two end nodes.”*

Given this definition, the diameter of a BST appears to be the longest distance measured in edges, between nodes that have a common parent; that is, they are children from a single node. That said; we might need to locate such nodes and compute the individual distances to the root. That seems to be referred to as the height of each node.

Before going after a solution, the approach suggested by GeeksforGeeks provides the following definition:

*The diameter of a tree T is the largest of the following quantities:*

** the diameter of T’s left subtree*

** the diameter of T’s right subtree*

** the longest path between leaves that goes through the root of T (this can be computed from the heights of the subtrees of T)*

OK, so far so good. As usual I will attempt to implement a solution using Test Driven Development (TDD). I will see if I use some code from a previous solution for BSTs. That was I could reuse the BST implementation.

Let’s first take a look at a diagram of a BST that was generated by the solution.

We start on the left side of the tree and start going up. When we reach node 4 we have the choice of selecting 1 or 2. Given that we are looking at the largest diameter we choose 2 and continue our way up.

We then move to the right branch and start going up. When we reach node 9 we have the choice of choosing 1 or 2. We choose 2, increment by 1 and keep on going up.

When we reach the root we have 4 on the left branch and 5 on the right. The dimension is 4 + 5 = 9.

Let’s now look at a second diagram.

We follow a similar route from the bottom up and we end up with the value of 2 on the left side of node 47 and with 5 on the right side. The dimension is equal to 2 + 5 = 7. Note that we did not touch the root of the BST.

Let’s now take a look at the Node class.

package bst.canessa; /* * Binary tree node */ public class Node { // **** members **** int val; Node left; Node right; Node parent; int depth; // **** constructor **** public Node(int val) { this.val = val; this.depth = 0; left = right = null; } // ***** constructor **** public Node(int val, int depth) { this.val = val; this.depth = depth; left = right = null; } // **** get the value of this node **** public int getVal() { return this.val; } // **** get the depth of this node **** public int getDepth() { return this.depth; } // **** get the left child for this node **** public Node getLeftChild() { return this.left; } // **** set the left child on this node **** public void setLeftChild(Node left) { this.left = left; } // **** get the right child for this node **** public Node getRightChild() { return this.right; } // **** set the right child for this node **** public void setRightChild(Node right) { this.right = right; } // **** return the contents of the node as a string **** public String toString() { return "val: " + getVal(); } }

Please note that we will not use the parent or the depth in the algorithm used to find the dimension. Until recently, I have been rewriting code from scratch each time I implement a data structure. It seems like repeating the same task over and over (i.e., creating a Node class to be used in a tree) is a waste of time. We can just reuse classes. In this example I was going to write the solution code in a separate solution which I had already created; but I forgot and added to the one you will see shortly. Sorry about the clutter.

Now let’s look at the BinaryTree class:

package bst.canessa; import java.util.Deque; import java.util.LinkedList; /* * */ public class BinaryTree { private Node root; private int diameter; // **** constructor **** public BinaryTree() { this.root = null; } // **** **** public Node getRoot() { return this.root; } // **** insert node **** public void insert(int val) { int depth = 0; if (this.root == null) { this.root = new Node(val, depth); } else { insert(this.root, val, ++depth); } } // **** insert node **** private void insert(Node node, int val, int depth) { // **** insert to the left **** if (val < node.getVal()) { if (node.getLeftChild() == null) { node.setLeftChild(new Node(val, depth)); } else { insert(node.getLeftChild(), val, ++depth); } } // **** insert to the right **** else { if (node.getRightChild() == null) { node.setRightChild(new Node(val, depth)); } else { insert(node.getRightChild(), val, ++depth); } } } // **** in order traversal **** void inorder(Node node) { // **** base case (to end recursion) **** if (node == null) return; // **** recursive rule(s) **** inorder(node.getLeftChild()); System.out.print(node.getVal() + ":" + node.getDepth() + " "); inorder(node.getRightChild()); } // **** port order traversal **** void postorder(Node node) { // **** base case (to end recursion) **** if (node == null) return; // **** recursive rule(s) **** postorder(node.getLeftChild()); postorder(node.getRightChild()); System.out.print(node.getVal() + ":" + node.getDepth() + " "); } // **** max depth of BST **** public int maxDepth2(Node node) { // **** base case **** if (node == null) return 0; // **** recursive rule(s) **** int maxDepth = node.getDepth(); // System.out.println("maxDepth2 <<< maxDepth: " + maxDepth); int depthLeft = maxDepth2(node.getLeftChild()); // System.out.println("maxDepth2 <<< depthLeft: " + depthLeft); maxDepth = Math.max(maxDepth, depthLeft); int depthRight = maxDepth2(node.getRightChild()); // System.out.println("maxDepth2 <<< depthRight: " + depthRight); maxDepth = Math.max(maxDepth, depthRight); // **** return the max depth of the current BST **** return maxDepth; } // **** find distance from root to the node with the specified value **** public int distRootToVal(Node root, int val) { // **** base case (to end recursion) **** if (root == null) return -1; // **** initialize the distance **** int dist = -1; // **** check if value is in this node or in one of it's children **** if ((root.getVal() == val) || (dist = distRootToVal(root.getLeftChild(), val)) >= 0 || (dist = distRootToVal(root.getRightChild(), val)) >= 0) return dist + 1; // **** **** return dist; } // **** find lca (Lowest Common Ancestor) **** public Node lca(Node root, int val1, int val2, int rootDist) { // **** base case (to end recursion) **** if (root == null) return null; // **** **** if (root.getVal() == val1) return root; if (root.getVal() == val2) return root; // **** **** Node leftLCA = lca(root.getLeftChild(), val1, val2, rootDist + 1); Node rightLCA = lca(root.getRightChild(), val1, val2, rootDist + 1); // **** **** if ((leftLCA != null) && (rightLCA != null)) return root; // **** **** return (leftLCA != null) ? leftLCA : rightLCA; } // **** find the distance between the specified nodes **** public int distNodes(Node root, int dist1, int dist2, Node lca) { int dist = 0; // **** **** if ((dist1 != -1) && (dist2 != -1)) { dist = dist1 + dist2 - (2 * distRootToVal(root, lca.getVal())); } // **** **** return dist; } // **** find distance between nodes in a BST **** public int findDist(Node root, int val1, int val2) { int dist = -1; // **** step 1: find distance from root to val1 **** int dist1 = distRootToVal(root, val1); if (dist1 == -1) return -1; System.out.println("findDist <<< dist1: " + dist1); // **** step 2: find distance from root to val2 **** int dist2 = distRootToVal(root, val2); if (dist2 == -1) return -1; System.out.println("findDist <<< dist2: " + dist2); // **** step 3: find distance from root to LCA (Lowest Common Ancestor) **** Node lca = lca(root, val1, val2, 0); if (lca == null) return -1; System.out.println("findDist <<< lca = " + lca.toString()); // **** step 4: find the distance between nodes **** dist = distNodes(root, dist1, dist2, lca); System.out.println("findDist <<< dist: " + dist); // **** **** return dist; } // **** get the max depth of the tree (recursive method) **** public int maxDepth(Node root) { // **** base case **** if (root == null) { return -1; } // **** recursive rule(s) **** else { // System.out.println("maxDepth <<< val: " + root.getVal()); // **** compute the depth of each subtree **** int leftDepth = maxDepth(root.left); int rightDepth = maxDepth(root.right); // **** use the largest value **** // System.out.println("maxDepth <<< leftDepth: " + leftDepth + " rightDepth: " + rightDepth); if (leftDepth > rightDepth) return (leftDepth + 1); else return (rightDepth + 1); } } // **** depth first traversal (non-recursive) **** public void depthfirst(Node root) { // **** double ended queue **** Deque<Node> q = new LinkedList<Node>(); // **** add the tree root **** q.add(root); // **** **** while (!q.isEmpty()) { // **** pop the head mode **** Node n = q.pop(); // **** display the node **** System.out.print(n.getVal() + ":" + n.getDepth() + " "); // **** insert left child **** if (n.getLeftChild() != null) q.add(n.getLeftChild()); // **** insert right child **** if (n.getRightChild() != null) q.add(n.getRightChild()); } } // **** display the tree **** public void display(Node root) { } // **** compute and return the diameter of the specified tree **** public int diameter(Node root) { this.diameter = 0; findDiameter(root); return this.diameter; } // **** compute diameter of the specified tree (post order traversal) **** private int findDiameter(Node node) { // **** base case **** if (node == null) return 0; // **** recursive rule(s) **** int left = findDiameter(node.getLeftChild()); int right = findDiameter(node.getRightChild()); // **** update diameter **** if ((left + right) > this.diameter) this.diameter = left + right; // System.out.println( "findDiameter <<< val: " + node.getVal() + " left: " + left + " right: " + right + " diameter: " + this.diameter); // **** return the largest diameter plus one **** if (left > right) return 1 + left; else return 1 + right; } }

In this class I have added the diameter member. The reason for it is that in Java arguments are passed by value.

*Java is Pass by Value and Not Pass by Reference*

We need to update this variable and we could have declared an outside variable but using a member seems somewhat more elegant. We also can protect it as needed. Note that in our code we declare both class members private.

Of interest are the diameter() and findDiameter() methods. I started with different names but changed them to loosely match the C++ code in David’s post.

The diameter() method is public so it can be called by the solution code. It sets the diameter member and calls findDiameter() which is a recursive and private call. As you will see, I use other convenience functions, some of which are in this class and others in the solution.

Now let’s take a look at the clutter solution code:

package bst.canessa; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; /* * */ public class Solution { // **** generate a range within the specified value **** final static int MAX_LEN = 31; final static int MAX_VAL = 200; // was: 16; final static int SEED = 1; // was: 12; /* * Generate an array of random numbers in the specified range. */ static int[] arrayWithUniqueValues(int from, int to) throws Exception { System.out.println("arrayWithUniqueValues <<< from: " + from + " to: " + to); // **** determine the number of integers to generate **** int n = to - from + 1; System.out.println("arrayWithUniqueValues <<< n: " + n); // **** declare and populate an array with the numbers in ascending order **** int arr[] = new int[n]; for (int i = 0; i < n; i++) arr[i] = from + i; // **** declare and populate the random array **** int[] rarr = new int[n]; int x = n; // SecureRandom srand = new SecureRandom(); Random rand = new Random(SEED); for (int i = 0; i < n; i++) { int k = rand.nextInt(x); rarr[i] = arr[k]; arr[k] = arr[x - 1]; x--; } // **** check if we have a duplicate random number **** HashSet<Integer> set = new HashSet<Integer>(); for (int i = 0; i < rarr.length; i++) if (!set.add(rarr[i])) throw new Exception("arrayWithUniqueValues <<< duplicate: " + rarr[i]); if (set.size() != n) throw new Exception("arrayWithUniqueValues <<< set.size() != n: " + n); // **** display the random array **** System.out.print("arrayWithUniqueValues <<< rarr: ["); for (int i = 0; i < rarr.length; i++) { System.out.print(rarr[i]); if (i < rarr.length - 1) System.out.print(", "); } System.out.println("]"); // **** return the random array **** return rarr; } /* * Populate array with integers in the specified range * using a random order. */ static int[] populateArray(int minVal, int maxVal) { // **** populate the list **** List<Integer> lst = new ArrayList<Integer>(); for (int i = 0; i < (maxVal - minVal + 1); i++) { lst.add(minVal + i); } // **** display the ordered list **** System.out.print("populateArray <<< lst: "); for (int val : lst) System.out.print(val + " "); System.out.println(); // **** shuffle the list **** Collections.shuffle(lst); // **** convert the shuffled list to an array **** int[] arrs = new int[lst.size()]; // **** populate the shuffled array **** for (int i = 0; i < lst.size(); i++) arrs[i] = lst.get(i); // **** display the shuffled array **** System.out.print("populateArray <<< arrs: "); for (int i = 0; i < arrs.length; i++) System.out.print(arrs[i] + " "); System.out.println(); // **** return the shuffled array **** return arrs; } /* * */ static int[] randomArray(Random rand) { int n = rand.nextInt(MAX_LEN); System.out.println("randomArray <<< n: " + n); int[] arr = new int[n]; // **** populate the array **** for (int i = 0; i < n; i++) arr[i] = rand.nextInt(MAX_VAL); // **** display the array **** System.out.print("randomArray <<< arr: "); for (int i = 0; i < arr.length; i++) System.out.print(arr[i] + " "); System.out.println(); // **** **** return arr; } /* * Return an array out of four based on the argument. */ static int[] dimArrays(int n) { int[] arr = null; // **** populate the array **** switch (n) { case 0: int[] arr0 = {100, 150, 50, 75, 125, 74, 73, 76, 124, 130, 131}; arr = arr0; break; case 1: int[] arr1 = {100, 50, 150, 25, 75, 125, 175, 12, 70, 78, 123, 128, 225, 5, 65, 73, 79, 122, 126, 129, 250, 0, 275}; arr = arr1; break; case 2: int[] arr2= {100, 75, 125, 50, 87, 112, 150, 25, 175}; arr = arr2; break; case 3: int[] arr3= {100, 125, 75, 80, 85, 90, 70, 65, 60}; arr = arr3; break; default: System.err.println("dimArrays <<< UNEXPECTED n: " + n); break; } // **** display the array **** System.out.print("dimArrays <<< arr: "); for (int i = 0; i < arr.length; i++) System.out.print(arr[i] + " "); System.out.println(); // **** **** return arr; } /* * Build a BST and perform some operations on it. */ public static void main(String[] args) throws Exception { // **** instantiate and initialize a pseudo random number generator **** Random rand = new Random(SEED); // int firstInt = rand.nextInt(); // System.out.println("main <<< firstInt: " + firstInt); // // **** select a range for the values **** // int[] range = {rand.nextInt(MAX_VAL) + 1, rand.nextInt(MAX_VAL) + 1}; // System.out.println("main <<< range: [" + range[0] + ", " + range[1] + "]"); // // **** generate an array of unique integers in the specified range **** // int[] values = arrayWithUniqueValues(Math.min(range[0], range[1]), // Math.max(range[0], range[1])); // // int[] values = populateArray(Math.min(range[0], range[1]), // Math.max(range[0], range[1])); // // **** generate a random array in the specified range **** // int[] values = randomArray(rand); // **** loop once per set of numbers **** int[] values = null; for (int i = 0; i < 5; i++) { // **** use a specific array **** if (i < 4) values = dimArrays(i); // **** use a pseudo random array **** else values = randomArray(rand); // **** use the pseudo random array to create a BST **** BinaryTree tree = new BinaryTree(); for (int val : values) { tree.insert(val); } // // **** populate the binary tree by hand **** // BinaryTree tree = new BinaryTree(); // tree.root = new Node(1); // tree.root.left = new Node(2); // tree.root.right = new Node(3); // tree.root.left.left = new Node(4); // tree.root.left.right = new Node(5); // // // **** display the value of the root node**** // System.out.println("main <<< root.val: " + tree.getRoot().getVal()); // System.out.println(); // **** in order traversal **** System.out.print("main <<< inorder: "); tree.inorder(tree.getRoot()); System.out.println(); // **** depth first traversal **** System.out.print("main <<< depthfirst: "); tree.depthfirst(tree.getRoot()); System.out.println(); // **** post order traversal **** System.out.print("main <<< postorder: "); tree.postorder(tree.getRoot()); System.out.println(); // **** get the max depth of the BST **** int maxDepth = tree.maxDepth(tree.getRoot()); System.out.println("main <<< maxDepth: " + maxDepth); // **** get the max depth of the tree **** maxDepth = tree.maxDepth2(tree.getRoot()); System.out.println("main <<< maxDepth2: " + maxDepth); // **** get the diameter of the tree **** System.out.println("main <<< diameter: " + tree.diameter(tree.getRoot())); // **** for the looks **** System.out.println(); } // **** display the BST tree **** // tree.display(tree.getRoot()); // ???? exit the program ???? System.exit(0); /* // **** generate two values to determine the distance **** int val1 = Math.min(range[0], range[1]) + rand.nextInt(MAX_VAL / 3); int val2 = Math.max(range[0], range[1]) - rand.nextInt(MAX_VAL / 3); System.out.println("main <<< val1: " + val1 + " val2: " + val2); // **** step 1: find distance from root to val1 **** int dist1 = tree.distRootToVal(tree.getRoot(), val1); System.out.println("main <<< dist1: " + dist1); // **** step 2: find distance from root to val2 **** int dist2 = tree.distRootToVal(tree.getRoot(), val2); System.out.println("main <<< dist2: " + dist2); // **** step 3: find distance from root to LCA (Lowest Common Ancestor) **** Node lca = null; if ((dist1 >= 0) && (dist2 >= 0)) { lca = tree.lca(tree.getRoot(), val1, val2, 0); if (lca != null) System.out.println("main <<< lca = " + lca.toString()); else System.out.println("main <<< lca == null"); } // **** step 4: find the distance between nodes **** if (lca != null) { int dist = tree.distNodes(tree.getRoot(), dist1, dist2, lca); System.out.println("main <<< dist: " + dist); } // **** putting all the steps together **** int dist = tree.findDist(tree.getRoot(), val1, val2); System.out.println("main <<< val1: " + val1 + " val2: " + val2 + " dist: " + dist); */ } }

In earlier posts I have used the functions which are now commented at the start of the main() function. Please disregard them. I also used them while developing this code.

As you can see there is a loop that starts by using the dimArrays() and randomArray() functions. I worked the code using the second function. Towards the end, I decided to verify my solution with the test cases in David’s code. He did a good job on providing the arrays and results. As you can see I used the four arrays and the results seem to match.

Now let’s look at the results in the console:

dimArrays <<< arr: 100 150 50 75 125 74 73 76 124 130 131 main <<< inorder: 50:1 73:4 74:3 75:2 76:3 100:0 124:3 125:2 130:3 131:4 150:1 main <<< depthfirst: 100:0 50:1 150:1 75:2 125:2 74:3 76:3 124:3 130:3 73:4 131:4 main <<< postorder: 73:4 74:3 76:3 75:2 50:1 124:3 131:4 130:3 125:2 150:1 100:0 main <<< maxDepth: 4 main <<< maxDepth2: 4 main <<< diameter: 8 dimArrays <<< arr: 100 50 150 25 75 125 175 12 70 78 123 128 225 5 65 73 79 122 126 129 250 0 275 main <<< inorder: 0:5 5:4 12:3 25:2 50:1 65:4 70:3 73:4 75:2 78:3 79:4 100:0 122:4 123:3 125:2 126:4 128:3 129:4 150:1 175:2 225:3 250:4 275:5 main <<< depthfirst: 100:0 50:1 150:1 25:2 75:2 125:2 175:2 12:3 70:3 78:3 123:3 128:3 225:3 5:4 65:4 73:4 79:4 122:4 126:4 129:4 250:4 0:5 275:5 main <<< postorder: 0:5 5:4 12:3 25:2 65:4 73:4 70:3 79:4 78:3 75:2 50:1 122:4 123:3 126:4 129:4 128:3 125:2 275:5 250:4 225:3 175:2 150:1 100:0 main <<< maxDepth: 5 main <<< maxDepth2: 5 main <<< diameter: 10 dimArrays <<< arr: 100 75 125 50 87 112 150 25 175 main <<< inorder: 25:3 50:2 75:1 87:2 100:0 112:2 125:1 150:2 175:3 main <<< depthfirst: 100:0 75:1 125:1 50:2 87:2 112:2 150:2 25:3 175:3 main <<< postorder: 25:3 50:2 87:2 75:1 112:2 175:3 150:2 125:1 100:0 main <<< maxDepth: 3 main <<< maxDepth2: 3 main <<< diameter: 6 dimArrays <<< arr: 100 125 75 80 85 90 70 65 60 main <<< inorder: 60:4 65:3 70:2 75:1 80:2 85:3 90:4 100:0 125:1 main <<< depthfirst: 100:0 75:1 125:1 70:2 80:2 65:3 85:3 60:4 90:4 main <<< postorder: 60:4 65:3 70:2 90:4 85:3 80:2 75:1 125:1 100:0 main <<< maxDepth: 4 main <<< maxDepth2: 4 main <<< diameter: 6 randomArray <<< n: 13 randomArray <<< arr: 188 47 113 54 104 34 6 178 148 169 73 117 63 main <<< inorder: 6:3 34:2 47:1 54:3 63:6 73:5 104:4 113:2 117:5 148:4 169:5 178:3 188:0 main <<< depthfirst: 188:0 47:1 34:2 113:2 6:3 54:3 178:3 104:4 148:4 73:5 117:5 169:5 63:6 main <<< postorder: 6:3 34:2 63:6 73:5 104:4 54:3 117:5 169:5 148:4 178:3 113:2 47:1 188:0 main <<< maxDepth: 6 main <<< maxDepth2: 6 main <<< diameter: 7

We first display the array used to construct the BST. We then traverse each tree in order to easily check that the tree was properly created. The method displays the value followed by a “:” and the depth of the node. The first entry in the arrays is used as a root so it is always at level zero (0).

After the inorder() call, we do a depth first traversal. That was done to help me verify that the tree diagrams I generated on paper and Visio were correct.

The post order method displays the tree in post order. If you enable the print statement in the findDiameter() method in the BinaryTree.java class you will see that the tree traversal is done in post order. That helps understand and visualize how things work.

The reason I displayed the depth of the tree twice was to check that both returned the same values. I could have commented that out because I had no use for them.

Finally the diameter for each tree is displayed.

Hope you enjoyed this post and most important try developing the code in the language of your choice. Then go back to the posts and verify that all is well. Of course, if you have a comment or question regarding this or any other post, please do not hesitate and leave me a message below.

Keep on reading and practicing. Seem like is the only way to learn and move ahead.

Regards;

John

Please follow me on Twitter: **@john_canessa**