1

I have an array of objects and I want to combine the objects that have the same date values so I have an array like. I am looping through them with .map(i => ) and chekcing on the previous index but i'm getting the wrong values.

 [{ id: '8',
   name: 'hfh',
   consumed_date: '2017-03-14',
   calories: 8952 },
 { id: '7',
   name: 'onion',
   consumed_date: '2017-03-14',
   calories: 224},
 { id: '6',
   name: 'onion',
   consumed_date: '2017-03-14',
   calories: 279},
 { id: '2',
   name: 'Ready-to-Serve Chocolate Drink',
   consumed_date: '2017-01-01',
   calories: 3036} ]

And i want to end up with an array like

[{date: 2017-03-14,
calories: 9455},
date: 2017-01-01,
calories: 3036}]
3
  • 1
    you should take a look at reduce. Commented Aug 18, 2017 at 0:08
  • Like in your example, you might have data that combines into an array with length > 1. So, array.reduce is not going to work, since I think reduce ALWAYS gives one result back. Commented Aug 18, 2017 at 0:19
  • But you can initialize it with [] and modify this array inside the reduction. Commented Aug 18, 2017 at 0:27

8 Answers 8

1

Reduce is perfect for situations like this: we need to "loop" through all items but we don't want the straight-forward "a => modify(a)"-like behavior of map, but rather need to reduce or collapse several values into something else (in this case both input and output is an array but some of the values are reduced to just one).

We need some variable to hold state (the progress so far) while we loop through all the items. In a more imperative style that would be solved by a variable declared outside of a for-loop, but in reduce that "progress so far"-variable (acc) is passed along for each iteration, along with the next item.

const reducedArray = arr.reduce((acc, next) => { // acc stands for accumulator
  const lastItemIndex = acc.length -1;
  const accHasContent = acc.length >= 1;

  if(accHasContent && acc[lastItemIndex].consumed_date == next.consumed_date) {
    acc[lastItemIndex].calories += next.calories;
  } else {
    // first time seeing this entry. add it!
    acc[lastItemIndex +1] = next;
  }
  return acc;
}, []);

console.log(reducedArray)

(This solution assumes data sorted on date.)

Sign up to request clarification or add additional context in comments.

5 Comments

How can i do this so that it pushes into a new array with just calorie and date values?
@elcapitan Then instead of adding the whole item in this line acc[lastItemIndex +1] = next;, you create a new object with only the properties you're interested in. Like so: ´´acc[lastItemIndex +1] = { consumed_date: next.consumed_date, calories: next.calories };´´. You can run the whole code here: jsfiddle.net/jonahe/zns9y3ws
@elcapitan Sorry I messed up the formatting. It should have been: acc[lastItemIndex +1] = { consumed_date: next.consumed_date, calories: next.calories };. Be sure to ask more questions if you have any. Did it work as expected now?
wow. Can't believe i didn't see that. This is an awesome solution!! Thanks so much!
@elcaptain No problem! :) Consider marking the answer as correct if you feel it is.
1

First the data will be reduced by using the consumed_date as an index and than the whole thing will be converted to an array by Object.entries and mapping that result.

var json = [{ id: '8',
   name: 'hfh',
   consumed_date: '2017-03-14',
   calories: 8952 },
 { id: '7',
   name: 'onion',
   consumed_date: '2017-03-14',
   calories: 224},
 { id: '6',
   name: 'onion',
   consumed_date: '2017-03-14',
   calories: 279},
 { id: '2',
   name: 'Ready-to-Serve Chocolate Drink',
   consumed_date: '2017-01-01',
   calories: 3036} ];
   
  var newar = json.reduce((acc, val) => {
    if(acc[val.consumed_date] === undefined) {
      acc[val.consumed_date] = { date: val.consumed_date, calories: val.calories };
    }
    else {
      acc[val.consumed_date].calories = acc[val.consumed_date].calories + val.calories;
    }

    return acc;
   }, []);
   
   document.getElementById("content").innerHTML = JSON.stringify(Object.entries(newar).map((e) => e[1]));
<pre id="content"></pre>

Comments

0

Probably something like this:

Object.values(arr.reduce(function(obj, entry){
    if(!(entry.consumed_date in obj)){
        obj[entry.consumed_date] = {
            date: entry.consumed_date,
            calories: 0
        };
    }
    obj[entry.consumed_date].calories += entry.calories;

    return obj;
}, {}));

https://jsfiddle.net/DerekL/9gyL9dfs/

Comments

0

This makes heavy use of ES 6 features like arrow functions, parameter destructuring, Object.entries, object shorthand properties, etc. Make sure to transpile.

Object.entries(arr.reduce((acc, record) => {
  if (!acc[record.consumed_date]) acc[record.consumed_date] = 0;
  acc[record.consumed_date] += record.calories;
  return acc;
}, {})).map(([date, calories]) => {date, calories});

Comments

0
let array =  [{ id: '8',
    name: 'hfh',
    consumed_date: '2017-03-14',
    calories: 8952 },
    { id: '7',
        name: 'onion',
        consumed_date: '2017-03-14',
        calories: 224},
    { id: '6',
        name: 'onion',
        consumed_date: '2017-03-14',
        calories: 279},
    { id: '2',
        name: 'Ready-to-Serve Chocolate Drink',
        consumed_date: '2017-01-01',
        calories: 3036} ]

let newArray = array.reduce((sum, value)=>{
    if(value.consumed_date === '2017-03-14') {
        return sum.concat(value)
    }else{
        return sum
    }
}, []);

console.log(newArray);

Comments

0
var after = {},
    found;

for (var i = 0; i < arr.length; i++) {
  found = false;
  for (var j = 0; j < after.length; j++) {
    if (arr[i]['consumed_date'] == after[j]['date']) {
      after[j]['calories'] += arr[i]['calories'];
      found = true;
      break;
    }
  }
  if (!found) {
    after[after.length] = {
      'date': arr[i]['consumed_date'],
      'calories': arr[i]['calories']
    }
  }
}

Name the larger array (containing daily entries) arr, or change arr throughout the code. The values will then be assigned to after as requested.

NOTE Rather poor example here, also not tested, but I've tried my best :/

1 Comment

If I've made a silly mistake here, please point out and forgive me haha. I'm just getting back into web development again, so I'm a little rusty. EDIT - Just read through, will fix it now as it obviously won't work
0

For each item in the array, check if we have seen it already. If so add the calories. If not add a new item to the combined list.

if you are okay with using Lodash:

var _ = require('lodash')

// Initial Data
var myArray =  [{ 
  id: '8',
  name: 'hfh',
  consumed_date: '2017-03-14',
  calories: 8952 
},
{ 
  id: '7',
  name: 'onion',
  consumed_date: '2017-03-14',
  calories: 224
},
{ 
  id: '6',
  name: 'onion',
  consumed_date: '2017-03-14',
  calories: 279
},
{ 
  id: '2',
  name: 'Ready-to-Serve Chocolate Drink',
  consumed_date: '2017-01-01',
  calories: 3036
}]

// sum the calories over the dates
var result = _(myArray)
  .groupBy('consumed_date')
  .map( (item, key) => ({
    date: key,
    calories: _.sumBy(item, 'calories')
  }))
  .value()

console.log('result', result)

or you can do on your own with something like this:

// Initial Data
var myArray =  [{ 
  id: '8',
  name: 'hfh',
  consumed_date: '2017-03-14',
  calories: 8952 
},
{ 
  id: '7',
  name: 'onion',
  consumed_date: '2017-03-14',
  calories: 224
},
{ 
  id: '6',
  name: 'onion',
  consumed_date: '2017-03-14',
  calories: 279
},
{ 
  id: '2',
  name: 'Ready-to-Serve Chocolate Drink',
  consumed_date: '2017-01-01',
  calories: 3036
}]

// helper find function
function getByDate(list, date) {
  function dateMatches (item) {
    return item.date === date
  }
  return list.find(dateMatches)
}

// get combined result
var combinedArray = []

myArray.forEach(function (item) {
  var previousMatch = getByDate(combinedArray, item.consumed_date)
  if (previousMatch) {
    previousMatch.calories += item.calories
  }
  else
  {
    combinedArray.push({
      date: item.consumed_date,
      calories: item.calories
    })
  }
})

console.log(combinedArray)

Comments

0

If you find the reduce() method confusing, here's another way:

array = [
    { 
        id: '8',
        name: 'hfh',
        consumed_date: '2017-03-14',
        calories: 8952 
    },
    {
        id: '7',
        name: 'onion',
        consumed_date: '2017-03-14',
        calories: 224
    },
    {
        id: '6',
        name: 'onion',
        consumed_date: '2017-03-14',
        calories: 279},
    { 
        id: '2',
        name: 'Ready-to-Serve Chocolate Drink',
        consumed_date: '2017-01-01',
        calories: 3036
    }
]


function combineObj(data) {

    let validator= new Set();
    let combinedArr = [];
    let caloriesCount = 0;
    
    // Create a list of unique properties to match against:
    data.forEach((e) => validator.add(e.consumed_date));

    // For each value in the validator, create a new object in a new array (combinedArr)
    // and add the unique values from the validator to the respective property:
    validator.forEach((e) => {
        combinedArr.push({
            name: "",
            consumed_date: e,
            calories: 0
        });
    })

    // Lastly, for each object in the combinedArr, use a counter to sum up the total values of each property
    // as you loop through your data:
    combinedArr.forEach((e) => {
        caloriesCount = 0;
        data.forEach((ee) => {
            if (e.consumed_date === ee.consumed_date) {
                caloriesCount += ee.calories;
                e.calories = caloriesCount;
            }
        })
    })

    return combinedArr;
}

Returns:

[
  { name: '', consumed_date: '2017-03-14', calories: 9455 },
  { name: '', consumed_date: '2017-01-01', calories: 3036 }
]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.