1

I have an array of objects with 3 keys, lat, lng and value. e.g.

let arr = [
  { lat: 20, lng: 30, value: 3 },
  { lat: 25, lng: 25, value: 4 },
  { lat: 20, lng: 30, value: 6 },
  { lat: 30, lng: 40, value: -5 },
  { lat: 25, lng: 25, value: 7 },
];

I'd like to compute the sum for each lat/lng pair, expecting the following result:

let res = [
  { lat: 20, lng: 30, value: 9 },
  { lat: 25, lng: 25, value: 11 },
  { lat: 30, lng: 40, value: -5 }
];

I have tried to store the results into an intermediate object, like this:

const temp = res.reduce((acc, { lat, lng, value }) => {
  acc[lat] = acc[lat] || {};
  acc[lat][lng] = acc[lat][lng] || 0;
  acc[lat][lng] += value;
  return acc;
}, {});

so temp would look like this:

let temp = { 
  20: {
    30: 9
  },
  25: {
    25: 11
  },
  30: {
    40: -5
  }
};

And then convert it to the format I want:

let res = Object.keys(temp).reduce((acc, lat) => {
  let tt = Object.keys(temp[lat]).map(lng => {
    return {lat: lat, lng: lng, value: temp[lat][lng]};
  });
  acc.push(...tt);
  return acc;
}, []);

This looks highly inefficient and I'm sure there is a smarter way to do it. I have looked at lodash's groupBy and sumBy but I couldn't put a solution together.

3 Answers 3

3

You could take a flat object and get the values from it as result set.

An array of keys are used to get a joined key.

let array = [{ lat: 20, lng: 30, value: 3 }, { lat: 25, lng: 25, value: 4 }, { lat: 20, lng: 30, value: 6 }, { lat: 30, lng: 40, value: -5 }, { lat: 25, lng: 25, value: 7 }],
    keys = ['lat', 'lng'],
    result = Object.values(array.reduce((r, o) => {
        const key = keys.map(k => o[k]).join('|');
        if (!r[key]) r[key] = { ...o, value: 0 };
        r[key].value += o.value;
        return r;
    }, {}));

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

Sign up to request clarification or add additional context in comments.

2 Comments

Very nice - as always! (up-voted) I think it would be beneficial for all readers if you use more meaningful names for currentValue and accumulator (and, where applicable, index, array, and so on). Here, I can deduce, you've used r for result and it's initialValue, o for array's item (which is an Object), and k for a key of keys Array. I've learned something "new" from you: spread operator works on Objects too (as well as on Arrays)!
Thank you! Works exactly the way I want. I'll probably have to tweak the keys separator as my real use case doesn't use lat/lng. Too bad we can't have something like key = { lat: 3, lng: 2 }; cache[key] = ...;
2

I'd loop through and keep track of previous entries with a matching lat and lng in a Map, then grab the Map's values at the end:

const known = new Map();
for (const {lat, lng, value} of arr) {
    const key = `${lat}:${lng}`;
    const prev = known.get(key);
    if (prev) {
        prev.value += value;
    } else {
        known.set(key, {lat, lng, value});
    }
}
const result = [...known.values()];

Live Example:

let arr = [
  { lat: 20, lng: 30, value: 3 },
  { lat: 25, lng: 25, value: 4 },
  { lat: 20, lng: 30, value: 6 },
  { lat: 30, lng: 40, value: -5 },
  { lat: 25, lng: 25, value: 7 },
];

const known = new Map();
for (const {lat, lng, value} of arr) {
    const key = `${lat}:${lng}`;
    const prev = known.get(key);
    if (prev) {
        prev.value += value;
    } else {
        known.set(key, {lat, lng, value});
    }
}
const result = [...known.values()];
console.log(result);

You could do that with reduce:

let arr = [
  { lat: 20, lng: 30, value: 3 },
  { lat: 25, lng: 25, value: 4 },
  { lat: 20, lng: 30, value: 6 },
  { lat: 30, lng: 40, value: -5 },
  { lat: 25, lng: 25, value: 7 },
];

const result = [...arr.reduce((map, {lat, lng, value}) => {
    const key = `${lat}:${lng}`;
    const prev = map.get(key);
    if (prev) {
        prev.value += value;
    } else {
        map.set(key, {lat, lng, value});
    }
    return map;
}, new Map()).values()];
console.log(result);

...but it doesn't buy you anything; it's just easier to get wrong. (I'm one of many who believe that unless you're doing functional programming with a predefined, reusable set of reducer functions, reduce is just unnecessary complication.)

2 Comments

Thanks! You've just provided the last push I needed to read/learn more about Map Object! (up-voted)
Thanks, TJ! I also agree that reduce is not necessary in most cases.
-1

I would usually do something like this:

let arr = [
  { lat: 20, lng: 30, value: 3 },
  { lat: 25, lng: 25, value: 4 },
  { lat: 20, lng: 30, value: 6 },
  { lat: 30, lng: 40, value: -5 },
  { lat: 25, lng: 25, value: 7 },
];

const temp = arr.reduce((acc, { lat, lng, value }) => {
  const key = `${lat}:${lng}`;
  acc[key] = acc[key] || 0;
  acc[key] += value;
  return acc;
}, {});

console.log(temp);

3 Comments

Not what OP wants, see OP's expected result.
iAmOren is right, this is not what I'm looking for.
No, it's a pattern to get you most of the way there. In general, if you say 'I want to identify my grouping by these two keys', then you can combine those two keys in the way I did in the answer and produce an intermediate object or array, and then take that object/array and turn it easily into the format you want.

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.