JS: Working with Arrays of Objects

In this post, I'll run through an example of working with a simple array of objects.

The Data

Take the following data as the foundation of our example:

const data = [
  {
    origin: 'London',
    destination: 'Paris',
    departureTime: '10:15 AM',
    departureDate: '10 February 2021',
    price: 156.45
  },
  {
    origin: 'London',
    destination: 'Paris',
    departureTime: '12:15 AM',
    departureDate: '10 February 2021',
    price: 106.99
  },
  {
    origin: 'London',
    destination: 'Paris',
    departureTime: '15:15 AM',
    departureDate: '10 February 2021',
    price: 217
  }
];

What we call data here is a constant equal to an array of objects.

So what do we want to do with this data?

Let's say our task is to find the cheapest trip.
As such, lets set our end goal as requiring a function that will return:

"London to Paris, departing on the 10 February 2021 at 12:15AM for £106.99"

This is the cheapest trip from our small subset of data.

The Approach

First of all, let's break this down into manageable chunks.

Let's say that to begin with, we simply want to figure out the cheapest price.

const findCheapestTrip = (trips) => {

    // map prices into an array of prices
    const prices = trips.map(trip => {
        return trip.price
       });

    return  prices;
}

Above, we use the in-built array.map method to create a new array of only the prices.

What we are doing in practice (assuming that trips is data when we call the function later on), is we are saying that for every element (which we are calling trip) in the array (i.e. every object in the array in this case), return the value of the property price (trip.price). And by default, the array.map method returns a new array with said values.

Lets call our findCheapestTrip function below, passing in our data to see this in action:

console.log(findCheapestTrip(data));

What we get back when we run this is a new array of only the prices:

[ 156.45, 106.99, 217 ]

This now makes it very easy for us to identify the cheapest price, as we can simply spread the values in this array to Math.min() as follows:

const findCheapestTrip = (trips) => {

    // map prices into an array of prices
    const prices = trips.map(trip => {
        return trip.price
       });

    // find lowest price
    const cheapestPrice = Math.min(...prices);

    return  cheapestPrice;
}

console.log(findCheapestTrip(data));

With the above alteration, running this would now give us the cheapest price and return:

106.99

What are we missing here?

So far we've neatly managed to work out the cheapest price, but this isn't enough to print out our end goal string of:

London to Paris, departing on the 10 February 2021 at 12:15AM for £106.99.

Why?

Because we still need access to the other properties in the object that has the cheapest price. E.g. we need know the values of the origin, destination, departureTime etc... in order to form our final string.

What does this mean?

It means we don't actually want to find the cheapest price. What we really want is to find and store the entire object that holds the cheapest price. This will allow us to reach our end goal.

Was our work above wasted?

No, because our key identifier for getting the whole object will in fact be based on the object with the cheapest price. So let's run through our next steps.

We want to update our code to return the object in our array of objects which has the lowest price. We already have this stored in our cheapestPrice variable, so all we have to do is filter our data for it using JavaScript's built-in array method, filter()

const findCheapestTrip = (trips) => {

    // map prices into an array of prices
    const prices = trips.map(trip => {
        return trip.price
       });

    // find lowest price
    const cheapestPrice = Math.min(...prices);

    // finds the element in the array that has the lowest 
    // price and returns the whole object
    const cheapestTrip = trips.filter(trip => trip.price === cheapestPrice) 

 return cheapestTrip;
}

console.log(findCheapestTrip(data));

Above, we've introduced a new variable called cheapestTrip, and inside it we run the array filter() method on our data, saying we want the element in our array who's trip.price is equal to the lowest value (a value we already have from our cheapestPrice variable).
We update the return to return cheapestTrip instead of cheapestPrice, and what we now get is an array with only the object holding the cheapest price:

[
  {
    origin: 'London',
    destination: 'Paris',
    departureTime: '12:15 AM',
    departureDate: '10 February 2021',
    price: 106.99
  }
]

Side Note: You could get back multiple objects in the array if they have the same price.

With this our goal is practically complete, as we now have access to all the data we need to print our end goal of:

"London to Paris, departing on the 10 February 2021 at 12:15AM for £106.99"

This is something we can produce simply now using string interpolation by updating our return to:

return `${cheapestTrip[0].origin} to ${cheapestTrip[0].destination}, departing on the ${cheapestTrip[0].departureDate} at ${cheapestTrip[0].DepartureTime} for £${cheapestTrip[0].price}`;

(We use [0] before calling each property because don't forget, we're still working in an array, and on the assumption that we only have one object element in the array with the lowest price (per our data set).

Still though, this is quite an ugly return, and we are repeating cheapestTrip[0] far too much. We can avoid this using destructuring.

Our finished code will look as follows:

const findCheapestTrip = (trips) => {

 // map prices into an array of prices --> [ 156.45, 106.99, 217 ]
 const prices = trips.map(trip => {
 return trip.price
     });

 // find lowest price
 const cheapestPrice = Math.min(...prices);

 // finds the element in the array that has the lowest price and returns the whole object
 const cheapestTrip = trips.filter(trip => trip.price === cheapestPrice) 

 // destructure properties we need to avoid repetition in the return string
 const { origin, destination, departureDate, departureTime, price } = cheapestTrip[0]

 return `${origin} to ${destination}, departing on the ${departureDate} at ${departureTime} for £${price}`;
}

console.log(findCheapestTrip(data));

This finally returns our end goal of:

London to Paris, departing on the 10 February 2021 at 12:15 AM for £106.99

We're Done!

But what's wrong with this solution now???

Although our solution is perfectly acceptable with working with such a small subset of data, it would not be as performant as possible should we have a larger dataset.

Why?

Because we are looping through our data on 2 separate occasions.
Once using map() when getting our array of prices, and a second time using filter() when grabbing our cheapest trip object. As such, we're creating a complexity level of 2n.

So the question is, can we achieve the above in a single loop through our data?
YES!

Here's a full working equivalent solution using the same dataset as before:

const data = [
  {
 origin: 'London',
 destination: 'Paris',
 departureTime: '10:15 AM',
 departureDate: '10 February 2021',
 price: 156.45
  },
  {
 origin: 'London',
 destination: 'Paris',
 departureTime: '12:15 AM',
 departureDate: '10 February 2021',
 price: 106.99
  },
  {
 origin: 'London',
 destination: 'Paris',
 departureTime: '15:15 AM',
 departureDate: '10 February 2021',
 price: 217
  }
];

const findCheapestTripIn1Loop = (trips) => {
 let lowestPrice = Infinity;
 let cheapestTrip;

 for (let i = 0; i < trips.length; i++) {
 if (trips[i].price < lowestPrice) {
 lowestPrice = trips[i].price;
 cheapestTrip = trips[i];
    }
  }

 const { origin, destination, departureTime, departureDate, price } = cheapestTrip;
 return `${origin} to ${destination}, departing ${departureDate} at ${departureTime} - £${price}`;
}

console.log(findCheapestTripIn1Loop(data));

In this solution, we've renamed our function to findCheapestTripIn1Loop - and here we use a traditional for() loop to conduct our logic in single iteration of the dataset.

21