JavaScript Practice: Arrays, Loops, Objects, Iterators: Groceries exercise efficiency

Hi all, I’ve completed this part of the practice session and got the intended outcome, but I feel like my answer is pretty convoluted. I would love some advice on how to make this more efficient!

Instructions:

Write a function groceries() that takes an array of object literals of grocery items. The function should return a string with each item separated by a comma except the last two items should be separated by the word 'and'. Make sure spaces (' ') are inserted where they are appropriate.

Examples:

groceries( [{item: 'Carrots'}, {item: 'Hummus'}, {item: 'Pesto'}, {item: 'Rigatoni'}] );
// returns 'Carrots, Hummus, Pesto and Rigatoni'

groceries( [{item: 'Bread'}, {item: 'Butter'}] );
// returns 'Bread and Butter'

groceries( [{item: 'Cheese Balls'}] );
// returns 'Cheese Balls'

My code:

const groceries = arr => {
  const newArray =[];
  for (let i = 0; i < arr.length; i++){
    newArray.push(arr[i].item);
    //console.log(newArray);
  }

  if (newArray.length > 2){
    for (let j = 0; j < newArray.length - 2; j++) {
      newArray[j] = newArray[j]+',';
    }
  }

  if (newArray.length > 1){
    newArray.splice(-1, 0, 'and');
      return newArray.join(' ');
  } else {
      return newArray[0];
  }
}

Any advice would be greatly appreciated.

You iterate over the array three times, that could be done in one. Then you would have a better runtime and less code:
One very easy step would be to omit the first for loop. In the second, you could then do:

newArray[j] = arr[j].item + ', '; // already include the space

Then you could append the last item like:

newArray.push(` and ${arr[arr.length-1].item}`)

You could also try the reduce() method.

1 Like

Ah I really like the

newArray[j] = arr[j].item + ', '; 

But in the condition I’d have to use arr.length - 2 instead of newArray.length - 2 right?

I somehow forgot I could simply just assign values of indexes directly. I’ve been so focused on using methods throughout this module - a good reminder to talk it through with myself first!

Thanks for your response!

1 Like

Inside a loop I’m not sure a splice is even necessary since we know when we reach the last item. @staledata shows an approach that would not use it.

To go off the beaten track, and perhaps off topic, so save this for later study, below is an approach for first constructing an array of item values, then a string from that, then a new string from that.

 > items = [{item: 'Carrots'}, {item: 'Hummus'}, {item: 'Pesto'}, {item: 'Rigatoni'}] 
<- (4) [{…}, {…}, {…}, {…}]
 > values = items.map(x => Object.values(x)[0])
<- (4) ['Carrots', 'Hummus', 'Pesto', 'Rigatoni']
 > str = values.join(', ')
<- 'Carrots, Hummus, Pesto, Rigatoni'
 > x = str.lastIndexOf(',')
<- 22
 > str = `${str.slice(0, x)} and ${str.slice(x + 2)}`
<- 'Carrots, Hummus, Pesto and Rigatoni'
 > console.log(`Grocery list: ${str}.`)
   Grocery list: Carrots, Hummus, Pesto and Rigatoni.
<- undefined
 > 

The lines of code are the ones entered at the prompt, >. We’ve written this code on the fly in the JavaScript console of Chrome. The code can be extracted to a text editor (sans prompt) for creation of the working function.

Below is constructed in the editor then copied as a block (between { and }) and pasted into the console.

 > {
   const groceries = function (items) {
     const str = items.map(x => Object.values(x)[0]).join(', ')
     const x = str.lastIndexOf(',')
     return x < 0 && str || `${str.slice(0, x)} and ${str.slice(x + 2)}`
   }
   let grocery_items;
   grocery_items = [
     {item: 'Carrots'}, 
     {item: 'Hummus'}, 
     {item: 'Pesto'}, 
     {item: 'Rigatoni'}
   ]
   console.log(`Grocery list: ${groceries(grocery_items)}.`)
   grocery_items = [ {item: 'Bread'}, {item: 'Butter'} ]
   console.log(`Grocery list: ${groceries(grocery_items)}.`)
   grocery_items = [ {item: 'Milk'} ]
   console.log(`Grocery list: ${groceries(grocery_items)}.`)
   }
   Grocery list: Carrots, Hummus, Pesto and Rigatoni.
   Grocery list: Bread and Butter.
   Grocery list: Milk.
<- undefined
1 Like

Right. A little refactoring is needed.

You could have the first edge case right below your string array initialization (const newArray =[];):

if(arr.length===1) return arr[0].item

Then you don’t need to wrap the for loop in another condition.
The loop is omitted if arr.length -2 is smaller or equal than the initial value 0:

for (let j = 0; j < arr.length - 2; j++) {
    newArray[j] = arr[j].item + ', ';
}

Then you add the last two items:

if (arr.length > 1){
    newArray.push(`${arr[arr.length-2].item} and ${arr[arr.length-1].item}`)
}

Then you finally iterate over the items array:

return newArray.join('')

That is your approach, a little more efficient. But you still need to iterate over two arrays.

If you use the reducer method, you can get away with just one iteration.

Code with reduce() method
const makeSentenceFromArray = (arr) => arr.reduce((acc, el, idx, arr) => {
	if(idx===0) return el.item
	if(idx===arr.length-1) return `${acc} and ${el.item}` 
	return `${acc}, ${el.item}`
}, '')

this is great and super clear! I just feel I’m not experienced enough at this point to ever have arrived there. Thank you for writing it all out.

1 Like

thanks for explaining! The reduce() method is a bit over my head right now

1 Like

One fully expected that this would be pure gibberish, so don’t feel as we’re expecting you to be grateful, or understand it. We simply sprang it upon you as a sneak peak to what you will be able to “arrive there” on your own, in due course of time. That is the goal. No pressure.

The subject of your lessons at present is, loops. I won’t characterize them in my own view except to say they are the mustard that goes with the frank in a hot dog. Take them out of the picture and programming is no longer possible. That is how important this concept is to programming in any language, even machine language. It’s all centered around loops.

Loops involve iteration and in your studies you will learn about ‘iterators’, of which .map() above is one. It’s essentially a self managing loop. That is how we could iterate over all the items in the input array.

The subject of Object should come up at some point, even if it is happensatance to your extra reading. It bears extra study. Plain objects do not inherit a, keys(), values(), or, items() method from their parent class’s prototype. We must go to the parent class itself to access those methods. Object is the parent class of all objects in JS. Hence,

Object.values(object)

Of course the return from that is an array, and all we want is the actual value,

Object.values(x)[0]

being the only, therefore first value in that array. This is a matter for extra study that while not important now, will be by the time you reach the end of this course. Definitely add it to your post-course reading/study/practice todo list.

In the course of your learning to apply Array methods, you will encounter the, Array.join(separator_string) method. It is the way we convert an array to a string object. Give that extra study when you learn the, String.split() method. They are inverse functions that work hand in hand through a lot of string manipulation logic. While easy to learn, it is difficult to apply. Practice will show us that, and will enable us to circumvent or mitigate special cases.

We used some rather off the cuff logic in the example that no doubt would raise a few eyebrows among unseasoned readers, namely new learners who have not yet been exposed to logical operations. If nothing else stumped you, that return line would surely have. It is tied to the return value of the method above it, something that will also need learning about and practice.

The only intention on my part was to expose you to what you will someday soon be doing on your own. It is not something we conceived of, out of the blue. It only came about in this fashion because of what we understand of what the language promises. That is why reading and practice are so important. To know a language means knowing all of its promises, over and above the syntax and usage aspects. Of course we don’t learn this stuff over night. In lessons and practice we begin to see with our own eyes and think with our own brain. By that and promises made by the language we see the range of possibilities that can be applied to solving any problem.

Stay the course in your lesson plan. Don’t try to supersede the object of the lesson; if anything, try to mete out as much information and clarity as you can before moving on to the next one. Let your brain soak this up, and keep your expectations low. It helps with the surprise when you reach the ‘eureka moment’.

Happy coding!

1 Like