0

I want to extract the min and max values from all of the properties of the collection:

"data": [
    {
        "food": 41000,
        "drinks": 60,
        "toys": 18961,
        "candy": 8740
    },
    {
        "food": 245,
        "abusive-usage": 96,
        "drinks": 5971,
        "candy": 12492,
        "somethingnew": 8
    },
    {
        "food": 365,
        "abusive-usage": 84,
        "toys": 22605,
        "candy": 9256,
        "somethingold": 1
    },
    {}
];

Would I have known the propertyname(s) in advance, I could do something like:

const max = Math.max(...graph.datasets.urgency.map((x) => x.knownPropertyName));

Unfortunately, as I've tried to demonstrate with the example above, the properties are dynamic. I don't know in advance what property names will be in there, nor do I actually care; I only want the min and max of the values. It is safe to assume that all the values are guaranteed to be numbers.

With the following snippet I can get this to work:

    let maxUrgency = 0;
    let minUrgency = 0;
    data.forEach((day) => {
        Object.keys(day).forEach((key, index) => {
            if (day[key] > maxUrgency) maxUrgency = day[key];
            if (day[key] < minUrgency) minUrgency = day[key];
        });
    });

Although it works, it seems overly cumbersome - and probably not the most efficient - to simply use the for/forEach here. Instead, I'm looking for a more clean, vanilla approach (ES6 preferred) to achieve this. For example, a neat lambda construction based on Array.prototype perhaps. Preferably, I don't want to use a lib like lodash or underscore.

How can this piece of code be improved, ideally so I don't have to iterate through each property-for-each-item in the collection?

Related question, but definitely not the same:

Juliën
  • 9,047
  • 7
  • 49
  • 80

2 Answers2

1

If you don't mind doing some ES2017, there's Object.values(). Use it together with array.reduce().

const data = [{
    "food": 41000,
    "drinks": 60,
    "toys": 18961,
    "candy": 8740
  },
  {
    "food": 245,
    "abusive-usage": 96,
    "drinks": 5971,
    "candy": 12492,
    "somethingnew": 8
  },
  {
    "food": 365,
    "abusive-usage": 84,
    "toys": 22605,
    "candy": 9256,
    "somethingold": 1
  },
  {}
]

const values = data.reduce((c, v) => [...c, ...Object.values(v)], [])
const min = Math.min(...values)
const max = Math.max(...values)

console.log(min, max)

Alternatively, array.map() together with array.concat() can be used instead of array.reduce().

const data = [{
    "food": 41000,
    "drinks": 60,
    "toys": 18961,
    "candy": 8740
  },
  {
    "food": 245,
    "abusive-usage": 96,
    "drinks": 5971,
    "candy": 12492,
    "somethingnew": 8
  },
  {
    "food": 365,
    "abusive-usage": 84,
    "toys": 22605,
    "candy": 9256,
    "somethingold": 1
  },
  {}
]

const valueArrays = data.map(v => Object.values(v))
const values = Array.prototype.concat.call(...valueArrays)
const min = Math.min(...values)
const max = Math.max(...values)

console.log(min, max)
Joseph
  • 117,725
  • 30
  • 181
  • 234
  • is there a need for an ID wrapper to map Object.values, or can we just data.map(Object.values) to get the same? Also, what's the point of the concat.call part, can we just do [].concat(...valueArrays)? – dandavis Aug 30 '18 at 21:27
  • @dandavis It helps avoid surprises. `array.map()` passes three (3) arguments to the callback. If you assign a function that consumes more than one arg (i.e. `parseInt()`), it will not work as expected and this is hard to spot. For `array.concat()`, personal preference. I prefer not creating that extra array. – Joseph Aug 31 '18 at 12:13
1

Example using reduce() and for in loop

var obj = {
    "data": [
        {
            "food": 41000,
            "drinks": 60,
            "toys": 18961,
            "candy": 8740
        },
        {
            "food": 245,
            "abusive-usage": 96,
            "drinks": 5971,
            "candy": 12492,
            "somethingnew": 8
        },
        {
            "food": 365,
            "abusive-usage": 84,
            "toys": 22605,
            "candy": 9256,
            "somethingold": 1
        }
    ]
};

//max of each property

var res = obj.data.reduce((acc, c) => {

    for (let p in c) {
        if (c[p] > (acc[p] || 0)) acc[p] = c[p];
    }

    return acc;

}, {})

console.log(res)

//absolute max and min

var res = obj.data.reduce((acc, c) => {

    for (let p in c) {
        if (c[p] > acc[1] ) acc[1] = c[p];
        if (c[p] < acc[0] ) acc[0] = c[p];
    }
return acc
}, [Infinity,0])


console.log(res)
Emeeus
  • 5,072
  • 2
  • 25
  • 37
  • That just seems to spit out an object with the distinct properties, but with "random" values? – Juliën Aug 30 '18 at 22:15
  • @Juliën not random, each value of each property. `c[p] > (acc[p] || 0)` means if the value (`c[p]`) is greater than 0 (if the property does not exist) or the actual value stored. `(acc[p] || 0)` returns the value (if there is any) or 0. – Emeeus Aug 30 '18 at 22:18
  • I appreciate the effort. But I don't see how this would be a way to even get the min/max value: ` { "food": 41000, "drinks": 5971, "toys": 22605, "candy": 12492, "abusive-usage": 96, "somethingnew": 8, "somethingold": 1 } ` – Juliën Aug 30 '18 at 22:21
  • @Juliën is the maximun of each property. if you want the absolute it's just if `(c[p] > acc ) acc = c[p]` – Emeeus Aug 30 '18 at 22:26
  • @Juliën I've added the example. – Emeeus Aug 30 '18 at 22:29
  • OK, the final example makes sense, although a bit obtruse to my personal taste. And it doesn't remove the for loop. But still, it's a working alternative so thanks! – Juliën Aug 30 '18 at 22:36
  • @Juliën Lol, your welcome, I don't like so much the `reduce` syntax neither, but is usually the fastest way. – Emeeus Aug 30 '18 at 22:40
  • @Juliën map, reduce, filter, sort, min, max.... are all loops, Joseph solution have 3 loops in fact. – Emeeus Aug 30 '18 at 22:45
  • Sure, diving deep enough it is. But the native syntactic sugar makes the verbosity much less. I'd say in terms of readability the combination of reduce() with the spread operator is very nice in that answer. – Juliën Aug 30 '18 at 22:48
  • @Juliën sure, 100% agree – Emeeus Aug 30 '18 at 22:50