-1

I have an array of objects something like the following: -

0: {Id: 0, count1: 5, count2: 10, yearMonth: "201803"}
1: {Id: 0, count1: 10, count2: 0, yearMonth: "201804"}
2: {Id: 1, count1: 900, count2: 200, yearMonth: "201805"}
3: {Id: 0, count1: 10, count2: 0, yearMonth: "201806"}
4: {Id: 1, count1: 100, count2: 100, yearMonth: "201807"}
5: {Id: 1, count1: 100, count2: 2, yearMonth: "201808"}

I want to sum similar keys to get Expected output:

1: {Id: 0, count1: 25, count2: 10}
2: {Id: 1, count1: 1100, count2: 402}

Is it possible to achieve it using any mathematical operation or reduce or any other javascript method?

Thanks

2
  • 2
    Is it possible to achieve it using any mathematical operation or reduce or any other javascript method? - yes Commented Oct 9, 2018 at 11:33
  • 1
    Welcome to SO, @Sac. The reason your question has been downvoted is that your question doesn't indicate that you've attempted to solve this problem yourself. You might find reading the help quide on how to ask a good question useful. Commented Oct 9, 2018 at 11:37

2 Answers 2

1

With knockout, you can use a chain of computed properties to get to the desired (UI?) format!

Disclaimer: I'm assuming this list will not contain thousands of items

1. Grouping

The first step is to go from a list (ko.observableArray([])) of items to a computed object that groups by id:

// Search for "group by javascript" to have this function explained
const groupBy = (prop, xs) => xs.reduce(
  (acc, x) => Object.assign(acc, { [x[prop]]: (acc[x[prop]] || []).concat(x) }), {}
);

const items = ko.observableArray([]);

const itemsById = ko.pureComputed(() =>
  groupBy("Id", items())
);

itemsById.subscribe(console.log);
items([{Id: 0, count1: 5, count2: 10, yearMonth: "201803"},{Id: 0, count1: 10, count2: 0, yearMonth: "201804"},{Id: 1, count1: 900, count2: 200, yearMonth: "201805"},{Id: 0, count1: 10, count2: 0, yearMonth: "201806"},{Id: 1, count1: 100, count2: 100, yearMonth: "201807"},{Id: 1, count1: 100, count2: 2, yearMonth: "201808"}]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

2. Merging

Now that we have grouped lists of items that need to be summarized, we can start applying our merge logic:

const itemsWithSameId = [{Id:0,count1:5,count2:10,yearMonth:"201803"},{Id:0,count1:10,count2:0,yearMonth:"201804"},{Id:0,count1:10,count2:0,yearMonth:"201806"}];

const merge = (itemA, itemB) => ({
  Id: itemB.Id,
  count1: itemA.count1 + itemB.count1,
  count2: itemA.count2 + itemB.count2
});

// Look up "merging objects using reduce in javascript" to find out more
console.log(
  itemsWithSameId.reduce(merge, { count1: 0, count2: 0 })
)

3. Going back from indexed object to array

Now that we know how to merge our groups, we can move back to the array we need in our UI:

// Utilities:
const groupBy = (prop, xs) => xs.reduce(
  (acc, x) => Object.assign(acc, {
    [x[prop]]: (acc[x[prop]] || []).concat(x)
  }), {}
);

// Data Logic:
const merge = (itemA, itemB) => ({
  Id: itemB.Id,
  count1: itemA.count1 + itemB.count1,
  count2: itemA.count2 + itemB.count2
});

// App
const items = ko.observableArray([]);
const itemsById = ko.pureComputed(() =>
  groupBy("Id", items())
);

// Look up "mapping over the values of a javascript object" for more info
const summedItems = ko.pureComputed(() =>
  Object
    .values(itemsById())
    .map(items => items.reduce(merge, { count1: 0, count2: 0 }))
);

// Apply bindings with viewmodel exposing summedItems
ko.applyBindings({ summedItems });

// Inject data (probably in success callback of ajax call)
items([{Id: 0, count1: 5, count2: 10, yearMonth: "201803"},{Id: 0, count1: 10, count2: 0, yearMonth: "201804"},{Id: 1, count1: 900, count2: 200, yearMonth: "201805"},{Id: 0, count1: 10, count2: 0, yearMonth: "201806"},{Id: 1, count1: 100, count2: 100, yearMonth: "201807"},{Id: 1, count1: 100, count2: 2, yearMonth: "201808"}]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<table>
  <thead>
    <tr>
      <th>Id</th>
      <th>Count 1</th>
      <th>Count 2</th>
    </tr>
  </thead>
  <tbody data-bind="foreach: summedItems">
    <td data-bind="text: Id"></td>
    <td data-bind="text: count1"></td>
    <td data-bind="text: count2"></td>
  </tbody>
</table>

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

1 Comment

You wrote this in less than 25 min? :O
0

You could reduce the array by using a hash table and an array of keys for summing the wanted values.

var data = [{ Id: 0, count1: 5, count2: 10, yearMonth: "201803" }, { Id: 0, count1: 10, count2: 0, yearMonth: "201804" }, { Id: 1, count1: 900, count2: 200, yearMonth: "201805" }, { Id: 0, count1: 10, count2: 0, yearMonth: "201806" }, { Id: 1, count1: 100, count2: 100, yearMonth: "201807" }, { Id: 1, count1: 100, count2: 2, yearMonth: "201808" }],
    keys = ['count1', 'count2'],
    grouped = Object.values(data.reduce((result, object) => {
        result[object.Id] = result[object.Id] || { Id: object.Id };
        keys.forEach(key => result[object.Id][key] = (result[object.Id][key] || 0) + object[key]);
        return result;
    }, Object.create(null)));

console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }

1 Comment

Thanks for your time and efforts, This did work for me. :-)

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.