-1
var persons = [
  { Color: "Gold", Location: ["Down"] },
  { Color: "Silver", Location: ["Up", "Down"] },
  { Color: "Silver", Location: ["Up"] }
];

var criteria = [
   { Field: "Color", Values: ["Silver"] },
   { Field: "Location", Values: ["Up", "Down"] }
];

Here field color is of type String, and Location is an array. I have persons, and then there is a filter criteria. I need an output so that all the values selected in the filter needs to match with the data. So in the data provided, only those records should be visible if Silver, Up and Down are available in a record. (Note the AND parameter, there is no OR condition anywhere).

So the output will be:

{ Color: "Silver", Location: ["Up", "Down"] }

Now if the filter criteria is:

var criteria = [
       { Field: "Color", Values: ["Silver"] },
       { Field: "Location", Values: ["Up"] }
    ];

the output will be:

{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }

So you see all the values in the filter should match with the records.

snl
  • 113
  • 1
  • 9
  • If you've found one of the answers below is working, please accept it so that others know a working solution is there for the question – Udayraj Deshmukh Feb 15 '18 at 08:46

3 Answers3

0

I broke down the problem into separate functions. It's way more verbose than your solution but I do think it's more readable.

Also: it does work.

var persons = [
  { Color: "Gold", Location: ["Down"] },
  { Color: "Silver", Location: ["Up", "Down"] },
  { Color: "Silver", Location: ["Up"] }
];

var criteria = [
   { Field: "Color", Values: ["Silver"] },
   { Field: "Location", Values: ["Up", "Down"] }
];

const arraysEqual = (arr1, arr2) => {
  // Very simple array comparison.
  if (arr1.length !== arr2.length) return false;
  arr1 = arr1.sort();
  arr2 = arr2.sort();
  for(let i=0; i < arr1.length; i++) {
    if (arr1[i] !== arr2[i]) return false;
  }
  return true;
};

let result = persons.filter(person => {
  // All criteria must match.
  for (let criterium of criteria) {
    if (criterium.Field === 'Color') {
      if (person.Color !== criterium.Values[0]) return false;
    }
    if (criterium.Field === 'Location') {
      if (!arraysEqual(person.Location, criterium.Values)) return false;
    }
  }
  // We didn't *not* match for anything, so we matched!
  return true;
});
console.log(result);

/*
{ Color: "Silver", Location: ["Down", "Up"] }
*/

See https://repl.it/@niels_bom/GoldenIdealMicrobsd for a running example.

Niels Bom
  • 8,728
  • 11
  • 46
  • 62
  • No. This is not working. Only this should be visible. I need to check both the values in Location field. { Color: 'Silver', Location: [ 'Up', 'Down' ] } – snl Feb 13 '18 at 00:22
  • Ah, that makes it easier. Except comparing arrays, that's not super simple. – Niels Bom Feb 13 '18 at 01:01
  • @Niels Bom It is sweet and simple - https://stackoverflow.com/questions/7837456/how-to-compare-arrays-in-javascript/19746771#19746771 – Udayraj Deshmukh Feb 13 '18 at 03:00
  • The arrays need to be sorted too. – Niels Bom Feb 13 '18 at 09:37
  • right, that's when I used `includes` in my answer. Can pre-sort the arrays to improve the speed, but seems it's not really needed here as criteria will be a few in number. – Udayraj Deshmukh Feb 13 '18 at 10:22
  • I was talking about the array comparison algorithm you linked to, not your solution :-) – Niels Bom Feb 13 '18 at 10:26
  • Based on the 2nd test case, partial matches are ok. You should take out the array length comparison in `arrayEquals`. – Andrew Feb 13 '18 at 20:48
0

After third edit, now I am clear on what you want - to have an array as a subset of the other.

var result = persons.filter(function (person) {
    return criteria.every(function (c) {
        var value = person[c.Field];
        if (typeof value === 'object') {
                return c.Values.length<=value.length && 
                c.Values.every(function(v,i){return value.includes(v) ;});
        }
        else
            return c.Values.indexOf(value) > -1;
    })
})

I also updated your jsfiddle : https://jsfiddle.net/gzL42dna/1/

Also checkout: How to compare arrays in JavaScript?

Udayraj Deshmukh
  • 1,814
  • 1
  • 14
  • 22
  • Hi, this code is not giving the desired output. Only this record should be displayed as output: { Color: 'Silver', Location: [ 'Up', 'Down' ] } In the criteria, both Up and Down values from Location should match. So there should be only 1 record in output. – snl Feb 13 '18 at 00:26
  • Its not working. Check this link: https://jsfiddle.net/gzL42dna/. I am still getting 2 records. – snl Feb 13 '18 at 00:46
  • All the values in the filter should match with the records. If in the filter, there are multiple values, then the record should also have all those values. – snl Feb 13 '18 at 00:57
  • Hi. It is working to a certain extent. But if the criteria is var criteria = [ { Field: "Color", Values: ["Silver"] }, { Field: "Location", Values: ["Up"] } ]; Then its returning only 1 record. It should return 2 records, 2nd and 3rd one. Any thoughts? – snl Feb 13 '18 at 03:46
  • 1
    okay, you were not being clear again. The above means filter's array should be a subset of a person element. Have you checked the link I've given? It hints to your final answer – Udayraj Deshmukh Feb 13 '18 at 07:48
  • 1
    Seems like you're new to the community, it's okay if you couldn't ask the question properly for the first time. Please make your questions more concise from next time and in this one, replace "output will be:" with "output should be:" to avoid confusion. – Udayraj Deshmukh Feb 13 '18 at 07:59
  • Ideally: add some testcases :-) That will also help yourself with your code. – Niels Bom Feb 13 '18 at 09:38
0

I believe this works. Using a Set helps a lot with partial matches. Tell me if you would like an explanation of the code.

var persons = [
  { Color: "Gold", Location: ["Down"] },
  { Color: "Silver", Location: ["Up", "Down"] },
  { Color: "Silver", Location: ["Up"] }
];

var criteria = [
   { Field: "Color", Values: ["Silver"] },
   { Field: "Location", Values: ["Up", "Down"] }
];

console.log(match(persons, criteria));

function match(persons, criteria) {
  let personMatches = [...persons]
  for (let i=0; i < criteria.length; i++) {
    let {Field, Values} = criteria[i]
    personMatches = personMatches.filter(obj => {
      if (Array.isArray(obj[Field])) {
        return hasMatches(obj[Field], Values)
      } else {
        return Values.includes(obj[Field])
      }
    })
  }
  return personMatches
}

function hasMatches(arr1, criteria) {
  let criteriaSet = new Set(criteria)
  let personsSet = new Set(arr1)
  for (let el of criteriaSet) {
    if (!personsSet.has(el)) return false
  }
  return true
}
Andrew
  • 7,201
  • 5
  • 25
  • 34