1

I have an array of objects:

[
{"market": "Qacha's nek","commodity": 55,"price": "90","month": "04","year": "2017"}, 
{"market": "Mohales Hoek","commodity": 55,"price": "75","month": "04","year": "2017"}, 
{"market": "Mafeteng","commodity": 55,"price": "75","month": "04","year": "2017"}, 
{"market": "Maseru","commodity": 55,"price": "69","month": "04","year": "2017"}, 
{"market": "Butha-Buthe","commodity": 55,"price": "66","month": "04","year": "2017"}, 
{"market": "Leribe","commodity": 55,"price": "64","month": "04","year": "2017"}, 
{"market": "Butha-Buthe","commodity": 55,"price": "65","month": "04","year": "2017"}, 
{"market": "Thaba-Tseka","commodity": 55,"price": "82","month": "04","year": "2017"},
{"market": "Thaba-Tseka","commodity": 55,"price": "81","month": "04","year": "2017"},
{"market": "Maseru",    "commodity": 55,"price": "74,99","month": "04","year": "2017"}
]

I'm trying to aggregate duplicates by price average.

So the keys to identifying duplicated rows are all the properties except price, that must be aggregated by average.

In the data above, for example, line 5 and 7:

5) "market": "Butha-Buthe","commodity": 55,"price": "66","month": "04","year": "2017"
7) "market": "Butha-Buthe","commodity": 55,"price": "65","month": "04","year": "2017"

are duplicates and I want to merge them and make the average of their price value.

I was trying to use the reduce function, but I can't figure out how to identify duplicated values, especially if they are not sorted.

I post the code, but it's useless as I can't understand how to identify duplicates with reduce:

var data = [
{"market": "Qacha's nek","commodity": 55,"price": "90","month": "04","year": "2017"}, 
{"market": "Mohales Hoek","commodity": 55,"price": "75","month": "04","year": "2017"}, 
{"market": "Mafeteng","commodity": 55,"price": "75","month": "04","year": "2017"}, 
{"market": "Maseru","commodity": 55,"price": "69","month": "04","year": "2017"}, 
{"market": "Butha-Buthe","commodity": 55,"price": "66","month": "04","year": "2017"}, 
{"market": "Leribe","commodity": 55,"price": "64","month": "04","year": "2017"}, 
{"market": "Butha-Buthe","commodity": 55,"price": "65","month": "04","year": "2017"}, 
{"market": "Thaba-Tseka","commodity": 55,"price": "82","month": "04","year": "2017"},
{"market": "Thaba-Tseka","commodity": 55,"price": "81","month": "04","year": "2017"},
{"market": "Maseru","commodity": 55,"price": "74,99","month": "04","year": "2017"}
];

var avg = data.reduce(function(result, current) {
			console.log(result,current);
      if(!result){
      	result=current;
      }
      else {
      	if(result.market==current.market){
        	console.log(current.market);
        }
      }
});

Here a jsfiddle where I was trying to understand how the reduce function works: https://jsfiddle.net/brainsengineering/7tmdx0kg/7/

5
  • 2
    All code directly here please ... Commented Mar 6, 2019 at 19:00
  • Hi! The way SO works, your whole question (including any necessary code) has to be in your question, not just linked. Two reasons: People shouldn't have to go off-site to help you; and links rot, making the question and its answers useless to people in the future. Please put a minimal reproducible example in the question. More: How do I ask a good question? Commented Mar 6, 2019 at 19:01
  • You can make your MCVE runnable using Stack Snippets (the [<>] toolbar button; here's how to do one). Commented Mar 6, 2019 at 19:01
  • Why is price a string? Commented Mar 6, 2019 at 19:07
  • @JonasWilms unfortunately, the backend service returned this way... I know it shouldn't Commented Mar 6, 2019 at 19:09

3 Answers 3

2

You could take a combined key for the wanted properties and replace the price format to a numerical parsable format.

var data = [{ market: "Qacha's nek", commodity: 55, price: "90", month: "04", year: "2017" }, { market: "Mohales Hoek", commodity: 55, price: "75", month: "04", year: "2017" }, { market: "Mafeteng", commodity: 55, price: "75", month: "04", year: "2017" }, { market: "Maseru", commodity: 55, price: "69", month: "04", year: "2017" }, { market: "Butha-Buthe", commodity: 55, price: "66", month: "04", year: "2017" }, { market: "Leribe", commodity: 55, price: "64", month: "04", year: "2017" }, { market: "Butha-Buthe", commodity: 55, price: "65", month: "04", year: "2017" }, { market: "Thaba-Tseka", commodity: 55, price: "82", month: "04", year: "2017" }, { market: "Thaba-Tseka", commodity: 55, price: "81", month: "04", year: "2017" }, { market: "Maseru", commodity: 55, price: "74,99", month: "04", year: "2017" }],
    keys = ['market', 'commodity', 'month', 'year'],
    count = {},
    result = data.reduce(function (r, o) {
        var key = keys.map(function (k) { return o[k]; }).join('|');
        if (!count[key]) {
            count[key] = { sum: +o.price.replace(',', '.'), data: JSON.parse(JSON.stringify(o)) };
            count[key].data.count = 1;
            r.push(count[key].data);
        } else {
            count[key].sum += +o.price.replace(',', '.');
            count[key].data.price = (count[key].sum / ++count[key].data.count).toString();
        }
        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.

7 Comments

Thought of using a hashtable for counts, but I couldn't find a way without transforming the object at least two times, this combined object/array is just very elegant :)
@nina always the best on map and reduce! This solution is really the best as I can define also the keys. And would it possible to add a new property to the object for keeping track of the count (how many elements have been aggregated)?
@Giox, now with count in the result set.
This code certainly does the job but I wouldn't want to maintain it for sure. Not sure OP will even understand how it actually works...
There is a trick that @NinaScholz is using there. After pushing the data to the result array (in the r.push(count... line), she relies on the fact that javascript is a "pass by reference" language. You need to understand that when she updates the price in count object (last line of the else block) she is also updating the result array. I think it would benefit you to start from scratch and try to solve the problem without using reduce first.
|
2

Group prices together for each item by adding them to an array in your reduce call. You can keep track of which items are duplicated in the same function. Then use loop over the duplicate items to compute the averages.

Note I had to change your price 74,99 to 74.99 to parse more easily. You'll probably want some sort of localization/globalization library if this is critical in your use case.

var data = [
{"market": "Qacha's nek","commodity": 55,"price": "90","month": "04","year": "2017"}, 
{"market": "Mohales Hoek","commodity": 55,"price": "75","month": "04","year": "2017"}, 
{"market": "Mafeteng","commodity": 55,"price": "75","month": "04","year": "2017"}, 
{"market": "Maseru","commodity": 55,"price": "69","month": "04","year": "2017"}, 
{"market": "Butha-Buthe","commodity": 55,"price": "66","month": "04","year": "2017"}, 
{"market": "Leribe","commodity": 55,"price": "64","month": "04","year": "2017"}, 
{"market": "Butha-Buthe","commodity": 55,"price": "65","month": "04","year": "2017"}, 
{"market": "Thaba-Tseka","commodity": 55,"price": "82","month": "04","year": "2017"},
{"market": "Thaba-Tseka","commodity": 55,"price": "81","month": "04","year": "2017"},
{"market": "Maseru","commodity": 55,"price": "74.99","month": "04","year": "2017"}
];

function parsePrice(str) {
  // TODO: localization
  return +str;
}

function formatPrice(num) {
  return num.toFixed(2);
}

function getHashKey(item) {
  return JSON.stringify([item.market, item.commodity, item.month, item.year]);
}

var duplicatedItems = {};
var prices = data.reduce(function(result, current) {
  var key = getHashKey(current);
  if (key in result) {
    result[key].push(parsePrice(current.price));
    duplicatedItems[key] = current;
  } else {
    result[key] = [parsePrice(current.price)];
  }
  return result;
}, {});
var avg = Object.keys(duplicatedItems).map(function(key) {
  var item = duplicatedItems[key];
  var avgPrice = prices[key].reduce(function(acc, price) { return acc + price; }, 0) / prices[key].length;
  return {
    market: item.market,
    commodity: item.commodity,
    price: formatPrice(avgPrice),
    month: item.month,
    year: item.year
  };
});

console.log(avg);

Comments

0

You could insert the values into a new array and merge if it already exists:

 const result = [];

 outer: for(const {  market, commodity,  price, month, year } of input) {
    for(const other of result) {
       if(market === other.market && commodity === other.commodity && month === other.month && year === other.year) {
         other.prices.push(+price);

         continue outer;
       }
    }
    result.push({ market, commodity, prices: [+price], month, year });
 }

 for(const group of result)
   group.price = group.prices.reduce((a, b) => a + b, 0) / group.prices.length;

2 Comments

is it compatible with IE11?
@giox no, it isn't. Transpile it if needed.

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.