1

I have a list of objects like

const usageCosts = {
    224910186407: {
        deviceId: "224910186407",
        currency: "GBP",
        yearlyUsage: 1480.81
    },
    224910464538: {
        deviceId: "224910464538",
        currency: "GBP",
        yearlyUsage: 617.36
    },
    224910464577: {
        deviceId: "224910464577",
        currency: "EUR",
        yearlyUsage: 522.3
    }
}

I'm reducing it to sum by currency like

 const totalYearlyCost = Object.values(usageCosts).reduce(
      (acc: { [key: string]: any }, stat: any) => {
        if (stat.currency && !acc[stat.currency]) {
          acc[stat.currency] = 0
        }
        return {
          ...acc,
          [stat.currency!]: acc[stat.currency!] + stat.yearlyUsage,
        }
      },
      {},
    )

and it returns an object like

{
EUR: 522.3
GBP: 2,098.17
}

I want to also return the total devices of each currency, something like:

{
EUR: 522.3 (1 device)
GBP: 2,098.17 (2 devices)
}

tried to add another loop but it's not working as expected

Marks
  • 11
  • 1
  • 1
    Your original result is key: string, value: number. By adding `(n device)` you're making the value a string. Is that what you want? Will it interfere with other code used to access the data? Would it be better to have an object like `EUR: { value: 522.3, devices: 1 }`? – Andy Aug 14 '23 at 13:59
  • I would really consider @Andy's suggestion if I were you. – Roko C. Buljan Aug 14 '23 at 14:00

4 Answers4

0

It's a lot easier to do this in 2 parts.

First reduce it to an array with the grouped values.

Then loop over (could also a reduce ofc) the object, and get the sum of the array, and add ${array.length} devices to the string:

const usageCosts = {
    224910186407: {
        deviceId: "224910186407",
        currency: "GBP",
        yearlyUsage: 1480.81
    },
    224910464538: {
        deviceId: "224910464538",
        currency: "GBP",
        yearlyUsage: 617.36
    },
    224910464577: {
        deviceId: "224910464577",
        currency: "EUR",
        yearlyUsage: 522.3
    }
}

let grouped = Object.values(usageCosts).reduce((p, c) => {
    if (!p[c.currency]) p[c.currency] = [];
    p[c.currency].push(c.yearlyUsage);
    return p;
}, {});

for (var key in grouped) {
    grouped[key] = `${grouped[key].reduce((a,b)=>a+b)} (${grouped[key].length}) devices`;
}

console.log(grouped)
0stone0
  • 34,288
  • 4
  • 39
  • 64
0

here is a solution:

const result = Object.values(usageCosts).reduce((acc, stat) => {
    if (stat.currency && !acc[stat.currency]) {
        acc[stat.currency] = {
            total: 0,
            devices: 0,
        }
    }

    return {
        ...acc,
        [stat.currency]: {
            total: acc[stat.currency].total + stat.yearlyUsage,
            devices: acc[stat.currency].devices + 1,
        }
    };
}, {});

As a result, you will get the following structure:

{
  GBP: { total: 2098.17, devices: 2 },
  EUR: { total: 522.3, devices: 1 }
}
  • This output structure is not the same as OP states in his question. – 0stone0 Aug 14 '23 at 13:51
  • 2
    @0stone0 it's a far better output. Sometimes OP don't really understand the topic or know what they're after. There's nothing complicated in retrieving the desired concatenation of values at a later time. All it takes is a loop - anyways. – Roko C. Buljan Aug 14 '23 at 14:01
  • @0stone0 because it can't be the same. Roko C. Buljan, thanks :) – Sviatoslav Kliuchev Aug 14 '23 at 14:10
0

A far better solution would be to get an output like {"CUR": {yearlyUsage: N, devices: N}, } - it's more flexible and reusable. Concatenating the values at a later time is also simple:

const usageCosts = {
  3: {deviceId: "3", currency: "GBP", yearlyUsage: 1480.81},
  5: {deviceId: "5", currency: "GBP", yearlyUsage: 617.36},
  7: {deviceId: "7", currency: "EUR", yearlyUsage: 522.3},
};

const byCurrency = Object.values(usageCosts).reduce((ob, {currency, yearlyUsage}) => {
  ob[currency] ??= {yearlyUsage: 0, devices: 0};
  ob[currency].yearlyUsage += yearlyUsage;
  ob[currency].devices += 1;
  return ob;
} , {});


console.log(byCurrency);

// Desired stringified output:
console.log(Object.entries(byCurrency).map(([k,v]) => `${k}: ${v.yearlyUsage} (${v.devices} device${v.devices<2?"":"s"})`).join("\n"))
.as-console-wrapper { min-height: 100dvh !important; top: 0; }

The resulting output of byCurrency is:

{
  "GBP": {
    "yearlyUsage": 2098.17,
    "devices": 2
  },
  "EUR": {
    "yearlyUsage": 522.3,
    "devices": 1
  }
}

and the second custom output (concatenated values):

GBP: 2098.17 (2 devices)
EUR: 522.3 (1 device)
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
0

You can do the following:

  1. Access the values of the object and reduce by the currency and keep track of a count and total
  2. Access the entries of the Map, sort by the key (currency), and reduce into an object where the key is the currency and the value is the formatted count and total

const usageCosts = {
  224910186407: { deviceId: "224910186407", currency: "GBP", yearlyUsage: 1480.81 },
  224910464538: { deviceId: "224910464538", currency: "GBP", yearlyUsage:  617.36 },
  224910464577: { deviceId: "224910464577", currency: "EUR", yearlyUsage:   522.3 }
};

const summary =
  // Part 1
  Array.from(Object.values(usageCosts)
    .reduce((acc, { currency, yearlyUsage }) => 
      (({ count = 0, total = 0 }) =>
        acc.set(currency, { count: count + 1, total: total + yearlyUsage }))
      (acc.get(currency) ?? {}), new Map)
    .entries())
  // Part 2
  .sort(([a], [b]) => a.localeCompare(b)) // Sort keys
  .reduce((acc, [currency, { count, total }]) => Object.assign(acc, {
    [currency]: `${total} (${count} device${count > 1 ? 's' : ''})`
  }), {});

console.log(summary);
.as-console-wrapper { top: 0; max-height: 100% !important }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132