Combine an array of objects and their properties. Remove object after combining

后端 未结 4 1684
梦如初夏
梦如初夏 2021-01-29 08:18

I need to merge the objects together. The resource property is what determines if the objects can be merged. To determine where the hours property valu

相关标签:
4条回答
  • 2021-01-29 08:28

    One technique is to loop over each member, find the first one with that resource, update the totals in it, and then filter so as to retain only the first occurrences.

    members.filter(member => {
      const first = members.find(m => m.resource === member.resource);
    
      if (member.billable) first.totalBillableHours += member.hours;
      else first.totalNonBillableHours += member.hours;
      first.totalHours += member.hours.
    
      return first === member;
    });
    

    A more orthodox approach would be to group the objects by resource, creating an array of objects for each resource, and then transform that into your desired output, as in

    totals(groupBy(members, 'resource'))
    

    groupBy would be defined as producing something of the form:

    {
      resource1: [obj, obj],
      resource2: [obj, obj]
    }
    

    To take totals first, that would be

    function totals(groups) {
      const hours    = m => m.hours;
      const billable = m => m.billable;
      const not      = f => x => !f(x);
    
      return Object.keys(groups).map(resource => {
        const members          = groups[resource];
        const totalHours       = sum(members.map(hours));
        const billableHours    = sum(members.filter(billable).map(hours));
        const nonBillableHours = sum(members.filter(not(billable)).map(hours));
    
        return {resource, totalHours, billableHours, nonBillableHours};
      });
    }
    

    sum can be written as

    const sum = arr => arr.reduce((a, b) => a + b, 0);
    

    There are many implementations of groupBy out there, including ones provided by libraries such as underscore. Here's a real simple version:

    function groupBy(arr, prop) {
      return arr.reduce((result, obj) {
        const key = obj[prop];
        if (!result[key]) result[key] = [];
        result[key].push(obj);
        return result;
      }, {});
    }
    
    0 讨论(0)
  • 2021-01-29 08:32

    The problem is you're relying on the adjacent record of each record. Instead, you could keep a tally of each member by their resource. Since resource is unique, you can use it as a property of an object that keeps the tally -- think of it like a key. Then you can add the number of hours in each record to the appropriate object.

    Here's my attempt: http://codepen.io/anon/pen/bBYyPa

    var members = [
    {billable: true, hours: 15, name: "Joe Smith", resource: "00530000003mgYGAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0},
    {billable: true, hours: 5, name: "Joe Smith", resource: "00530000003mgYGAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0},
    {billable: false, hours: 5, name: "Joe Smith", resource: "00530000003mgYGAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0},
    {billable: false, hours: 5, name: "Jan Smith", resource: "00530000003mgYTAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0},
    {billable: true, hours: 12, name: "Jan Smith", resource: "00530000003mgYTAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0},
    {billable: true, hours: 2, name: "Jam Smith", resource: "00530000003mgYTAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0}  
    ];
    
    var membersObj = {};
    
    for (i = 0; i < members.length; i++) {
    
      var member = members[i];
      if (!membersObj[member.resource]){
        membersObj[member.resource] = members[i];
      }
    
      if(member.billable){
        membersObj[member.resource].totalBillableHours += member.hours;
      } else {
        membersObj[member.resource].totalNonBillableHours += member.hours;
      }
    
      membersObj[member.resource].totalHours += member.hours;
    
    }
    console.log(membersObj);
    

    Of course, this gives you back an object instead of an array, but that can be converted if necessary.

    Here is the output:

    { 
    '00530000003mgYGAAY': 
       { billable: true,
         hours: 15,
         name: 'Joe Smith',
         resource: '00530000003mgYGAAY',
         totalBillableHours: 20,
         totalHours: 25,
         totalNonBillableHours: 5 },
    '00530000003mgYTAAY': 
       { billable: false,
         hours: 5,
         name: 'Jan Smith',
         resource: '00530000003mgYTAAY',
         totalBillableHours: 14,
         totalHours: 19,
         totalNonBillableHours: 5 } 
    }
    
    0 讨论(0)
  • 2021-01-29 08:40

    It gets pretty tricky when you remove items from an array while iterating over it.

    I've rewritten your solution in a more functional way here: http://codepen.io/tinacious/pen/gLXJow?editors=1011

    var members = [
      {billable: true, hours: 15, name: "Joe Smith", resource: "00530000003mgYGAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0},
      {billable: true, hours: 5, name: "Joe Smith", resource: "00530000003mgYGAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0},
      {billable: false, hours: 5, name: "Joe Smith", resource: "00530000003mgYGAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0},
      {billable: false, hours: 5, name: "Jan Smith", resource: "00530000003mgYTAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0},
      {billable: true, hours: 12, name: "Jan Smith", resource: "00530000003mgYTAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0},
      {billable: true, hours: 2, name: "Jam Smith", resource: "00530000003mgYTAAY", totalBillableHours: 0, totalHours: 0, totalNonBillableHours: 0}  
    ];
    
    function combineMembers(members) {
      var combinedMembers = {};
    
      members.forEach(function (member) {
        var resourceId = member.resource;
        var typeOfHour = member.billable ? 'totalBillableHours' : 'totalNonBillableHours';
    
        if (!combinedMembers[resourceId]) {
          combinedMembers[resourceId] = Object.assign({}, member);
        }
    
        combinedMembers[resourceId][typeOfHour] += member.hours;      
        combinedMembers[resourceId].totalHours += member.hours;
      });
    
      return Object.keys(combinedMembers).map(function (resourceId) {
        return combinedMembers[resourceId];
      });
    }
    
    console.log(combineMembers(members));
    

    The resulting output is what you are looking for:

    Array[2]
        0 : Object
            billable : true
            hours : 15
            name : "Joe Smith"
            resource : "00530000003mgYGAAY"
            totalBillableHours : 20
            totalHours : 25
            totalNonBillableHours : 5
            __proto__ : Object
        1 : Object
            billable : false
            hours : 5
            name : "Jan Smith"
            resource : "00530000003mgYTAAY"
            totalBillableHours : 14
            totalHours : 19
            totalNonBillableHours : 5
            __proto__ : Object
        length : 2
        __proto__ : Array[0]
    
    0 讨论(0)
  • 2021-01-29 08:41

    Here's a version using the Ramda library (disclaimer: I'm one of the authors):

    const process = pipe(
      groupBy(prop('resource')),
      values,
      map(group => reduce((totals, member) => ({
        name: member.name,
        resource: member.resource,
        totalHours: totals.totalHours + member.hours,
        totalBillableHours: totals.totalBillableHours + 
                (member.billable ? member.hours : 0),
        totalNonBillableHours: totals.totalNonBillableHours + 
                (member.billable ? 0 : member.hours)
      }), head(group), group))
    );
    

    With this,

    process(members)
    

    yields

    [
      {
        name: "Joe Smith",
        resource: "00530000003mgYGAAY",
        totalBillableHours: 20,
        totalHours: 25,
        totalNonBillableHours: 5
      },
      {
        name: "Jam Smith",
        resource: "00530000003mgYTAAY",
        totalBillableHours: 14,
        totalHours: 19,
        totalNonBillableHours: 5
      }
    ]    
    

    This works in two stages. First it collects the like values (using groupBy) and extracts the results as an array (using values).

    Then it maps over the resulting list of groups, reducing each one to a single value by combining the fields as appropriate.

    This might not be much of a help to you, as pulling in a library like Ramda for just one task is probably a ridiculous idea. But you might take inspiration in the breakdown of the problem.

    Most of the Ramda functions used here are easy to create on your own and are quite useful for many purposes.

    You can see this in action on the Ramda REPL.

    0 讨论(0)
提交回复
热议问题