Feeling nostalgic

Today’s coding session proved to be quite challenging. I found myself grappling with complex mathematical equations, which was particularly difficult for me since I tend to struggle with math, particularly word problems. I had to consult Google for assistance multiple times, but I did not make much progress. Unfortunately, it seems like I’ve taken a step back instead of moving forward.

I left my charging cable behind, but fortunately, my laptop had a full charge. Initially, I believed that my backpack contained an extra charging cable, but it turned out that the one inside was defective. Consequently, I’m left with only one functioning cable, which is currently connected to my desk.

Today, I find myself at the university library. Later on, Tommy and I have plans to catch a screening of Rascal Does Not Dream of a Knapsack Kid, the sequel to the beloved Rascal Does Not Dream of Bunny Girl Senpai. I thoroughly enjoyed the first movie, so I’m eagerly anticipating what the sequel has in store.

I had a relaxing weekend, mostly spent watching YouTube and playing Animal Crossing and Skyrim. While I enjoy Skyrim, it can sometimes be emotionally intense, so I’m taking my time with it. Having played the game multiple times, I’m familiar with the storyline.

I should have started placing some ephemera from my last Alaska trip in a notebook. I had the time to work on a travel journal. I’d also like to use photographs, so I will look into how to do that. I want to finish this before my next Alaska trip later this year. I collect a lot of ephemera; now I need to put it all together.

Yesterday was Alexis’s birthday, and it’s hard to believe she’s already 21. It feels like just yesterday, she was a child. As cliche as that sounds. However, in reality, it’s only been a short time since she entered adulthood. I feel nostalgic.

JavaScript notes…
————————————
Statistics calculator steps 1 – 35

Step 1
Statistics is a way of using math to make sense of data. It helps us understand patterns and trends in information, so we can make predictions and decisions based on that information.

In this challenge, you will build a statistics calculator that takes a set of numbers and returns the mean, median, mode, standard deviation, and variance.

The HTML and CSS have been provided for you. Feel free to explore the code – you may notice that the calculate function is called when the form is submitted. When you are ready, declare a calculate variable and assign it an empty function in the script.js file.

const calculate = () => {
  
}

Step 2
To begin, the calculate function needs to find the number that was entered in the #numbers input field. To do this, use a .querySelector to locate the input field and then use the .value property to get the number entered.

Store this in a value variable.

const calculate = () => {
  const value = document.querySelector("#numbers").value
}

Step 3
Now that you have the value of the input, you need to split it into an array of numbers. Use the .split() method to do this.

The .split() method takes a string and splits it into an array of strings. You can pass it a string of characters or a RegEx to use as a separator. For example, string.split(“,”) would split the string at each comma and return an array of strings.

Use the /,\s*/g regex to split the value string by commas. You can tweak it based on the number of spaces separating your values. Store the array in an array variable.

const calculate = () => {
  const value = document.querySelector("#numbers").value;
  const array = value.split(/,\s*/g);
}

Step 4
The value of an input element is always a string, even if the input type is number. You need to convert this array of strings into an array of numbers. To do this, you can use the .map() method.

Create a numbers variable and assign it the value of array.map(). Remember that .map() creates a new array, instead of mutating the original array.

const calculate = () => {
  const value = document.querySelector("#numbers").value;
  const array = value.split(/,\s*/g);
  const numbers = array.map();
}

Step 5
The .map() method takes a callback function as its first argument. This callback function takes a few arguments, but the first one is the current element being processed. Here is an example:

array.map(el => {

})
The callback function needs to return a value. In this case, you want to return the value of each element converted to a number. You can do this by using the Number() constructor, passing the element as an argument.

Add a callback function to your .map() method that converts each element to a number.

const calculate = () => {
  const value = document.querySelector("#numbers").value;
  const array = value.split(/,\s*/g);
  const numbers = array.map(el => Number(el));
}

Step 6
A user could put any text they want into the input box. You want to make sure that you are only working with numbers. The Number() constructor will return NaN (which stands for “not a number”) if the value passed to it cannot be converted to a number.

You need to filter these values out – thankfully, arrays have a method specifically for this. The .filter() method will allow you to filter elements out of an array, creating a new array in the process.

Declare a filtered variable and assign numbers.filter() to it.

const calculate = () => {
  const value = document.querySelector("#numbers").value;
  const array = value.split(/,\s*/g);
  const numbers = array.map(el => Number(el));
  const filtered = numbers.filter();
}

Step 7
Much like the .map() method, the .filter() method takes a callback function. The callback function takes the current element as its first argument.

array.filter(el => {

})
The callback function needs to return a Boolean value, which indicates whether the element should be included in the new array. In this case, you want to return true if the element is not NaN (not a number).

However, you cannot check for equality here, because NaN is not equal to itself. Instead, you can use the isNaN() method, which returns true if the argument is NaN.

Add a callback function to your .filter() method that returns true if the element is not NaN.

const calculate = () => {
  const value = document.querySelector("#numbers").value;
  const array = value.split(/,\s*/g);
  const numbers = array.map(el => Number(el));
  const filtered = numbers.filter(el => !isNaN(el));
}

Step 8
Array methods can often be chained together to perform multiple operations at once. As an example:

array.map().filter();
The .map() method is called on the array, and then the .filter() method is called on the result of the .map() method. This is called method chaining.

Following that example, remove your filtered variable, and chain your .filter() call to your .map() call above. Do not remove either of the callback functions.

const calculate = () => {
  const value = document.querySelector("#numbers").value;
  const array = value.split(/,\s*/g);
  const numbers = array.map(el => Number(el)).filter(el => !isNaN(el));
}

Step 9
That is as far as you can get with the calculate function for now. It is time to write your mean logic.

Create an empty function called getMean. It should take a single parameter array.

function getMean(array) {
  
}

Step 10
The mean is the average value of all numbers in a list. The first step in calculating the mean is to take the sum of all numbers in the list. Arrays have another method, called .reduce(), which is perfect for this situation. The .reduce() method takes an array and applies a callback function to condense the array into a single value.

Declare a sum variable and assign array.reduce() to it.

const getMean = (array) => {
 const sum = array.reduce();
}

Step 11
Like the other methods, .reduce() takes a callback. This callback, however, takes at least two parameters. The first is the accumulator, and the second is the current element in the array. The return value for the callback becomes the value of the accumulator on the next iteration.

array.reduce((acc, el) => {

});
For your sum variable, pass a callback to .reduce() that takes the accumulator and the current element as parameters. The callback should return the sum of the accumulator and the current element.

const getMean = (array) => {
  const sum = array.reduce((acc, el) => acc + el);
}

Step 12
The .reduce() method takes a second argument that is used as the initial value of the accumulator. Without a second argument, the .reduce() method uses the first element of the array as the accumulator, which can lead to unexpected results.

To be safe, it’s best to set an initial value. Here is an example of setting the initial value to an empty string:

array.reduce((acc, el) => acc + el.toLowerCase(), “”);
Set the initial value of the accumulator to 0.

const getMean = (array) => {
  const sum = array.reduce((acc, el) => acc + el, 0);
}

Step 13
The next step in calculating the mean is to divide the sum of numbers by the count of numbers in the list.

Declare a mean variable and assign it the value of sum divided by the length of array.

const getMean = (array) => {
  const sum = array.reduce((acc, el) => acc + el, 0);
  const mean = sum / array.length;
}

Step 14
Finally, you need to return the value of mean.

const getMean = (array) => {
  const sum = array.reduce((acc, el) => acc + el, 0);
  const mean = sum / array.length;
  return mean;
}

Step 15
You can actually clean this logic up a bit. Using the implicit return of an arrow function, you can directly return the value of the .reduce() method divided by the length of the array, without having to assign any variables.

Update your getMean function as described above.

const getMean = (array) => 
  array.reduce((acc, el) => acc + el, 0) / array.length;

Step 16
Now you need to use your new getMean function. In your calculate function, declare a mean variable and assign it the value of getMean(numbers).

const calculate = () => {
  const value = document.querySelector("#numbers").value;
  const array = value.split(/,\s*/g);
  const numbers = array.map(el => Number(el)).filter(el => !isNaN(el));
  const mean = getMean(numbers);

}

Step 17
To display the value of mean, your app has a #mean element ready to go.

Use a .querySelector to find that element, and then set its .textContent to the value of mean.

const calculate = () => {
  const value = document.querySelector("#numbers").value;
  const array = value.split(/,\s*/g);
  const numbers = array.map(el => Number(el)).filter(el => !isNaN(el));
  
  const mean = getMean(numbers);
  document.querySelector("#mean").textContent = mean

Step 18
If you test your form with a list of numbers, you should see the mean display on the page. However, this only works because freeCodeCamp’s iframe has special settings. Normally, when a form is submitted, the event triggers a page refresh.

To resolve this, add return false; after your calculate(); call in the onsubmit attribute.

Step 19

Time to start working on the median calculation. The median is the midpoint of a set of numbers.

Begin with an empty function called getMedian, which should take an array parameter.

function getMedian(array) {
  
}

Step 20

The first step in calculating the median is to ensure the list of numbers is sorted from least to greatest. Once again, there is an array method ideal for this – the .sort() method.

Declare a sorted variable and assign array.sort() to it.

const getMedian = (array) => {
  const sorted = array.sort();
}

Step 21

By default, the .sort() method converts the elements of an array into strings, then sorts them alphabetically. This works well for strings, but not so well for numbers. For example, 10 comes before 2 when sorted as strings, but 2 comes before 10 when sorted as numbers.

To fix this, you can pass in a callback function to the .sort() method. This function takes two arguments, which represent the two elements being compared. The function should return a value less than 0 if the first element should come before the second element, a value greater than 0 if the first element should come after the second element, and 0 if the two elements should remain in their current positions.

To sort your numbers from smallest to largest, pass a callback function that takes parameters a and b, and returns the result of subtracting b from a.

 const getMedian = (array) => {
  const sorted = array.sort((a, b) => a - b);
}

Step 22

The next step is to find the number in the middle of the list. If the list has an odd number of numbers, the middle number is the median. If the list has an even number of numbers, the median is the average of the two middle numbers.

You can check if a number is even or odd with the modulus operator, which is represented by the % symbol. This operator returns the remainder of the division of two numbers. If the remainder is 0, the number is even. If the remainder is 1, the number is odd:

array.length % 2 === 0;

Declare a median variable. Using the ternary operator, check if the length of array is even. If the length of array is even, find the two middle numbers and calculate the mean of those numbers. If the length of array is odd, find the middle number and assign it to the median variable.

const getMedian = (array) => {
  const sorted = array.sort((a, b) => a - b);
  const median = array.length % 2 === 0 ?
  getMean([sorted[array.length / 2], sorted[array.length / 2 - 1]]):
  sorted[Math.floor(array.length / 2)];
}

Step 23

Finally, return the value of median.

Like the getMean function, you could condense this code into one line and reduce the number of variables you instantiate. However, it is important to remember that shorter code is not always better code. In this case, reducing the lines of code would make the code harder to read and understand, impacting future maintainability.

const getMedian = (array) => {
  const sorted = array.sort((a, b) => a - b);
  const median =
    array.length % 2 === 0
      ? getMean([sorted[array.length / 2], sorted[array.length / 2 - 1]])
      : sorted[Math.floor(array.length / 2)];
      return median;
}

Step 24

Like you did with your getMean function, you need to add your getMedian function to your calculate logic.

Declare a variable median and assign it the value of getMedian(numbers). Then, query the DOM for the #median element and set the textContent to median.

const mean = getMean(numbers);
  const median = getMedian(numbers)

  document.querySelector("#mean").textContent = mean;
  document.querySelector("#median").textContent = median;

Step 25

Your next calculation is the mode, which is the number that appears most often in the list. To get started, declare a getMode function that takes the same array parameter you have been using.

function getMode(array) {
  
}

Step 26

In your new function, declare an empty counts object. You will use this to track the number of times each number appears in the list.

const getMode = (array) => {
  const counts = {
    
  }
}

Step 27

Remember that the .forEach() method allows you to run a callback function for each element in the array.

Use the .forEach() method to loop through the array. In the callback, use the el parameter to access the counts object and increment the count for each number.

const getMode = (array) => {
  const counts = {};

  array.forEach((el) => {
    counts[el] = (counts[el] || 0) + 1;
  })
}

Step 28

There are a few edge cases to account for when calculating the mode of a dataset. First, if every value appears the same number of times, there is no mode.

To calculate this, you will use a Set. A Set is a data structure that only allows unique values. If you pass an array into the Set constructor, it will remove any duplicate values.

Start by creating an if statement. In the condition, create a Set with new Set() and pass it the Object.values() of your counts object. If the size property of this Set is equal to 1, that tells you every value appears the same number of times. In this case, return null from your function.

const getMode = (array) => {
  const counts = {};
  array.forEach((el) => {
    counts[el] = (counts[el] || 0) + 1;
  })
  if (new Set(Object.values(counts)).size === 1) {
    return null;
  }
}

Step 29

Now you need to find the value that occurs with the highest frequency. You’ll use the Object.keys() method for this.

Start by declaring a highest variable, and assigning it the value of the counts object’s Object.keys() method.

const getMode = (array) => {
  const counts = {};
  array.forEach((el) => {
    counts[el] = (counts[el] || 0) + 1;
  })
  if (new Set(Object.values(counts)).size === 1) {
    return null;
  }
  const highest = Object.keys(counts);
}

Step 30

Now you need to sort the values properly. Chain the .sort() method to your Object.keys() call.

For the callback, you’ll need to use the counts object to compare the values of each key. You can use the a and b parameters to access the keys. Then, return the value of counts[b] minus the value of counts[a].

Finally, access the first element in the array using bracket notation to complete your highest variable.

const getMode = (array) => {
  const counts = {};
  array.forEach((el) => {
    counts[el] = (counts[el] || 0) + 1;
  })
  if (new Set(Object.values(counts)).size === 1) {
    return null;
  }
  const highest = Object.keys(counts).sort((a, b) => counts[b] - counts[a])[0];
}

Step 31

If multiple numbers in a series occur at the same highest frequency, they are all considered the mode. Otherwise, the mode is the number that occurs most often, that single number is the mode.

Thankfully, you can handle both of these cases at once with the .filter() method. Start by declaring a mode variable and assigning it the value of Object.keys(counts).

const getMode = (array) => {
  const counts = {};
  array.forEach((el) => {
    counts[el] = (counts[el] || 0) + 1;
  })
  if (new Set(Object.values(counts)).size === 1) {
    return null;
  }
  const highest = Object.keys(counts).sort(
    (a, b) => counts[b] - counts[a]
  )[0];
  const mode = Object.keys(counts);
}

Step 32

Now chain the filter method to your latest Object.keys() call. The callback function should return whether the value of counts[el] is equal to your counts[highest].

const getMode = (array) => {
  const counts = {};
  array.forEach((el) => {
    counts[el] = (counts[el] || 0) + 1;
  })
  if (new Set(Object.values(counts)).size === 1) {
    return null;
  }
  const highest = Object.keys(counts).sort(
    (a, b) => counts[b] - counts[a]
  )[0];
  const mode = Object.keys(counts).filter((el) => {
    return counts[el] === counts[highest];
  });
}

Step 33

Time to return your mode variable.

mode is an array, so return it as a string with the .join() method. Separate the elements with a comma followed by a space.

const getMode = (array) => {
  const counts = {};
  array.forEach((el) => {
    counts[el] = (counts[el] || 0) + 1;
  })
  if (new Set(Object.values(counts)).size === 1) {
    return null;
  }
  const highest = Object.keys(counts).sort(
    (a, b) => counts[b] - counts[a]
  )[0];
  const mode = Object.keys(counts).filter(
    (el) => counts[el] === counts[highest]
  );
  return mode.join(", ");
}

Step 34

Add your getMode() function to your calculate logic, and update the respective HTML element.

const mean = getMean(numbers);
  const median = getMedian(numbers);
  const mode = getMode(numbers);

  document.querySelector("#mean").textContent = mean;
  document.querySelector("#median").textContent = median;
  document.querySelector("#mode").textContent = mode;

Step 35

Your next calculation is the range, which is the difference between the largest and smallest numbers in the list.

You previously learned about the global Math object. Math has a .min() method to get the smallest number from a series of numbers, and the .max() method to get the largest number. Here’s an example that gets the smallest number from an array:

const numbersArr = [2, 3, 1];

console.log(Math.min(…numbersArr));

// Expected output: 1

Declare a getRange function that takes the same array parameter you have been using. Using Math.min(), Math.max(), and the spread operator, return the difference between the largest and smallest numbers in the list.

const getRange = (array) => {
  return Math.max(...array) - Math.min(...array);
}

Facebooktwitterredditpinterestlinkedinmail