1
\$\begingroup\$

This is a function which filters on multiple object properties and their corresponding arrays.

  • I need to match all properties to a pattern and have an array as their property value.
  • Properties that match that pattern and contain an array get filtered on whether they contain string values.
  • The values can be in any one of the arrays related to that object.

  • All objects which have met the criteria and contain the appropriate strings irrespective of the property array they are contained in. With the exception being that all values are in the same object.

  • The object containing all string values do not get filtered out.


If I match all object properties containing 'ev_' and those properties contain arrays whose values include 'value75' and '12+'

This should be the resulting output

{
  "data": {
    "id": 3,
    "date": "2019-02-03",
    "ev_filter_1": [
      "art",
      "foodie"
    ],
    "ev_filter_2": [
      "value1",
      "value75"
    ],
    "ev_filter_3": [
      "value1",
      "value2"
    ],
    "ev_filter_4": [
      "all",
      "12+"
    ]
  }
}

From this array of objects

const posts = [{
    data: {
      id: 1,
      date: "2019-02-03",
      ev_filter_1: ["art", "foodie"],
      ev_filter_2: ["value1", "value2"],
      ev_filter_3: ["value1", "value2"],
      ev_filter_4: ["all", "12+"]
    }
  },
  {
    data: {
      id: 2,
      date: "",
      ev_filter_1: ["arti", "foodie"],
      ev_filter_2: ["value1", "value2"],
      ev_filter_3: ["value1", "value2"],
      ev_filter_4: ["all", "19+"]
    }
  },
  {
    data: {
      id: 3,
      date: "2019-02-03",
      ev_filter_1: ["art", "foodie"],
      ev_filter_2: ["value1", "value75"],
      ev_filter_3: ["value1", "value2"],
      ev_filter_4: ["all", "12+"]
    }
  }
];

This example is currently working but it is ugly and rigid. The "some" is just used for iteration, which I painfully coerced to return true. There is also a nested if statement (horrid).

const posts = [{
    data: {
      id: 1,
      date: "2019-02-03",
      ev_filter_1: ["art", "foodie"],
      ev_filter_2: ["value1", "value2"],
      ev_filter_3: ["value1", "value2"],
      ev_filter_4: ["all", "12+"]
    }
  },
  {
    data: {
      id: 2,
      date: "",
      ev_filter_1: ["arti", "foodie"],
      ev_filter_2: ["value1", "value2"],
      ev_filter_3: ["value1", "value2"],
      ev_filter_4: ["all", "19+"]
    }
  },
  {
    data: {
      id: 3,
      date: "2019-02-03",
      ev_filter_1: ["art", "foodie"],
      ev_filter_2: ["value1", "value75"],
      ev_filter_3: ["value1", "value2"],
      ev_filter_4: ["all", "12+"]
    }
  }
];

function sift(arrObjLit, pattern, argOne, argTwo) {
  return arrObjLit.filter(function(x, y) {
    return (result = Object.entries(x).some((q, i) => {
      for (let [key, v] of Object.entries(q[1])) {
        if (key.startsWith(pattern) && Array.isArray(v) && v.includes(argOne)) {
          for (let an of Object.values([x.data][0])) {
            if (Array.isArray(an) && an.includes(argTwo)) {
              return true;
            }
          }
        }
      }
    }))
  })
}

console.log(...sift(posts, "ev_", "value75", "12+"));

\$\endgroup\$
9
  • \$\begingroup\$ Please reformat the question to be more bullet point-ish. I am trying to sort through the requirements \$\endgroup\$ Commented Jul 11, 2017 at 0:59
  • \$\begingroup\$ @ChristianBongiorno will do \$\endgroup\$
    – Rick
    Commented Jul 11, 2017 at 1:00
  • \$\begingroup\$ Can your posts structure be flattened or does everything have to be stored within a data object? Could there be more/different properties on the object that need to be checked for the filter value? \$\endgroup\$
    – Gerrit0
    Commented Jul 11, 2017 at 1:41
  • \$\begingroup\$ @Gerrit0, in general, the pattern can be anything, I used startsWith, but a regular expression can just as easily take its place. One of the most difficult parts of this question is the odd nesting of the data, as a requirement, everything should be stored in the data object. However, if no answer is forthcoming I am willing to accept some flattening, but that is not the desired solution. \$\endgroup\$
    – Rick
    Commented Jul 11, 2017 at 1:52
  • \$\begingroup\$ Just to make sure, should {data: {ev_1: ["value75"] }, somethingElse: {ev_2: ["12+"]} } be matched? \$\endgroup\$
    – Gerrit0
    Commented Jul 11, 2017 at 1:55

2 Answers 2

2
\$\begingroup\$

First, and I can't emphasize this enough, use meaningful variable names. What is argOne? argTwo? The same goes for all of your single letter variable names. They make it harder to look at your code and figure out what's going on.

To help you see where the code you provided might be difficult to understand, I've copied it and commented what came to mind when reading each line, hopefully this can help with writing code that self-documents.

// It looks like this will filter arrObjLit based on pattern.
// But what is argOne, argTwo? Do I need both? What if I include argThree?
function sift(arrObjLit, pattern, argOne, argTwo) {
  // Filtering as expected, but why is the index y needed? 
  return arrObjLit.filter(function(x, y) {
    // Where did result come from? Right now it declares a global variable.
    // What is q? Is the index of it, i, needed?
    // Does this need to be wrapped in parenthesis?
    // The above line used function, here there's an arrow function. Why?
    return (result = Object.entries(x).some((q, i) => {
      // Okay, q[1] is the value, but q[0] isn't used, maybe 
      // Object.values() should be used above?
      // What happens if q isn't an object?
      for (let [key, v] of Object.entries(q[1])) {
        // This is clear, but what is special about argOne? 
        // Why not check argTwo here too?
        if (key.startsWith(pattern) && Array.isArray(v) && v.includes(argOne)) {
          // Why is [x.data][0] used instead of just x.data?
          // x.data == q[1]? What happens if q[1] != x.data?
          for (let an of Object.values([x.data][0])) {
            // Why don't we check that the key for the an array starts 
            // with key like we did for argOne?
            if (Array.isArray(an) && an.includes(argTwo)) {
              return true;
            }
          }
        }
      }
    }))
  })
}

In chat, you mentioned that ideally, every array contained within the object should be searched, if it matches the pattern. With this in mind, here is how I would solve your problem. (It might be a good idea to rename pattern to prefix, with pattern I would expect that I can pass in a regular expression)

function sift(entries, pattern, ...filters) {
  return entries.filter(entry => {
    // Copy the filters array into a set
    let toMatch = new Set(filters);

    let searchForMatches = root => {
      for (let [key, value] of Object.entries(root)) {
        if (Array.isArray(value) && key.startsWith(pattern)) {
          // The key for the array matches the pattern, so remove all of these
          // items from the required items to match, if they are found
          value.forEach(item => toMatch.delete(item));
        } else if (typeof value == 'object' && value) {
          // This item isn't null, and is an object.
          // It may contain an array, recurse
          searchForMatches(value);
        }
      }
    };

    searchForMatches(entry);

    // If the set is empty, all matches were found
    // and this object should be included in the result.
    return toMatch.size == 0;
  });
}
\$\endgroup\$
6
  • \$\begingroup\$ I should have said the same thing about your variables. \$\endgroup\$ Commented Jul 11, 2017 at 4:34
  • \$\begingroup\$ @ChristianBongiorno , I'd love to hear any suggestions you have to increase the clarity - or feel free to submit an edit :) \$\endgroup\$
    – Gerrit0
    Commented Jul 11, 2017 at 4:57
  • \$\begingroup\$ In general: extra variables should be toss. The ones that are around need to be clearly named. It would help to declare intermediate functions instead of nesting returns \$\endgroup\$ Commented Jul 11, 2017 at 5:28
  • \$\begingroup\$ @ChristianBongiorno, I originally did split the function up into two parts, with the recursive function placed on its own and returning an array of values to be checked, however this increased the complexity as it required searchForMatches to receivec 3 parameters (root, pattern, array) and also reduced the speed since it resulted in each item being checked twice (granted, speed could still be improved - but would make the code a bit messier). As far as I can tell, there are no extra variables. As for names, I'd still like to hear any suggestions, I'm not sure how to improve them. \$\endgroup\$
    – Gerrit0
    Commented Jul 11, 2017 at 11:52
  • \$\begingroup\$ @Gerrit0 why don't you use strict equality? \$\endgroup\$
    – Rick
    Commented Jul 11, 2017 at 19:15
1
\$\begingroup\$

Try this script:

function sift2(arrObjLit, pattern, ...values) {
  const toMatch = new Set(values)
  return arrObjLit.filter(o =>
    Object.entries(o.data)
    .some(([k, v]) => {
      return k.startsWith(pattern)
          && Array.isArray(v)
          && v.some(x => toMatch.has(x));
    })
  );
}

The idea is to decompose it, apply the filtering and then reconstruct it.

part of the problem is this code mixes development paradigms: it uses function and => and for

\$\endgroup\$
1
  • \$\begingroup\$ @ChristianBogiorno In your update, you're third conditional on the second return 'some'. It will produce false positives because it will run through all arrays returning true and false several times. \$\endgroup\$
    – Rick
    Commented Jul 12, 2017 at 1:53

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.