๐พ Related pieces of data
Learning Objectives
In programming, we often have related pieces of data.
Let’s consider a list of prices in a bill:
4.6, 5.03, 7.99, 8.01
limitations of many variables
We can store this list of prices in a JavaScript program by declaring multiple variables:
const price0 = 4.6;
const price1 = 5.03;
const price2 = 7.99;
const price4 = 8.01;
Each identifier is the word price
with a numerical suffix to indicate its position in the list. However, this is undoubtedly the wrong approach.
- If the number of items in the bill is huge, we must keep declaring new variables.
- If the number of items changes, we must reassign the values of variables so they’re in the correct order, and change any place we’re using the variables to know about the new one.
- If we do mutliple things to all of the values (say we have one loop adding them, and one loop printing them), we will need to change all of the places any time we add new values.
Instead we have to group the data together using a
๐ grouping data
Learning Objectives
In JavaScript, we can store data inside an
Instead of writing:
const item0 = 4.6;
const item1 = 5.03;
const item2 = 7.99;
const item4 = 8.01;
We can declare an array literal as follows:
const items = [4.6, 5.03, 7.99, 8.01];
Notice the identifier for the array is items. We chose to use the plural word items
instead of the singular item
, because arrays can store multiple pieces of information.
ordered data
Recall
We’ve already encountered ordered data before. A string is an ordered collection of characters. Let’s recall an example of a string:
const volunteer = "Moussab";
The character "M"
is at index 0, "o"
is at index 1, and so on.
As with strings, arrays are also zero-indexed in a similar way:
const items = [4.6, 5.03, 7.99, 8.01];
So we can refer to the
index | 0 | 1 | 2 | 3 |
---|---|---|---|---|
element | 4.6 | 5.03 | 7.99 | 8.01 |
In JavaScript, we can use square bracket notation to access specific elements in the array using an index.
items[0]; // evaluates to 4.6
items[1]; // evaluates to 5.03
items[2]; // evaluates to 7.99
// etc
๐ Calculating the mean
Learning Objectives
Let’s consider a problem where we calculate the mean of a list of numbers.
Given an array of numbers,
When we call calculateMean
with the array of numbers
Then we get the mean
Let’s create a test to check its functionality:
test("calculates the mean of a list of numbers", () => {
const list = [3, 50, 7];
const currentOutput = calculateMean(list);
const targetOutput = 20;
expect(currentOutput).toBe(targetOutput); // 20 is (3 + 50 + 7) / 3
});
In this test, we’re checking we get a value of 20
by adding together 3 + 50 + 7
and then dividing by the number of items (3
). We calculate the mean of a list of numbers by:
- summing all the numbers in the array
- dividing the sum by the length of the array
We can define a ๐ฏ sub-goal of calculating the sum of all numbers in the list.
โ Summation
Learning Objectives
๐ฏ Sub-goal: compute the sum of an array of numbers.
To sum a list we can start by creating a variable total
with an initial value of 0
.
We then need to repeatedly add each value in the list to our total
.
function sumValues(list) {
let total = 0;
total += list[0]; // access a list element and add to total
total += list[1];
total += list[2];
total += list[3];
total += list[4];
return total;
}
sumValues([1, 2, 3, 4, 5]);
However, this approach is flawed.
๐ iterating
Learning Objectives
To solve the sub-goal, we have to repeatedly add each number in the array to the total
, one at a time. In programming, the process of repeating something is called iteration.
In programming, we can iterate by using a
In particular, we can use a for...of
statement to sum the elements of the array.
function calculateMean(list) {
let total = 0;
for (const item of list) {
total += item;
}
}
๐ Calculating the median
Learning Objectives
Let’s define another problem.
We want to calculate the median value from an array of numbers.
Given an array of numbers in ascending order,
When we call calculateMedian
with this array
Then we get the median value
We calculate the median of a list of numbers by finding the middle value in the list.
Let’s start with a test to check the return value of calculateMedian
given an ordered list of numbers.
test("calculates the median of a list of odd length", function () {
const list = [10, 20, 30, 50, 60];
const currentOutput = calculateMedian(list);
const targetOutput = 30;
expect(currentOutput).toBe(targetOutput);
});
๐จ Implementing calculateMedian
So we can implement calculateMedian
.
We can summarise our approach as follows.
flowchart TD A[Step 1: Find the middle index of the array] --> B[Step 2: Get the middle item] B --> C[Step 3: Return the middle item]
In code we can we can use splice
to to get the middle item.
function calculateMedian(list) {
const middleIndex = Math.floor(list.length / 2);
const median = list.splice(middleIndex, 1)[0];
return median;
}
Try writing a test case to check calculateMedian
works in the case when it is passed an array of even length.
Use documentation to check how the median is computed in this case.
calculateMedian
, hopefully you see this implementation isn’t doing the right thing. Try implementing the functionality for this case.๐งฑ Assembling the parts
Learning Objectives
Now suppose we have a program where we use the functions we implemented earlier:
const salaries = [10, 20, 30, 40, 60, 80, 80];
const median = calculateMedian(salaries);
const mean = calculateMean(salaries);
console.log(`The median salary is ${median}`);
console.log(`The mean salary is ${mean}`);
Predict and explain what will get printed to the console when the code above runs.
Then run the code above on your local machine to check your prediction. Does your initial explanation now make sense?
(Note: you’ll have to declare the functions somewhere too)
๐ Finding the bug
In the code above, the median
value is correct: however, the mean
is incorrect.
We can add a log to the program to identify the origin of the bug.
|
|
Run it
To understand why this bug occurs, we need to explore more concepts.
๐ค References
Learning Objectives
Arrays are stored by
Consider the following example,
const list = [10, 20, 30];
const copy = list;
copy.push(60, 70);
console.log(list);
console.log(copy);
Let’s break down what is happening in this program.
Play computer with the code above to step through the code and find out what happens when the code is executed.
- We make an array
[10, 20, 30]
and store it somewhere in memory. list
is assigned a reference to[10, 20, 30]
copy
is assigned a reference pointing at the same memory aslist
At this stage in the program, list
and copy
point to the same location in memory.
push
function mutates (changes) the array thatcopy
points to.- prints out
list
:[10, 20, 30, 60, 70]
- prints out
copy
:[10, 20, 30, 60, 70]
So as copy
and list
point to the same array.
If we mutate list
then we’re mutating the same list that copy
points to.
So the console output is the same.
|
|
In the example above, salaries
is assigned a reference on the first line.
Explain why calculateMedian
and calculateMean
both get access to the same array.
Shared reference
We can also check these variables share the same reference.
const list = [10, 20, 30];
const copy = list;
console.log(list === copy); // evaluates to true
If we’re comparing 2 array variables with ===
, then it will evaluate to true
only if they have the same reference. ===
is comparing the references to the arrays, not the arrays themselves.
Value vs reference
In JavaScript, arrays and objects are reference types: everything else is a value type.Passing by value
Use the tabs below to compare the effects of passing by reference and passing by value.
There are two different but similar implementations of pluralise
- a function that appends an s
to the end of its input.
Here pluralise
is passed an array by reference.
lettersInAnArray
is passed by reference. pluralise
’s modification is visible here, because the same underlying storage was modified.
Step through the code to observe this behaviour:
Here pluralise
is passed a string by value.
This means a copy of string
’s value is passed to pluralise
in the second tab. pluralise
’s reassignment is not visible here, because a copy was made just for the function before the value was modified.
Step through the code to observe this behaviour:
mutation
Learning Objectives
Let’s take another look at our earlier implementation of calculateMedian
:
function calculateMedian(list) {
const middleIndex = Math.floor(list.length / 2);
const median = list.splice(middleIndex, 1)[0];
return median;
}
const salaries = [10, 20, 30, 40, 60, 80, 80];
const median = calculateMedian(salaries);
// At this point, the array referenced by salaries has been mutated after calculateMedian(salaries), and a reference to the same array is given to calculateMean
const mean = calculateMean(salaries);
console.log(`The median salary is ${median}`);
console.log(`The mean salary is ${mean}`);
calculateMedian
gets the middle value by calling splice
. However, splice
is a
When we call splice
it does 2 things:
- removes the specified item from the list
- returns the removed item
splice
modifies the array: however, calculateMean
is also passed a reference to the same array too.
In other words,
calculateMedian
modifies the same array that is passed tocalculateMean
.
Play computer with the example above. Pay careful attention to what happens when salaries
is passed to calculateMedian
โ ๏ธ Side effects
Learning Objectives
Currently calculateMedian
mutates its input - the list
of numbers. This mutation is called a
In this case, the side effect has unintended consequences. We have introduced a
calculateMean
return the wrong value. Both calculateMean
and calculateMedian
need access to the original salaries
array. Therefore, we should take make sure we don’t mutate the array unless we really mean to.
Testing no mutation
We can add an additional assertion to the tests for calculateMedian
to check it isn’t modifying the original input:
test("doesn't modify the input", () => {
const list = [1, 2, 3];
calculateMedian(list);
expect(list).toEqual([1, 2, 3]); // Note that the toEqual matcher checks the values inside arrays when comparing them - it doesn't use `===` on the arrays, we know that would always evaluate to false.
});
In this test, we don’t check the return value of calculateMedian
. We assert that the input has the same contents as the original input. We can use the toEqual
matcher to check the contents of the array referenced by the variable list
.
Recall the current buggy implementation of calculateMedian
:
function calculateMedian(list) {
const middleIndex = Math.floor(list.length / 2);
const median = list.splice(middleIndex, 1)[0];
return median;
}
We’ve established that we shouldn’t use splice
to retrieve the median from the input array.
Fix the implementation of calculateMedian
above so it no longer calls splice
(which mutates the input), and instead gives the right answer without mutating the input.
Fail Fast Prep ๐
Learning Objectives
Introduction
Failing fast is crucial for identifying flaws and giving you a quick opportunity to change anything that is needed.
Embracing failure as a learning opportunity accelerates growth and adaptation and is a skill that makes you a more efficient professional.
Failing fast
๐ฏ Goal: Learn about failing fast (20 minutes)
- Read this piece that will introduce you to the concept of failing fast and how this can be put into practice by using the Agile framework.
- Watch this video about failure and how you learn and innovate.
Reflect on your experience of Failing Fast
๐ฏ Goal: Reflection on failing fast concept in your life (20 minutes)
Considering what you have learned regarding Failing Fast, think of an instance when you failed to achieve something you aimed for.
Write down about this event and reflect on it.
You can use the following questions to guide you.
- What did you learn from it?
- Did you try a different approach to achieve the same goal?
- Did you change the objective you were after?
- What would you have done differently?