0

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));

7 Answers 7

1

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;
  }, {})
)
3
  • Thanks for your answer, I have actually edited my original question, as the object might have more values (dynamically) other than just name and value, your answer only cover an object with 2 values
    – caiovisk
    Commented Jul 2, 2024 at 1:21
  • This doesn't work if a value can be negative.
    – Barmar
    Commented Jul 2, 2024 at 2:00
  • @Barmar if you want negative values, use -Infinity instead of 0
    – Evert
    Commented Jul 2, 2024 at 3:20
1

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);

2
  • Thanks for your answer, I have actually edited my original question, as the object might have more values (dynamically) other than just name and value, your answer only cover an object with 2 values
    – caiovisk
    Commented Jul 2, 2024 at 1:21
  • @caiovisk Instead of running Math.max() you can use a loop (such as reduce) to find the max object and use that instead. See updated answer. Commented Jul 2, 2024 at 1:45
1

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)
1
  • Nice use of groupBy
    – slebetman
    Commented Jul 2, 2024 at 2:38
0

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);

0

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! }

0

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);
0

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));

5
  • Thanks for your answer, I have actually edited my original question, as the object might have more values (dynamically) other than just name and value, your answer only cover an object with 2 values
    – caiovisk
    Commented Jul 2, 2024 at 1:20
  • The same approach works for any number of properties.
    – Barmar
    Commented Jul 2, 2024 at 1:56
  • Just copy them all into the accumulator.
    – Barmar
    Commented Jul 2, 2024 at 1:57
  • If your changes invalidate the answers, you should do it as a new question. It's not fair to edit the question after receiving valid answers to the original question.
    – Barmar
    Commented Jul 2, 2024 at 1:58
  • Agreed, however I tried to update the questions as all answer were limited to a fixed object structure... Updating the question would benefit anyone out there chasing this approach.
    – caiovisk
    Commented Jul 3, 2024 at 0:11

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.