caiovisk
caiovisk

Reputation: 3819

Array of objects reducing based on multiple parameters

Need to reduce an array of Objects to return the highest value of the same name.

Need the following,

[
    {
        name: 'a',
        value: 20,
        ...(other object values)
    },
    {
        name: 'a',
        value: 80
        ...(other object values)
    },
    {
        name: 'b',
        value: 90,
        ...(other object values)
    },
    {
        name: 'b',
        value: 50,
        ...(other object values)
    }
]

To return

[
    {
        name: 'a',
        value: 80
        ...(other object values)
    },
    {
        name: 'b',
        value: 90,
        ...(other object values)
    }
]

I have a solution, but it seems a little to much, wondering if there is a more straightforward way to achieve it?

Below is the solution I could think of:

var toReduce = [
    {
        'name': 'a',
        'value': 20,
        'other': 'any',
    },
    {
        'name': 'a',
        'value': 80,
        'other': 'value',
    },
    {
        'name': 'b',
        'value': 90,
        'other': 'extra',
    },
    {
        'name': 'b',
        'value': 50,
        'other': 'super',
    }
];


function arrayReducer(arrayToReduce) {
    // Created an object separating by name
    let reduced = arrayToReduce.reduce((accumulator, currentValue) => {
        (accumulator[currentValue.name] =
            accumulator[currentValue.name] || []).push(currentValue);
        return accumulator;
    }, {});
    // Reduce object to the highest value
    for (let quotes of Object.keys(reduced)) {
        reduced[quotes] = reduced[quotes].reduce(
            (accumulator, currentValue) => {
                return accumulator && accumulator.value > currentValue.value
                    ? accumulator
                    : currentValue;
            }
        );
    }
    // return only object values
    return Object.values(reduced);
}

console.log(arrayReducer(toReduce));

Upvotes: 0

Views: 125

Answers (7)

Barmar
Barmar

Reputation: 782166

You can calculate the maximum during reduce() rather than in a separate loop.

var toReduce =
  [ { 'name': 'a', 'value': 20 }
  , { 'name': 'a', 'value': 80 }
  , { 'name': 'b', 'value': 90 }
  , { 'name': 'b', 'value': 50 }
  ];

function arrayReducer(arrayToReduce) 
  {
  // Created an object separating by name
  let reduced = arrayToReduce.reduce((accumulator, currentValue) => 
    {
    accumulator[currentValue.name] = 
      { name  : currentValue.name
      , value : accumulator.hasOwnProperty(currentValue.name) 
                ? Math.max(accumulator[currentValue.name].value, currentValue.value) 
                : currentValue.value
      };
    return accumulator;
    }
    , {});
  // return only object values
  return Object.values(reduced);
  }

console.log(arrayReducer(toReduce));

Upvotes: 0

Nabin
Nabin

Reputation: 1

A simple way to achieve it:

const newObj = [toReduce[0]];
const objKeyArray = [toReduce[0].name];


for (i = 1; i < toReduce.length; i++) {
  for (j = 0; j < newObj.length; j++) {
    if (toReduce[i].name === newObj[j].name) {
        // If you just want to change the data of the specific property
      //   newObj[j].value = Math.max(toReduce[i].value, newObj[j].value);
      // If you want to replace the object.
      newObj[j] = toReduce[i];
    } else {
      if (!objKeyArray.includes(toReduce[i].name)) {
        objKeyArray.push(toReduce[i].name);
        newObj.push(toReduce[i]);
      }
    }
  }
}
console.log(newObj);

Upvotes: 0

Odri
Odri

Reputation: 379

try this

const data = [
    {
        name: 'a',
        value: 20,
        ...(other object values)
    },
    {
        name: 'a',
        value: 80
        ...(other object values)
    },
    {
        name: 'b',
        value: 90,
        ...(other object values)
    },
    {
        name: 'b',
        value: 50,
        ...(other object values)
    }
]

const obj = Object.groupBy(data, ({name}) => name)
const result = Object.keys(obj).map(key => {
  const max = obj[key].toSorted((a, b) => b.value - a.value)[0]
  return max
})

console.log(result)

Upvotes: 1

Nick Parsons
Nick Parsons

Reputation: 50914

Another option is to use Map.groupBy() to group each object by name. Once grouped, you can iterate through the map, and for each group of objects grab the maximum value (this is done using Array.from() on the Map with a mapping function below):

const arr = [ { name: 'a', value: 20 }, { name: 'a', value: 80 }, { name: 'b', value: 90 }, { name: 'b', value: 50 } ];

const res = Array.from(
  Map.groupBy(arr, obj => obj.name),
  ([name, grouped]) => ({name, value: Math.max(...grouped.map(obj => obj.value))}) 
);
console.log(res);

If your objects can have other keys/properties, then you can find the max object from each group and use that:

const arr =  [{ 'name': 'a', 'value': 20, 'other': 'any', }, { 'name': 'a', 'value': 80, 'other': 'value', }, { 'name': 'b', 'value': 90, 'other': 'extra', }, { 'name': 'b', 'value': 50, 'other': 'super', } ];

const findMax = arr => arr.reduce((max, curr) => curr.value > max.value ? curr : max);
const res = Array.from(
  Map.groupBy(arr, obj => obj.name).values(),
  findMax 
);
console.log(res);

Upvotes: 1

Evert
Evert

Reputation: 99745

The easiest here is probably a simple loop:

const result = {};

for (const item of input) {
  if (item.value > (result[item.name]?.value ?? 0)) {
    result[item.name] = item;
  }
}

// To turn it back into the original array format:
const output = Object.values(result)

Of course you can turn this into a single statement with reduce() and so on, but legibility will suffer as you can see from all the other answers.

But here's the .reduce() version using the same approach:

const output = Object.values(
  input.reduce( (acc, cur) => {
    if (cur.value > (acc[cur.name]?.value ?? 0)) {
      acc[cur.name] = cur;
    } 
    return acc;
  }, {})
)

Upvotes: 1

Mr. Polywhirl
Mr. Polywhirl

Reputation: 48713

The keep function below is a reusable way to reduce a list of items into a map by their keys, and compare their values using a comparator.

For the key and value options, you can pass an accessor (getter) or a index (field) to tell the function how to determine how to "get" the value from the item.

This all happens in O(n) time.

const original = [
  { name: 'a', value: 20 },
  { name: 'a', value: 80 },
  { name: 'b', value: 90 },
  { name: 'b', value: 50 }
];

const get = (item, keyOrFn) => {
  if (!keyOrFn) return item;
  return typeof keyOrFn === 'function'
    ? keyOrFn(item)
    : item[keyOrFn];
};

const keep = (arr, comparator, { key, value }) => {
  return [...arr.reduce((acc, item) => {
    const k = get(item, key);
    const existing = acc.get(k);
    if (!existing) return acc.set(k, item);
    const a = get(item, value);
    const b = get(existing, value);
    if (comparator(a, b) > 0) return acc.set(k, item);
    return acc;
  }, new Map()).values()];
};

const maxValues = keep(
  original,
  (a, b) => a - b,
  { key: 'name', value: 'value' },
  // OR: { key: x => x.name, value: x => x.value }
);

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

Upvotes: 0

AMD
AMD

Reputation: 203

This might help. But it's not in one Go.

var toReduce = [
    {
        name: "a",
        value: 20,
    },
    {
        name: "a",
        value: 80,
    },
    {
        name: "b",
        value: 90,
    },
    {
        name: "b",
        value: 50,
    },
];

const itemByMaxValue = toReduce.reduce((acc, item) => {
    if (acc[item.name]) {
        acc[item.name] = Math.max(item.value, acc[item.name]);
    } else {
        acc[item.name] = item.value;
    }
    return acc;
}, {});

const result = Object.entries(itemByMaxValue).map(([name, value]) => ({
    name,
    value,
}));

console.log(result);

Upvotes: 0

Related Questions