๐ Ordered data
Learning Objectives
Let’s imagine we’re writing a program that involves information about a user’s profile.
We could store some user’s profile details in an array:
Interactive code block
const profileData = ["Franceso", "Leoni", 33, "Manchester"];
At the moment, we could visualise profileData
in a table like this:
index | value |
---|---|
0 | “Francesco” |
1 | “Leoni” |
2 | 33 |
3 | “Manchester” |
Inside profileData
we access items using an index
. However, with an ordered list of items we can’t tell what each item in the list represents. We only know the position of data in the array. We could access the item at index 3 to get "Manchester"
: however, we don’t know what "Manchester"
tells us about the user. "Manchester"
could be the city they currently live in, it could be their city of birth, a place where they studied in the past etc. We need to know the values but also what these values represent about the user.
We might think we can just remember (and maybe write in a comment) “index 0 is the person’s first name”, but this has problems. What if we need to introduce a new piece of data? We may need to change every piece of code that uses the array. What if some of the data is optional (e.g. a middle name)? It’s also really hard for someone new to come read our code.
Keys not indexes
However, instead of ordering data with indexes, we can label data with keys.
key | value |
---|---|
firstName | “Francesco” |
lastName | “Leoni” |
age | 33 |
cityOfResidence | “Manchester” |
We can look up values in this table by the key. With data stored like this, we can see what values like "Manchester"
actually mean - in this case, it refers to a city of residence for the user.
In JavaScript, we can use an
We can declare an object in the following way:
Interactive code block
const profileData = {
firstName: "Franceso",
lastName: "Leoni"
age: 33,
cityOfResidence: "Manchester",
};
๐๏ธ Key value pairs
Learning Objectives
The profileData
object is made up of properties.
Each property is an association between a key and a value.
Interactive code block
{
firstName: "Francesco",
lastName: "Leoni",
age: 33,
cityOfResidence: "Manchester"
};
In the
firstName
and "Francesco"
. firstName
is the key, "Francesco"
is the value associated with the key firstName
.
In object literals, each key-value pair is separated by a comma.
๐ช Property access
Learning Objectives
We’ve already accessed object property values. console
is an object:
Welcome to Node.js v16.19.1.
Type ".help" for more information.
> console
Object [console] {
log: [Function: log],
warn: [Function: warn],
dir: [Function: dir],
time: [Function: time],
timeEnd: [Function: timeEnd],
.
.
.
}
We use dot notation to access the property value associated with a key.
When we write console.log
- think of this as saying:
“access the value associated with key of
"log"
, inside theconsole
object”
Similarly we can use dot notation to access property values stored in object literals:
Interactive code block
const profileData = {
firstName: "Francesco",
lastName: "Leoni",
age: 33,
};
console.log(profileData.firstName); // logs "Francesco"
Objects also allow looking up property values using square brackets, similar to arrays. Instead of an index, we use a string of the key inside the square brackets:
Interactive code block
const profileData = {
firstName: "Francesco",
lastName: "Leoni",
age: 33,
};
console.log(profileData["firstName"]); // logs "Francesco"
Using dot notation or square brackets both work the same way.
Mutation
Objects are mutable data structures. We can use the assignment operator =
to update the value associated with a particular key.
Interactive code block
const profileData = {
firstName: "Francesco",
lastName: "Leoni",
age: 33,
};
profileData.firstName = "Fraz";
console.log(profileData.firstName); // firstName is now "Fraz"
Interactive code block
const profileData = {
firstName: "Francesco",
lastName: "Leoni",
age: 33,
};
const twinData = profileData;
twinData.age++;
console.log(profileData === twinData);
console.log(profileData.age);
Predict and explain what the console output be when we run the code above runs.
Properties are optional
It’s possible to add properties to an object that already exists. Objects don’t always have the same properties.
Object literals vs objects
What’s the difference between an object, and an object literal?
An object is the thing we’re making, which maps keys to values.
An object literal is how we can write one out specifying all of its key-value pairs in one statement.
These two blocks of code construct equivalent objects:
Interactive code block
const object1 = {
firstName: "Francesco",
lastName: "Leoni",
};
const object2 = {};
object2.firstName = "Francesco";
object2.lastName = "Leoni";
object1
is all constructed in one object literal.object2
starts off with an empty object literal, and then adds some properties to it.
Note: This same terminology is used for other types:
"abc"
is a string literal, "a" + "b" + "c"
makes the same string, but by concatenating three string literals together.
โ๐ชข Query strings
Learning Objectives
Letโs define a problem.
Websites have addresses called urls like this: “https://example.com/widgets". Urls often have
https://example.com/widgets?colour=blue&sort=newest
For the url above, the query string is "colour=blue&sort=newest"
. Query strings consist of query parameters, separated by an ampersand character &
. colour=blue
is a query parameter: we say that colour
is the key and blue
is the value.
urls must always be strings. However, a string isn’t a useful data type for accessing query parameters. Given a key like colour
, accessing the value from a query string stored as a string is not straightforward. However, objects are ideal for looking up values with keys.
We’re going to implement a function parseQueryString
to extract the query parameters from a query string and store them in an object:
Given a query string and a function parseQueryString
,
When we call parseQueryString
with a query string,
Then it should return an object with the key-value pairs
E.g.
Interactive code block
parseQueryString("colour=blue&sort=newest");
// should return { colour: "blue", sort: "newest" }`
โ No parameters
Learning Objectives
Let’s look at the case where the query string is an empty string.
In this case, we need to think of an output that makes sense.
We saw before that we can try to look up a property on an object which the object doesn’t actually have - this will evaluate to undefined
.
What we want in the case that the query string is an empty string, is something where any time we ask it for the value of a query parameter’s key, we get back undefined.
An empty object behaves this way, so it makes sense to return an empty object.
We can write a test case as follows:
Interactive code block
test("given a query string with no query parameters, returns an empty object", function () {
const input = "";
const currentOutput = parseQueryString(input);
const targetOutput = {};
expect(currentOutput).toBe(targetOutput);
});
We can pass this test just by returning an empty object for now. We can define a function parseQueryString
as follows:
Interactive code block
function parseQueryString() {
return {};
}
However, after re-running the tests, we get the following feedback:
๐ ๐ research and interpret
The error message contains the phrase “serializes to the same string”.
Research the meaning of this phrase and then interpret what this error message is telling you in your own words. Start a thread in Slack to discuss your interpretation of the error message.
Checking objects
We saw earlier that objects, like arrays, are reference types. That means that comparing two objects with ===
will only evaluate to true
if they are references to the same objects in memory. Two objects which happen to have the same properties, but are not in the same location in memory, will compare false
using ===
.
In our test, parseQueryString
returns a reference to an empty object. So currentOutput
is assigned this reference. But targetOutput
is assigned a reference to a different object.
In other words, toBe
checks whether the two objects are references to the same object in memory.
As currentOutput
and targetOutput
point to different objects - this can never be true. However, we can use a different matcher that compares the contents of the two objects. We can use toStrictEqual
to check that both objects have exactly the same contents:
Interactive code block
expect(currentOutput).toStrictEqual(targetOutput);
โ Single pair case
Learning Objectives
Let’s consider another test case: when the query string contains a single key-value pair.
We can write a test:
Interactive code block
test("given a query string with one pair of query params, returns them in object form", function () {
const input = "fruit=banana";
const currentOutput = parseQueryString(input);
const targetOutput = { fruit: "banana" };
expect(currentOutput).toStrictEqual(targetOutput);
});
๐งญ Strategy
We first need to separate out the "fruit=banana"
string so we can access "fruit"
and "banana"
separately. We can do this by splitting up the string by the =
character. We can split the string into an array consisting of ['fruit', 'banana']
. Then we can grab the array’s contents and assign the elements meaningful names:
Interactive code block
function parseQueryString(queryString) {
const queryParams = {};
const keyValuePair = queryString.split("=");
const key = keyValuePair[0]; // key will hold 'fruit'
const value = keyValuePair[1]; // value will hold 'banana'
queryParams.key = value;
return queryParams;
}
However, instead of accessing the array’s elements like this, we can use array destructuring to create new variables and assign them values, based on values in an array.
Interactive code block
function parseQueryString(queryString) {
const queryParams = {};
const [key, value] = queryString.split("="); // key will hold 'fruit', value will hold 'banana
queryParams.key = value;
return queryParams;
}
๐ฎ Play computer with the implementation of parseQueryString
above to see why it isn’t working properly.
[ ] Access with variables
Learning Objectives
We can mutate an object using .
dot notation. However, if we look at the return value in the previous implementation we get { key : "banana"}
.
Let’s take another look at our current implementation of parseQueryString
:
Interactive code block
function parseQueryString(queryString) {
const queryParams = {};
const [key, value] = queryString.split("="); // will hold ['fruit', 'banana']
queryParams.key = value; // mutate the queryParams object
return queryParams;
}
On line 4, we’re declaring an identifier called key
. When parseQueryString
is called with "fruit=banana"
then key
will be assigned the value of "fruit"
.
We want to add a property name to the object that is the value of the
key
variable and not the string"key"
.
We can do this with square bracket notation:
Interactive code block
function parseQueryString(queryString) {
const queryParams = {};
const [key, value] = queryString.split("="); // will hold ['fruit', 'banana']
queryParams[key] = value; // will set the property name with the value of the key variable
return queryParams;
}
We can’t use dot syntax if we don’t know what the name of the key is going to be. Square bracket notation is more powerful than dot notation, because it lets us use variables as keys.
We’ve currently got the following test suite:
Interactive code block
describe("parseQueryString()", () => {
test("given a queryString with no query parameters, returns an empty object", function () {
const input = "";
const currentOutput = parseQueryString(input);
const targetOutput = {};
expect(currentOutput).toStrictEqual(targetOutput);
});
test("given a queryString with one pair of query params, returns them in object form", function () {
const input = "fruit=banana";
const currentOutput = parseQueryString(input);
const targetOutput = { fruit: "banana" };
expect(currentOutput).toStrictEqual(targetOutput);
});
});
We’ve currently got the following test suite:
Explain why this test case is no longer passing for the first test case. Playing computer will help you to explain why!
Sometimes when we’re solving a problem, it can be useful to work out different cases (like empty query strings, or non-empty query strings) and work out how to solve them separately, then come back when we think we understand the cases and work out how to put the solutions together into one function. This often is useful when there are really different cases to consider.
Most of the time, though, it’s useful to try to keep all of our existing tests passing as we cover more cases. If we wanted to do that here, we could make our function be something like:
Interactive code block
function parseQueryString(queryString) {
const queryParams = {};
if (queryString.length === 0) {
return queryParams;
}
const [key, value] = queryString.split("="); // will hold ['fruit', 'banana']
queryParams[key] = value; // will set the property name with the value of the key variable
return queryParams;
}
Here, we only add a key to the object if there was actually something to add - we return early if there’s no extra work to do.
โโโ Multiple parameters
Learning Objectives
Let’s consider the case when there are multiple query parameters in the query string.
Recall
&
.Interactive code block
test("given a query string with multiple key-value pairs, returns them in object form", function () {
const input = "sort=lowest&colour=yellow";
const currentOutput = parseQueryString(input);
const targetOutput = { sort: "lowest", colour: "yellow" };
expect(currentOutput).toStrictEqual(targetOutput);
});
๐งญ Strategy
We’ve already worked out how to update the query params object given a single key-value pair in the query string.
To work out our strategy, let’s consider what we already know how to do. We already know how to take a key-value pair as a string, and add it to our object.
๐ก Key insight: If we can do it for one pair, we can try doing it for a list of pairs too.
So we’re missing a step - breaking up the string of multiple key-value pairs into an array where each element is a single key-value pair. If we do this, then we can iterate over the array, and do what we already know how to do on each key-value pair.
Our strategy will be to break the query string apart into an array of key-value pairs. Once we’ve got an array we can try iterating through it and storing each key value pair inside the queryParams
object.
Let’s start with the first sub-goal.
๐ฏ Sub-goal 1: split the query string into an array of key-value pairs
Query strings with multiple key-value pairs use &
as a separator e.g. sort=lowest&colour=yellow
. We want to split sort=lowest&colour=yellow
into ["sort=yellow", "colour=yellow"]
. We can achieve this by calling split
with the "&"
separator.
Interactive code block
function parseQueryString(queryString) {
// suppose queryString has a value of "sort=lowest&colour=yellow"
const queryParams = {};
const keyValuePairs = queryString.split("&"); // keyValuePairs will point to ["sort=yellow", "colour=yellow"]
}
๐ฏ Sub-goal 2: add each key-value pair in the array to the query params object
Once we’ve got an array we can iterate through the key-value pairs and update the queryParams
object each time (like we did when we just had one key-value pair).
Interactive code block
function parseQueryString(queryString) {
// assume queryString has a value of "sort=lowest&colour=yellow"
const queryParams = {};
const keyValuePairs = queryString.split("&"); // keyValuePairs will point to ["sort=yellow", "colour=yellow"]
for (const pair of keyValuePairs) {
const [key, value] = pair.split("=");
queryParams[key] = value;
}
return queryParams;
}
Play computer with the implementation of parseQueryString
above and pay attention to how the queryParams
object is updated.
Now that we’ve worked out how to solve this problem in the case of multiple query parameters, let’s integrate that solution into our previous implementation, to make sure it works for all cases.
We can keep our if (queryString.length === 0) {
check from before. We don’t need to do anything special for the one-value case, as an array containing one element gets iterated the same as an array of multiple elements:
Interactive code block
function parseQueryString(queryString) {
const queryParams = {};
if (queryString.length === 0) {
return queryParams;
}
const keyValuePairs = queryString.split("&");
for (const pair of keyValuePairs) {
const [key, value] = pair.split("=");
queryParams[key] = value;
}
return queryParams;
}
When we’re solving problems involving several values, often we need slightly differently handling for the cases when there are 0, 1, or more than 1 values. In our example here, we need to treat 0 values specially (if the query string is empty, we return early), but we can handle 1 and more than 1 the same way.
When you’re breaking down problems, think to yourself: What are special cases we may need to handle differently?
Prep Conflict Resolution ๐
Learning Objectives
Introduction
People at work will inevitably disagree with each other at some time. Most people want to do whatโs best for them and those around them. Often, conflicts arise from misunderstandings or different attitudes to general problem-solving.
These exercises will ensure you understand the theory and have reflected on this theme, so you can do the in-class exercises more effectively.
How to deal with workplace conflicts
๐ฏ Goal: Understand tips that will help you to handle conflicts on day-to-day basis. (7 minutes)
Watch the video โHow to deal with workplace conflicts - Develop your personality and business skillsโ.Conflict Resolution Curve
๐ฏ Goal: Understand what is meant by Conflict Resolution (30 minutes)
Read about the Conflict Resolution Curve on WikipediaProtective Factors
๐ฏ Goal: Understand and reflect on your protective factors (20 minutes)
- Read this text: โProtective Factorsโ
- Answer the reflective questions.
Reflect on your own conflict experience
๐ฏ Goal: Reflect on your conflict experience (30 minutes)
Think of a time you have had a conflict with a work colleague.
Answer these questions in a Google Doc:
- Was there a misunderstanding of the facts?
- Have you learned different lessons from your experience which affected your views?
- Did either of you have a hidden agenda directly opposed to the other person?
- How did either of you try to resolve the conflict?
- What was the outcome?