Burton
Burton

Reputation: 447

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
    }  
  ]

Upvotes: 0

Views: 52

Answers (4)

Andy
Andy

Reputation: 63587

It's because you're not combining the value of the current property with the accumulator, and returning that result. This maybe simpler:

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}];

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

console.log(result);

Upvotes: 1

Rinkal Rohara
Rinkal Rohara

Reputation: 352

Ternary operator cannot be used with complete statements. The below would have been correct implementation:

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

Upvotes: 1

trincot
trincot

Reputation: 351403

The value that the reduce callback returns, should be the accumulator, yet your callback returns the value of an assignment, which is the assigned value, not the object whose property is getting that value.

So if you really want to stay with a single-expression-callback, use the comma operator to still return the accumulator:

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

Upvotes: 3

CertainPerformance
CertainPerformance

Reputation: 371233

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;
}

Upvotes: 4

Related Questions