0

I've got the following array that I want to filter based on one property and get distinct from another property. I managed to get it working but is there a much cleaner way to achieve this?

var result = [{
    "Class": "Class C",
    "Group": "Group A",
    "Value": 1
  },
  {
    "Class": "Class C",
    "Group": "Group A",
    "Value": 2
  },
  {
    "Class": "Class A",
    "Group": "Group B",
    "Value": 2
  },
  {
    "Class": "Class C",
    "Group": "Group B",
    "Value": 10
  }
];

result = result.reduce((x, {
  Class,
  Group,
  Value
}) => {
  Class.includes("Class C") && x.push({
    Class,
    Group,
    Value
  });
  return x;
}, []);
console.log(result);

result = [...new Map(result.map(item => [item["Group"], item])).values()];
console.log("Expected Result:");
console.log(result);

0

3 Answers 3

2

You have two tasks, filtering, and finding distinct items (practically: grouping).

So when we define two functions, one for filtering by a given property:

const filterByProp = 
    (prop) =>
        (value) =>
            (items) =>
                items.filter((item) => item[prop] === value);

and one for finding the distinct (in this case: each first item per value) by a given property:

const distinctByProp = 
    (prop) =>
        (items) =>
            Object.values(items.reduce((group, item) => {
                if (!group.hasOwnProperty(item[prop])) group[item[prop]] = item;
                return group;
            }, {}));

we can do

const onlyClassC = filterByProp("Class")("Class C");
const distinctByGroup = distinctByProp("Group");

const filtered = distinctByGroup(onlyClassC(result));

and get filtered as:

[
  {
    "Class": "Class C",
    "Group": "Group A",
    "Value": 1
  },
  {
    "Class": "Class C",
    "Group": "Group B",
    "Value": 10
  }
]

If you want the last item per distinct group instead of the first, remove the if (!group.hasOwnProperty(item[prop])) from distinctByProp.

const filterByProp = 
    (prop) =>
        (value) =>
            (items) =>
                items.filter((item) => item[prop] === value);

const distinctByProp = 
    (prop) =>
        (items) =>
            Object.values(items.reduce((group, item) => {
                if (!group.hasOwnProperty(item[prop])) group[item[prop]] = item;
                return group;
            }, {}));

// -----------------------------------------------------------------------
const onlyClassC = filterByProp("Class")("Class C");
const distinctByGroup = distinctByProp("Group");

// -----------------------------------------------------------------------
var result = [
  {
    "Class": "Class C",
    "Group": "Group A",
    "Value": 1
  },
  {
    "Class": "Class C",
    "Group": "Group A",
    "Value": 2
  },
  {
    "Class": "Class A",
    "Group": "Group B",
    "Value": 2
  },
  {
    "Class": "Class C",
    "Group": "Group B",
    "Value": 10
  }
];

const filtered = distinctByGroup(onlyClassC(result));
console.log(filtered);

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

11 Comments

Yup, this would do! Thanks.
@nadz There are a many libraries that supply the two helper functions that I wrote myself here, among others, so that you would be left with implementing the "we can do" block only. Some of them are more "functional programming"-minded, some take a more traditional approach, have a look around - it's not always worth it to reinvent the wheel.
Yes, I agree. I'll do some digging. Thanks a bunch!
@nadz, why do you accept this answer even the expected output is not the same as you suggest? Just curious.
With the more "functional programming" approach that I took above, you get higher function reusability and composability, i.e. you can make a function filterByClass = filterByProp("Class"), and keep it around, and specialize it for the filter value only when needed, or you could use it to do filtering elsewhere.
|
2

This is not the most efficient way but more clean codes only.

const result = [
  { Class: "Class C", Group: "Group A", Value: 1 },
  { Class: "Class C", Group: "Group A", Value: 2 },
  { Class: "Class A", Group: "Group B", Value: 2 },
  { Class: "Class C", Group: "Group B", Value: 10 },
];

const output = result
  .filter(obj => obj.Class.includes("Class C"))
  .reverse()
  .filter(
    (obj, i, arr) => i === arr.findIndex(obj2 => obj.Group === obj2.Group)
  )
  .reverse();
  
console.log(output);

Comments

0

I don't think this is necessarily better than yours, but it employs a few different approaches. Namely, sorting the data first so you don't have to query to find the largest value and filtering first to get rid of dupes.

let classFilter = "Class C", filtered = 
  // We're making an object that should be delivered as an array
  Object.values(result  
     // first sort by value so when we filter, we capture the highest if there are duplicates
     .sort((a, b) => (a.Value < b.Value) ? 1 : -1)
     // filter out any duplicates of our target classFilter
     .filter(e => e.Class === classFilter)
     // get unique only Groups
     .reduce((b, a) => {
        if (!b.hasOwnProperty(a.Group)) b[a.Group] = a;
        return b;
     }, {})
  )

let result = [{
    "Class": "Class C",
    "Group": "Group A",
    "Value": 1
  },
  {
    "Class": "Class C",
    "Group": "Group A",
    "Value": 2
  },
  {
    "Class": "Class A",
    "Group": "Group B",
    "Value": 2
  },
  {
    "Class": "Class C",
    "Group": "Group B",
    "Value": 10
  }
];

let classFilter = "Class C"

let filtered = Object.values(result.sort((a, b) => (a.Value < b.Value) ? 1 : -1).filter(e => e.Class === classFilter).reduce((b, a) => {
  if (!b.hasOwnProperty(a.Group)) b[a.Group] = a;
  return b;
}, {}))
console.log(filtered)

Comments

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.