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;
  }, {})
)
Sign up to request clarification or add additional context in comments.

3 Comments

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
This doesn't work if a value can be negative.
@Barmar if you want negative values, use -Infinity instead of 0
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 Comments

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 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.
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 Comment

Nice use of groupBy
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);

Comments

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

Comments

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

Comments

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 Comments

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
The same approach works for any number of properties.
Just copy them all into the accumulator.
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.
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.

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.