Home > Software design >  How to reduce a property within an array of array?
How to reduce a property within an array of array?

Time:08-11

I have an array of products which in turn have an array of categories. I want to extract distinct values of the type property on category object.
Both the Lodash and native versions below do the job.
I want to make a generic function which takes the path of property and return unique values.

Essentially I am looking at something terse like map(products, property("categories[].type") but heres the longer version(s)

import { compact, flatten, map, property, uniq } from "lodash";

export const getAllTypes1 = (products) => {
  return uniq(
    compact(map(flatten(map(products, property("categories"))), "type"))
  );
};
export const getAllTypes2 = (products) => {
  const types = [];
  products.forEach((product) => {
    product.categories.forEach((category) => {
      if (!types.some((t) => t === category.type)) {
        types.push(category.type);
      }
    });
  });
  return types;
};

Example data

const product1 = {
  name: 'Wilson Orange',
  price: 72.50,
  categories: [{
    type: 'flash sale',
    discountable: false,
  },{
    type: 'tennis',
    discountable: true,
  }]
};
const product2 = {
  name: 'Babolat Green',
  price: 65.50,
  categories: [{
    type: 'tennis',
    discountable: true,
  }]
};
const products = [product1, product2];

Result

const result = getAllTypes2(products);
console.log(result); // ["flash sale", "tennis"]

Here's a working example

CodePudding user response:

Here's a vanilla JS function that takes the path without needing [] and automatically checks arrays wherever it finds one.

How it works is:

  1. Create an empty Set to easily remove duplicates
  2. Turn the path string to an array of properties -> props
  3. Call a recursive function recurse(currObj, props) which:
    1. Checks if the currObj is an array, and if it is: a. Recurses again with the array values as currObj b. Use the same props since we didn't check an object in the path
    2. Check if we're at the last prop in the path, if yes a. Add the property's value in the current object to the set
    3. Otherwise. recurse with currObj[currProp], and the remaining props
  4. Convert the set to an array and return it.

const product1 = {
  name: 'Wilson Orange',
  price: 72.5,
  categories: [
    {
      type: 'flash sale',
      discountable: false,
    },
    {
      type: 'tennis',
      discountable: true,
    },
  ],
};
const product2 = {
  name: 'Babolat Green',
  price: 65.5,
  categories: [
    {
      type: 'tennis',
      discountable: true,
    },
  ],
};
const products = [product1, product2];

function getProperties(array, path) {
  const props = path.split('.');
  const values = new Set();

  function recurse(currObj, props) {
    const currProp = props[0]
    const nextProps = props.slice(1);
    if (Array.isArray(currObj)) {
      for (let val of currObj) {
        recurse(val, props);
      }
      return
    }
    if (nextProps.length === 0) {
      values.add(currObj[currProp])
    } else {
      recurse(currObj[currProp], nextProps)
    } 
  }
  recurse(array, props);
  return [...values];
}

console.log(getProperties(products,'categories.type'))
console.log(getProperties(products,'price'))
console.log(getProperties(products,'name'))

CodePudding user response:

It's not a property path string, but it's pretty terse and expressive:

const pipe = (...fs) => fs.reduceRight(
  (next, f) => x => f(x, next), x => x,
);
const getAllTypes = pipe(
  (x, next) => [...new Set(x.flatMap(next))],
  (x, next) => x.categories.map(next),
  (x) => x.type,
);
// equivalent to
// const getAllTypes =
//   x => [...new Set(x.flatMap(y => y.categories.map(z => z.type)))];

const products = [{
  name: 'Wilson Orange',
  price: 72.50,
  categories: [{
    type: 'flash sale',
    discountable: false,
  }, {
    type: 'tennis',
    discountable: true,
  }]
}, {
  name: 'Babolat Green',
  price: 65.50,
  categories: [{
    type: 'tennis',
    discountable: true,
  }]
}];

console.log(getAllTypes(products));

The parameter x => x allows pipe to be called without any arguments and return the identity function. It also allows the last function argument of pipe to accept a next parameter for consistency, i.e. (x, next) => next(x.type) would have been equivalent to (x) => x.type.

Reference:

  • Related