Reduce does not give me the answer I expect



I have been struggeling with a “simple” task for some time now and have figured out how to solve the problem in an alternative way. But I still would like to know what I was doing wrong in my first attempt where I used reduce. My goal is to count the number of blogs for each author so that I get this result: { 'Michael Chan': 1, 'Edsger W. Dijkstra': 2, 'Robert C. Martin': 3 }

My first attempt:

blogs.reduce((acc,curr)=> acc[curr.author] ? acc[curr.author]+=1:acc[curr.author]=1,{})

This always returns 1

If I try what I understand is the same thing but with more code:

let temp = {}
blogs.forEach(blog => {
  if(temp[blog.author]){
    temp[blog.author]+=1
  }else{
    temp[blog.author] =1
  }

This works and gives me: { 'Michael Chan': 1, 'Edsger W. Dijkstra': 2, 'Robert C. Martin': 3 }

The blogarray that I am using:

const blogs = [
    {
      _id: "5a422a851b54a676234d17f7",
      title: "React patterns",
      author: "Michael Chan",
      url: "https://reactpatterns.com/",
      likes: 7,
      __v: 0
    },
    {
      _id: "5a422aa71b54a676234d17f8",
      title: "Go To Statement Considered Harmful",
      author: "Edsger W. Dijkstra",
      url: "http://www.u.arizona.edu/~rubinson/copyright_violations/Go_To_Considered_Harmful.html",
      likes: 5,
      __v: 0
    },
    {
      _id: "5a422b3a1b54a676234d17f9",
      title: "Canonical string reduction",
      author: "Edsger W. Dijkstra",
      url: "http://www.cs.utexas.edu/~EWD/transcriptions/EWD08xx/EWD808.html",
      likes: 12,
      __v: 0
    },
    {
      _id: "5a422b891b54a676234d17fa",
      title: "First class tests",
      author: "Robert C. Martin",
      url: "http://blog.cleancoder.com/uncle-bob/2017/05/05/TestDefinitions.htmll",
      likes: 10,
      __v: 0
    },
    {
      _id: "5a422ba71b54a676234d17fb",
      title: "TDD harms architecture",
      author: "Robert C. Martin",
      url: "http://blog.cleancoder.com/uncle-bob/2017/03/03/TDD-Harms-Architecture.html",
      likes: 0,
      __v: 0
    },
    {
      _id: "5a422bc61b54a676234d17fc",
      title: "Type wars",
      author: "Robert C. Martin",
      url: "http://blog.cleancoder.com/uncle-bob/2016/05/01/TypeWars.html",
      likes: 2,
      __v: 0
    }  
  ]

Answer

With reduce, the value of the accumulator in the next iteration will be the value returned from the callback in the prior iteration. So, your original code of:

blogs.reduce((acc,curr)=> acc[curr.author] ? acc[curr.author]+=1:acc[curr.author]=1,{})

is carrying out a process equivalent to:

let result = {};
for (const curr of blogs) {
  result = acc[curr.author]
    ? acc[curr.author] += 1
    : acc[curr.author] = 1;
}

But assignments evaluate to the value of what was assigned. For example

let val1;
const someVal = (val1 = 15);

will result in someVal being 15, since 15 was the value assigned to val1.

You’ll need to return the acc separately for reduce to work.

blogs.reduce((acc,curr)=> {
  acc[curr.author] ? acc[curr.author]+=1:acc[curr.author]=1;
  return acc;
} ,{})

Or, I’d suggest not using reduce here at all, it doesn’t really help over a plain for loop.

const result = {};
for (const blog of blogs) {
  result[blog.author] = (result[blog.author] || 0) + 1;
}


Source: stackoverflow