skip to Main Content

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

Need the following,

[
    {
        name: 'a',
        value: 20
    },
    {
        name: 'a',
        value: 80
    },
    {
        name: 'b',
        value: 90
    },
    {
        name: 'b',
        value: 50
    }
]

To return

[
    {
        name: 'a',
        value: 80
    },
    {
        name: 'b',
        value: 90
    }
]

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
    },
    {
        '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] =
            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));

5

Answers


  1. 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));
    Login or Signup to reply.
  2. The easiest here is probably a simple loop:

    const result = {};
    
    for (const item of input) {
      result[item.name] = Math.max(item.value, result[item.name] ?? 0);
    }
    
    // To turn it back into the original array format:
    const output = Object.entries(result).map([k,v] => {name: k, value: v});
    

    Of course you can turn this into a single statement with reduce() and so on, but legibility will suffer.

    Login or Signup to reply.
  3. 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);
    Login or Signup to reply.
  4. 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);
    Login or Signup to reply.
  5. 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.

    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' }
    );
    
    console.log(maxValues);
    .as-console-wrapper { top: 0; max-height: 100% !important! }
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search