5

I am having some trouble rationalising and aggregating an array of objects in javascript.

Given the array

[{"description":"Bright","size":"2XL","price":10.99},{"description":"Bright","size":"XL","price":10.99},{"description":"Bright","size":"L","price":9.99},{"group":"Foos","description":"Dull","size":"XL","price":9.99},{"description":"Dull","size":"L","price":8.99},{"description":"Dull","size":"2XL","price":9.99},{"description":"Shiny","size":"XL","price":9.99},{"description":"Shiny","size":"S","price":8.99},{"description":"Shiny","size":"3XL","price":10.3},{"description":"Shiny","size":"2XL","price":9.99}]

I am trying to convert it to an array in the format (actual values may be wrong here).

[{"descriptions":"Shiny, Bright, Dull","sizeRange":"S - L","price":8.99},{"descriptions":"Shiny, Bright, Dull","sizes":"XL - 2XL","price":9.99},{"descriptions":"Dark","sizes":"S - 2XL","price":10.99}]

That is - I wish to group each set of items by price, showing the descriptions and size ranges for them.

So far this is what I have, and it seems to be working, but it just seems very cumbersome. Really I'd be more than happy to use something like lodash or underscore if it would help to rationalise the code a bit rather than using native JS.

function groupBy (array, key) {
  return array.reduce(function(value, property) {
    (value[property[key]] = value[property[key]] || []).push(property);
    return value;
  }, {});
};

function unique(array) {
  return Array.from(new Set(array));
};

function getRanges(data)
{
    var result = [];

    // simple map of sizes from smallest to largest to use for sorting
    var sizeSort = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5, '3XL':6, '4XL':7, '5XL':8};

    // group the remaining variants by price
    var group = groupBy(data, 'price');

    // for each variant price group 
    for(var price in group) {
        var item = {};
        item.price = price;
        // get the range of sizes sorted smallest to largest
        var sizes = unique(group[price].map(function(i) {
            return i.size;
        })).sort(function(a, b) {
            return sizeSort[a] - sizeSort[b];
        });

        // Add single size, or first and last size.
        item.sizes = (sizes.length === 1) ?
           sizes.shift() :
           sizes.shift() + ' - ' + sizes.pop();

        // Add the descriptions as alphabetically sorted CSV
        item.description = unique(group[price].map(function(i) {
            return i.description;
        })).sort().join(", ");

        result.push(item);
    }

    return result;
}
0

2 Answers 2

3

Here is a version using lodash.. I think it looks more rational..

function calc(data) {
  var sizeSort = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5, 
                  '3XL':6, '4XL':7, '5XL':8};
  return _.chain(data).
  groupBy('price').
  map(function(f){ 
     var sizes = _.chain(f).map('size').uniq().
        sortBy(function (a) { return sizeSort[a] }).value();
     return { 
       price: _.head(f).price,
       description: _.chain(f).map('description').uniq().join(',').value(),
       size: sizes.length === 1 ? _.first(sizes) : _.join([_.first(sizes),_.last(sizes)], ' - ')
     } 
  }).
  sortBy(['price']). 
  value();
}

//put data at end, so not having to scroll down to see code
var data = [{"description":"Bright","size":"2XL","price":10.99},{"description":"Bright","size":"XL","price":10.99},{"description":"Bright","size":"L","price":9.99},{"group":"Foos","description":"Dull","size":"XL","price":9.99},{"description":"Dull","size":"L","price":8.99},{"description":"Dull","size":"2XL","price":9.99},{"description":"Shiny","size":"XL","price":9.99},{"description":"Shiny","size":"S","price":8.99},{"description":"Shiny","size":"3XL","price":10.3},{"description":"Shiny","size":"2XL","price":9.99}];

console.log(calc(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.3/lodash.js"></script>

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

2 Comments

Thanks, this is excellent, the lodash chain function is just the ticket and it really does make the code a lot clearer.
apologies I thought I'd accepted this answer long ago!
2

A vanilla JavaScript solution (with ES6 template strings)

/*
  Some boilerplate functions. Listed underscore/lodash functions that 
  could replace them above
*/

// _.mapObject(object, reducer)
function reduceValues(object, reducer) {
  let newObject = {}
    for (var property in object) {
      if (object.hasOwnProperty(property)) {
          newObject[property] = reducer(object[property])
      }
    }
  return newObject
}

// _.groupBy
function groupBy(arr, key) {
  let reducer = (grouped, item) => {
    let group_value = item[key]
    if (!grouped[group_value]) {
      grouped[group_value] = []
    }
    grouped[group_value].push(item)
    return grouped
  }
  return arr.reduce(reducer, {})
}

// _.values
function objectValues(object) {
  let values = []
  for (var property in object) {
    if (object.hasOwnProperty(property)) {
        values.push(object[property])
    }
  }
  return values
}

/*
  Shirt specific functions and data
*/

// Mapping of shirts to their order.
let sizesToNumbers = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5, '3XL':6, '4XL':7, '5XL':8};

// Create an intermediate summary with data instead of strings.
// This makes processing easier to write and reason about
function reduceShirtsToSummary(shirts) {
  let reducer = (summary, shirt) => {
    summary['descriptions'].add(shirt['description'])
    let shirtSize = shirt['size']
    if (!summary['smallestSize'] || sizesToNumbers[shirtSize] < sizesToNumbers[summary['smallestSize']]) {
      summary['smallestSize'] = shirtSize
    }
    if (!summary['largestSize'] || sizesToNumbers[shirtSize] > sizesToNumbers[summary['largestSize']]) {
      summary['largestSize'] = shirtSize
    }
    summary['prices'].push(shirt['price'])
    return summary
  }
  return shirts.reduce(reducer, {'descriptions': new Set(), 'prices': []})
}

// Convert the shirt summary data into the "labelized" version with strings in the example
function labelizeShirtSummary(shirtSummary) {
  let labelizedShirtSummary = {}
  labelizedShirtSummary['descriptions'] = Array.from(shirtSummary['descriptions']).join(', ')
  labelizedShirtSummary['price'] = shirtSummary['prices'][0]
  labelizedShirtSummary['sizes'] = `${shirtSummary['smallestSize']} - ${shirtSummary['largestSize']}`
  return labelizedShirtSummary
}

let grouped = groupBy(shirts, 'price')
let groupedAndSummarized = reduceValues(grouped, reduceShirtsToSummary)
let labelizedSummaries = objectValues(groupedAndSummarized).map(labelizeShirtSummary)
// Gives desired output
console.log(labelizedSummaries)

2 Comments

Thanks, FWIW it's not a homework problem - but if it was what would the issue be? Some good discussion of such things here meta.stackoverflow.com/questions/334822/…
Sorry, just end of a long day. Editing to remove "seems like a homework problem" comment.

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.