javascript find child object in nested arrays - javascript

I have a javascript structure like below (nested arrays of objects)
var categoryGroups = [
{
Id: 1, Categories: [
{ Id: 1 },
{ Id: 2 },
]
},
{
Id: 2, Categories: [
{ Id: 100 },
{ Id: 200 },
]
}
]
I want to find a child Category object matching an Id, assuming the Category Id's are all unique.
I've got this below, but was wondering if there is a more concise way of doing it:
var category, categoryGroup, found = false;
for (i = 0; i < categoryGroups.length ; i++) {
categoryGroup = categoryGroups[i];
for (j = 0; j < categoryGroup.Categories.length; j++) {
category = categoryGroup.Categories[j];
if (category.Id === id) {
found = true;
break;
}
}
if (found) break;
}

Using flatMap in ES2019
const category = categoryGroups.flatMap(cg => cg.Categories).find(c => c.Id === categoryId);

Caveat: This uses a couple of Array.prototype functions that were only added in ECMAScript 5 and thus will not work with older browsers unless you polyfill them.
You can loop over all first-level objects in your array, and then filter the categories based on your condition and collect all matches in an array. Your final result will be the first element in the array of matches (no match found if array is empty).
var matches = [];
var needle = 100; // what to look for
arr.forEach(function(e) {
matches = matches.concat(e.Categories.filter(function(c) {
return (c.Id === needle);
}));
});
console.log(matches[0] || "Not found");
JSFiddle: http://jsfiddle.net/b7ktf/1/
References:
Array.prototype.forEach
Array.prototype.concat
Array.prototype.filter

Using only Array.prototype.filter():
If you are sure that the id you are looking for exists, you can do:
var id = 200; // surely it exists
var category = arr.filter(g => g.Categories.filter(c => c.Id === id)[0])[0].Categories.filter(c => c.Id === id)[0];
If you are not sure that it exists:
var id = 201; // maybe it doesn't exist
var categoryGroup = arr.filter(e => e.Categories.filter(c => c.Id === id)[0])[0];
var category = categoryGroup ? categoryGroup.Categories.filter(c => c.Id === id)[0] : null;
jsfiddle

Using reduce and recursion :
function nestedSearch(value) {
return categoryGroups.reduce(function f(acc, val) {
return (val.Id === value) ? val :
(val.Categories && val.Categories.length) ? val.Categories.reduce(f, acc) : acc;
});
}
> try on JSFiddle

check the code in the fiddle
var categoryGroups = [
{
Id: 1, Categories: [
{ Id: 1 },
{ Id: 2 },
]
},
{
Id: 2, Categories: [
{ Id: 100 },
{ Id: 200 },
]
}
]
var id = 100;
var x = 'not found';
var category, categoryGroup, found = false;
for (i = 0; i < categoryGroups.length ; i++) {
categoryGroup = categoryGroups[i];
for (j = 0; j < categoryGroup.Categories.length; j++) {
category = categoryGroup.Categories[j];
if (category.Id == id) {
var x = category.Id;
found = true;
break;
}
}
if (found) break;
}
alert(x);
The above code checks if id = 100 is found in the array. If found will alert the value else alerts that its not found. value '100' has been hardcoded for the sake of demo

You could wrap it inside a function to get rid of the awkward break; syntax and you can load each element into a variable inside the for(;;) construct to shave off a few lines.
function subCategoryExists(groups, id)
{
for (var i = 0, group; group = groups[i]; ++i) {
for (var k = 0, category; category = group.Categories[k]; ++k) {
if (category.Id == id) {
return true;
}
}
}
return false;
}
var found = subCategoryExists(categoryGroups, 100);

Easy way using lodash library of NodeJS (assuming you are using NodeJS):
const _ = require('lodash');
let category ;
let categoryGroup = _.find(categoryGroups, (element)=>{
category = _.find(element.Categories, {Id : 100});
return category;
});
console.log(categoryGroup); // The category group which has the sub category you are looking for
console.log(category); // The exact category you are looking for

If you want to actually return the inner category (instead of just checking for it's presence) you can use reduce:
return categoryGroups.reduce((prev, curr) => {
//for each group: if we already found the category, we return that. otherwise we try to find it within this group
return prev || curr.Categories.find(category => category.Id === id);
}, undefined);
This short-circuits on the inner categories, and touches each categoryGroup once. It could be modified to short-cicuit on the categoryGroups as well.
Here's a JS Fiddle demonstration.

You could use underscore:
var cat = _(categoryGroups).
chain().
pluck('Categories').
flatten().
findWhere({Id: 2}).
value();
What I'm doing here is that I'm extracting all Categories values in a single array and then grepping for the correct categories.
EDIT: sorry, didn't get your question right the first time. As the comments suggest, you might not want to use underscore just for that, but that's how I would do it :)

We are using object-scan for our data processing now. It's very powerful once you wrap your head around it. For your questions this would look like this:
// const objectScan = require('object-scan');
const lookup = (id, data) => objectScan(['Categories.Id'], {
useArraySelector: false,
abort: true,
rtn: 'parent',
filterFn: ({ value }) => value === id
})(data);
const categoryGroups = [{ Id: 1, Categories: [{ Id: 1 }, { Id: 2 }] }, { Id: 2, Categories: [{ Id: 100 }, { Id: 200 }] }];
console.log(lookup(1, categoryGroups));
// => { Id: 1 }
console.log(lookup(100, categoryGroups));
// => { Id: 100 }
console.log(lookup(999, categoryGroups));
// => undefined
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

Related

Filter or compare a value in the same array [duplicate]

I have an array like this:
const array=[ {id:0, quantity:1}, {id:1, quantity:2}, {id:0, quantity:4} ]
My GOAL is to be like this:
const array=[ {id:1, quantity:2}, {id:0, quantity:4} ]
The order of the object does not matter as long as it can find the 'id' with the larger quantity
I tried filter + findIndex, map +filter, etc but I kept making mistake. I need help.
You could use a hash table and check if an object with the same id is in the result set. If the actual quantity is greater, assign the actual object.
var array = [{ id: 0, quantity: 1 }, { id: 1, quantity: 2 }, { id: 0, quantity: 4 }],
hash = Object.create(null),
unique = array.reduce(function (r, o) {
if (!(o.id in hash)) {
hash[o.id] = r.push(o) - 1;
return r;
}
if (o.quantity > r[hash[o.id]].quantity) {
r[hash[o.id]] = o;
}
return r;
}, []);
console.log(unique);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Let's get all the ids first. Then for each unique id, we'll find all the relevant objects in the array (using filter) and get the maximum of all their quantity properties.
function uniqByMax(arr) {
const ids = arr.map(elt => elt.id);
const uniqueIds = uniq(ids);
return uniqueIds.map(id => {
const matchingElts = arr.filter(elt => elt.id === id);
const quantities = matchingElts.map(elt => elt.quantity);
const quantity = Math.max(...quantities);
return {id, quantity};
});
}
You can grab uniq off the net somewhere, or use a library, or write it yourself.
Here is another approach, which uses a filter with side effects, if that is your cup of tea:
function uniqByMax(arr) {
return arr.filter(elt => {
const previous = arr.find(e2 => e2.id === elt.id);
if (previous === elt) return true;
previous.quantity = Math.max(previous.quantity, elt.quantity);
});
}
The basic idea is to loop through the elements. For each element, we find if there is an earlier element with the same id. If not, retain this element (return true;); otherwise, update the quantity of the earlier element we retained with the maximum of its quantity and this element's quantity.
In the interest of generality, it could be interesting to parameterize this function by the property we are finding unique values of, and the way to update/combine multiple items:
function uniqTransform(arr, prop, combine) {
return arr.filter(elt => {
const previous = arr.find(e2 => e2[prop] === elt[prop]);
if (previous === elt) return true;
combine(previous, elt);
});
Then we call this with
uniqTransform(arr, 'id', (a, b) => a.quantity = Math.max(a.quantity, b.quantity));
Or we could generalize it further by using another function to identify elements which are supposed to be considered the same for uniqueifying purposes, which we will call uniqueFunc:
function uniqTransform(arr, uniqueFunc, combineFunc) {
return arr.filter(elt => {
const previous = arr.find(e2 => uniqueFunc(elt, e2));
if (previous === elt) return true;
combineFunc(previous, elt);
});
Then we call this with
uniqTransform(
arr,
(a, b) => a.id === b.id,
(a, b) => a.quantity = Math.max(a.quantity, b.quantity));
Here you go.
const array=[ {id:0, quantity:1}, {id:1, quantity:2}, {id:0, quantity:4} ];
const arrayFiltered = [];
array.forEach(obj => {
const item = arrayFiltered.find(thisItem => thisItem.id === obj.id);
if (item) {
if (item.quantity < obj.quantity) {
item.quantity = obj.quantity;
}
return;
}
arrayFiltered.push(obj);
});
console.log(arrayFiltered);
Can you work with objects? There's a simple alternative using this code
const array = [{id:0, quantity:1}, {id:1, quantity:2}, {id:0, quantity:4}]
const object = {}
array.forEach((element) => {
object[element.id] = element
})
The only problem is that you will override the previous element each time you found an element with the same id.

compare two arrays and remove duplicates [duplicate]

I created an array of objects like so:
[
{
"lat": 12.123,
"lng": 13.213,
"city": "New York"
},
{
"lat": 3.123,
"lng": 2.213,
"city": "New York"
},
{
"lat": 1.513,
"lng": 1.113,
"city": "London"
}
]
I'm trying to create a new array that filters the places to only contains objects that don't have the same city property (lat/lng duplicates are ok). Is there a built in JS or Jquery function to achieve this?
I'd probably use a flags object during the filtering (edit: I wouldn't anymore, see the note at the end of the answer about ES2015's Set), like this:
var flags = {};
var newPlaces = places.filter(function(entry) {
if (flags[entry.city]) {
return false;
}
flags[entry.city] = true;
return true;
});
That uses Array#filter from ECMAScript5 (ES5), which is one of the ES5 additions that can be shimmed (search for "es5 shim" for several options).
You can do it without filter, of course, it's just a bit more verbose:
var flags = {};
var newPlaces = [];
var index;
for (index = 0; index < places.length; ++index) {
if (!flags[entry.city]) {
flags[entry.city] = true;
newPlaces.push(entry);
}
});
Both of the above assume the first object with a given city should be kept, and all other discarded.
Note: As user2736012 points out below, my test if (flags[entry.city]) will be true for cities with names that happen to be the same as properties that exist on Object.prototype such as toString. Very unlikely in this case, but there are four ways to avoid the possibility:
(My usual preferred solution) Create the object without a prototype: var flags = Object.create(null);. This is a feature of ES5. Note that this cannot be shimmed for obsolete browsers like IE8 (the single-argument version of Object.create can be except when that argument's value is null).
Use hasOwnProperty for the test, e.g. if (flags.hasOwnProperty(entry.city))
Put a prefix on that you know doesn't exist for any Object.prototype property, such as xx:
var key = "xx" + entry.city;
if (flags[key]) {
// ...
}
flags[key] = true;
As of ES2015, you could use a Set instead:
const flags = new Set();
const newPlaces = places.filter(entry => {
if (flags.has(entry.city)) {
return false;
}
flags.add(entry.city);
return true;
});
Shortest, but not best performance (see update bellow) solution for es6 :
function unique(array, propertyName) {
return array.filter((e, i) => array.findIndex(a => a[propertyName] === e[propertyName]) === i);
}
performance: https://jsperf.com/compare-unique-array-by-property
You can filter using a Set by only including elements with a property value that has not yet been added to the Set (after which it should be added to the Set). This can be accomplished in one line using the logical and operator (&&). Using this data structure has the advantage of sublinear lookup times (often O(1)).
Below is a general function to obtain a unique array of objects based on a specific property (prop) from an array of objects (arr). Note that in the case of duplicates, only the first object with the property value will be retained.
const getUniqueBy = (arr, prop) => {
const set = new Set;
return arr.filter(o => !set.has(o[prop]) && set.add(o[prop]));
};
Demo:
var places = [{
lat: 12.123,
lng: 13.213,
city: 'New York'
}, {
lat: 3.123,
lng: 2.213,
city: 'New York'
}, {
lat: 3.123,
lng: 4.123,
city: 'Some City'
}];
const getUniqueBy = (arr, prop) => {
const set = new Set;
return arr.filter(o => !set.has(o[prop]) && set.add(o[prop]));
};
console.log(getUniqueBy(places, 'city'));
https://lodash.com/docs#uniqBy
https://github.com/lodash/lodash/blob/4.13.1/lodash.js#L7711
/**
* This method is like `_.uniq` except that it accepts `iteratee` which is
* invoked for each element in `array` to generate the criterion by which
* uniqueness is computed. The iteratee is invoked with one argument: (value).
*
* #static
* #memberOf _
* #since 4.0.0
* #category Array
* #param {Array} array The array to inspect.
* #param {Array|Function|Object|string} [iteratee=_.identity]
* The iteratee invoked per element.
* #returns {Array} Returns the new duplicate free array.
* #example
*
* _.uniqBy([2.1, 1.2, 2.3], Math.floor);
* // => [2.1, 1.2]
*
* // The `_.property` iteratee shorthand.
* _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
* // => [{ 'x': 1 }, { 'x': 2 }]
*/
I expanded a bit on #IgorL solution, but extended prototype and gave it a selector function instead of a property to make it a little more flexible:
Array.prototype.unique = function(selector) {
return this.filter((e, i) => this.findIndex((a) => {
if (selector) {
return selector(a) === selector(e);
}
return a === e;
}) === i);
};
Usage:
// with no param it uses strict equals (===) against the object
let primArr = ['one','one','two','three','one']
primArr.unique() // ['one','two','three']
let a = {foo:123}
let b = {foo:123}
let fooArr = [a,a,b]
fooArr.unique() //[a,b]
// alternatively, you can pass a selector function
fooArr.unique(item=>item.foo) //[{foo:123}] (first "unique" item returned)
Definitely NOT the most performant way to do this but as long as the selector is simple and the array isn't massive, it should work fine.
In Typescript
Array.prototype.unique = function<T>(this: T[], selector?: (item: T) => object): T[] {
return this.filter((e, i) => this.findIndex((a) => {
if (selector) {
return selector(a) === selector(e);
}
return a === e;
}) === i);
};
My suggestion :
Array.prototype.uniqueCity = function() {
var processed = [];
for (var i=this.length-1; i>=0; i--){
if (processed.indexOf(this[i].city)<0) {
processed.push(this[i].city);
} else {
this.splice(i, 1);
}
}
}
in use :
places.uniqueCity();
or
Array.prototype.uniqueObjectArray = function(field) {
var processed = [];
for (var i=this.length-1; i>=0; i--) {
if (this[i].hasOwnProperty(field)) {
if (processed.indexOf(this[i][field])<0) {
processed.push(this[i][field]);
} else {
this.splice(i, 1);
}
}
}
}
places.uniqueObjectArray('city');
With the above you can sort the array by any of the fields in the objects, even if they are not present for some of the objects.
or
function uniqueCity(array) {
var processed = [];
for (var i=array.length-1; i>=0; i--){
if (processed.indexOf(array[i].city)<0) {
processed.push(array[i].city);
} else {
array.splice(i, 1);
}
}
return array;
}
places = uniqueCity(places);
You could use a Map so the entries with the same key property (in your case 'city') only appear once
module.exports = (array, prop) => {
const keyValueArray = array.map(entry => [entry[prop], entry]);
const map = new Map(keyValueArray);
return Array.from(map.values());
};
More info about Map and array objects here
Basic example on Codepen
Another option:
const uniqueBy = prop => list => {
const uniques = {}
return list.reduce(
(result, item) => {
if (uniques[item[prop]]) return result
uniques[item[prop]] = item
return [...result, item]
},
[],
)
}
const uniqueById = uniqueBy('id')
uniqueById([
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
{ id: 1, name: 'one' },
{ id: 3, name: 'three' }
])
You can paste it on your console to see it working.
It should work for the scenario presented and a few others.
We can create the list of unique objects by any property using JavaScript Map.
For example :
var places = [{ 'lat': 12.123, 'lng': 13.213, 'city': "New York"},
{ 'lat': 3.123, 'lng': 2.213, 'city': "New York"},
{ 'lat': 43.123, 'lng': 12.213, 'city': "London"}];
var cityMap = new Map();
places.forEach(p=> cityMap.set(p.city, p));
console.log([...cityMap.values()]);
Execute code snippet to see the result.
As pointed out in the comments, you could use an object as a map, which will allow you to avoid duplicates, you can then enumerate the properties of the object.
working fiddle: http://jsfiddle.net/gPRPQ/1/
var places = [];
var a = {};
a.lat = 12.123;
a.lng = 13.213;
a.city = "New York";
places.push(a);
var b = {};
b.lat = 3.123;
b.lng = 2.213;
b.city = "New York";
places.push(b);
var unique = {}
for (var i = 0; i < places.length; i++) {
var place = places[i];
unique[place.city] = place;
}
for (var name in unique) {
var place = unique[name];
console.log(place);
}
var places = [];
var a = {};
a.lat = 12.123;
a.lng = 13.213;
a.city = "New York";
places.push(a);
var b = {};
b.lat = 3.123;
b.lng = 2.213;
b.city = "New York";
places.push(b);
getUniqAR(places,'city'); //Return Uniq Array by property
function getUniqAR(Data,filter){
var uniar =[];
Data.forEach(function(item,ind,arr){
var dupi=false;
if(!uniar.length) uniar.push(item) //push first obj into uniq array
uniar.forEach(function(item2, ind2,arr){
if(item2[filter] == item[filter]){ //check each obj prop of uniq array
dupi=true; //if values are same put duplicate is true
}
})
if(!dupi){ uniar.push(item)} //if no duplicate insert to uniq
})
console.log(uniar)
return uniar;
}
In simple Javascript code to remove duplicate cities from places array list is
var places = [{ 'lat': 12.123, 'lng': 13.213, 'city': "New York"},
{ 'lat': 3.123, 'lng': 2.213, 'city': "New York"},
{ 'lat': 43.123, 'lng': 12.213, 'city': "London"}];
var unique = [];
var tempArr = [];
places.forEach((value, index) => {
if (unique.indexOf(value.city) === -1) {
unique.push(value.city);
} else {
tempArr.push(index);
}
});
tempArr.reverse();
tempArr.forEach(ele => {
places.splice(ele, 1);
});
console.log(places);
Generic Typescript answer based on https://stackoverflow.com/a/18773857/49564 above:
export function isDistinct<T>(mapper: (value: T) => string): (value: T) => boolean {
const keys: { [index: string]: boolean } = {};
return (entry: T) => {
const key = mapper(entry);
if (keys[key] !== undefined) {
return false;
}
return keys[key] = true;
};
}
// Usage example:
const items = [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 1 } ];
const unique = items.filter(isDistinct(i => i.id));
I think you want this,
NOTE: No library is required.
let array = [{ id: 1}, {id: 2}, {id: 3}];
function addUniqeObj(data) {
let index = -1;
for(let i = 0, i < array.length; i++) {
if(array[i].id === data.id) {
index = i;
}
}
if(index > -1) {
array[index] = data;
} else {
array.push(data)
}
}
Another variation of the rafaelbiten approach:
const dedupExample = [
{id: 1, c: 'whatever'},
{id: 1, c: '1whatever'},
{id: 2, c: '2whatever'},
{id: 2, c: '2whatever'},
{id: 3, c: '2whatever'},
]
const getUniqueBy = (prop, list) => {
const objUniq = list.reduce((res, item) => ({ ...res, [item[prop]]: item }), {})
return Object.keys(objUniq).map(item => objUniq[item])
}
const uniq = getUniqueBy('id', dedupExample)
console.info('info', { uniq })
/* [
{id: 1, c: 'whatever'},
{id: 2, c: '2whatever'},
{id: 3, c: '2whatever'},
] */
const distinctArrayByCity= [
...new Map(array.map((item) => [item.city, item])).values(),
];
This thread may be old but thought I should share it. It is based on Pure JavaScript and removes Duplicate Objects based on the Properties Specified.
function removeDuplicates(originalArray, properties) {
var newArray = [];
var index = 0;
var lookupObject = {};
var totalProperties = properties.length;
for (var i = 0; i < originalArray.length; i++) {
var exists = false;
for (var a = 0; a < newArray.length; a++) {
var propsFound = 0;
for (var b = 0; b < totalProperties; b++) {
if (originalArray[i][properties[b]] == newArray[a][properties[b]]) {
propsFound++;
}
}
//If there is a match then break the for loop
if (propsFound == totalProperties) {
exists = true;
break;
}
} //End of New Array
if (!exists) {
newArray[index] = originalArray[i];
index++;
}
} //End of originalArray
return newArray;
}
You can view the fiddle here

Loop into array object then check if there's a equal value?

Question, I have this array object, I want to find out which of this array have a similar values
then make them as one.
Example
[0:
cartProduct: {
category: "chair"
color: "navy"
id: "628a1738fd8299ae6659d994"
image: "http://localhost:5000/../public/Product_chair_communal-navy.jpg"
name: "The Communal"
price: "4.30"
}
quantity: 1,
1:
cartProduct: {{
category: "chair"
color: "navy"
id: "628a1738fd8299ae6659d994"
image: "http://localhost:5000/../public/Product_chair_communal-navy.jpg"
name: "The Communal"
price: "4.30"
}
quantity: 1,
]
For example the data above I want to know if they have the similar values interms of color if yes then only return one value.
Thanks!
You can use this loop:
let uniqueArray = [];
dataArray.forEach((item, indx) => {
let colorsArray = [];
if (colorsArray.includes(item.color)) {
continue;
}
uniqueArray.push(item);
})
Not the cleanest, or most performant approach:
// function to group the items
const groupCartItems = (items, byProperties = ['color', 'id']) => {
// utility funciton
const verifyEquality = (itemA, itemB) => {
let isEqual = true;
byProperties.forEach((prop) => {
if (itemA.cartProduct[prop] != itemB.cartProduct[prop]) {
isEqual = false;
break;
}
});
return isEqual;
};
const groupedItems = [];
items.forEach((item) => {
// if item has been added, skip
if (groupedItems.find((i) => verifyEquality(item, i))) {
return;
}
// find equal items
const equals = items.filter((i) => verifyEquality(item, i));
// sum quantities
const quantity = equals.reduce((previousValue, data) => previousValue + data.quantity, 0);
// push
groupedItems.push({
cartProduct: item.cartProduct,
quantity,
});
});
return groupedItems;
};
For the 'similarity' stuff, I would recommend not to do this, because it is just not a good practise. Have your values equal or else!!!
Now seriously, check string-similarity. From documentation, you would only need to change the if inside verifyEquality function to:
import stringSimilarity from 'string-similarity';
// tweak this value to change how similar strings should be to be considered equal
const EQUALITY_RATIO = 0.75;
// ....
if (stringSimilarity.compareTwoStrings(itemA.cartProduct[prop], itemB.cartProduct[prop]) < EQUALITY_RATIO) {
}

Compare array objects and show difference

I have two arrays which I want to compare and check if there is an deleted item in one of these arrays. If there is show me the difference (deleted item)
Here is the code below how I would like to achieve this:
var completedList = [{id:1},{id:2},{id:3},{id:4},{id:7},{id:8}];
var invalidList = [{id:3},{id:4},{id:5},{id:6}];
// filter the items from the invalid list, out of the complete list
var validList = completedList.map((item) => {
console.log(item.id)
return item.id;
//console.log(invalidList.id);
}).filter(item => {
Object.keys(invalidList).map(key => {
console.log(invalidList[key].id)
//return !invalidList[key].id.includes(item.id);
});
})
console.log(validList); // Print [1,2,7,8]
// get a Set of the distinct, valid items
var validItems = new Set(validList);
But this returns me a lot of id's how can I map through both array's and filter on object property id? And only show the difference between these array objects.
So basically what I expect is to see the difference between those arrays so log the differences in id's so in this example: 1,2,5,6,7,8
You could take a Set for getting a difference. For getting the differences from each other (a symmetric difference), you need to get both differences.
const
difference = (a, b) => Array.from(b.reduce((s, v) => (s.delete(v), s), new Set(a))),
getId = ({ id }) => id;
var completedList = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 7 }, { id: 8 }],
invalidList = [{ id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }],
complete = completedList.map(getId),
invalid = invalidList.map(getId),
left = difference(complete, invalid),
right = difference(invalid, complete),
result = [...left, ...right]
console.log(result.join(' '));
console.log(left.join(' '));
console.log(right.join(' '));
This should do the trick.
let completedList = [{id:1},{id:2},{id:3},{id:4},{id:7},{id:8}];
let invalidList = [{id:3},{id:4},{id:5},{id:6}];
// filter the items from the invalid list, out of the complete list
let temp1 = completedList.map(e => e.id);
let temp2 = invalidList.map(e => e.id);
let validList = temp1.filter(e => temp2.indexOf(e) === -1);
// find items only in invalidList
let difference = temp2.filter(e => temp1.indexOf(e) === -1);
console.log(validList); // Print [1,2,7,8]
console.log(difference);
I often rely on lodash implementation for comparison.
In lo dash you can get the job done following manner
_.intersectionWith(arr1, arr2, _.isEqual) - For similarity
_.differenceWith(arr1, arr2, _.isEqual) - for differences
This ans is confined to using a util library to get the job done.
If you are looking for the exact algo I would definitely take some time to develop it and reply as a comment to this post .
Thanks
var completedList = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 7 }, { id: 8 }];
var invalidList = [{ id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }];
//get the items that are in the invalid list but not completed list
var filteredList1 = invalidList.filter((invalidListItem) => !completedList.find((item) => item.id === invalidListItem.id));
//get the items that are in the completed list but not in the invalid list
var filteredList2 = completedList.filter((completedListItem) => !invalidList.find((item) => item.id === completedListItem.id));
//join the two arrays
var difference = filteredList1.concat(filteredList2);
//display the merged array and sort
console.log(difference.sort((item1, item2) => { return item1.id > item2.id ? 1 : item1.id < item2.id ? -1 : 0; }));
//outputs 1,2,5,6,7,8

JavaScript - Filter object based on multiple values

I need to filter some data based on multiple values. Language, title and slug
[
{
de: "4567uy55",
en: "654321",
lang: [
{
id: "654321",
language: "English",
title: "Title1"
},
{
id: "4567uy55",
language: "German",
title: "Title2"
}
],
slug: 'some-slug'
},
...
]
What I have now returns all objects which have one or part of the filters(in case title is This is a title, the word this should match), but I need to return objects which have all of them.
I used an object flattner just to get all properties and values in one object, but I can't get it to filter the way I need it.
multiFilter = (arr, filters) => {
console.log(filters)
console.log(arr)
let newArray = []
for (let c of arr) {
let flatCourse = flatten(c)
for (let k in flatCourse) {
const keyArr = k.split('/')
const filterKeys = Object.keys(filters)
Object.keys(filters).map((key) => {
if (keyArr.includes(key)) {
const flatVal = flatCourse[k].toString().toLowerCase()
const filterVal = filters[key].toString().toLowerCase()
console.log(flatVal)
console.log(filterVal)
if (flatVal.includes(filterVal)) {
arr = []
arr.push(c)
newArray.push(c)
}
}
})
}
}
return newArray
}
Filters look like this:
[
language:["English"],
title: ["Some title"],
slug:["some slug"]
]
Instead of mixing for loops and functional chaining you could just go with one of them:
multiFilter = (arr, filters) =>
arr.map(flatten).filter(el => // filter out elements from arr
Object.entries(filters).every(([fKey, fValues]) => // ensure that every key is included in the object
Object.entries(el).some(([oKey, oValue]) =>
oKey.split("/").includes(fKey) && fValues.includes(oValue)// make sure that at least one of the values equals the elements value
)
)
);
arr.filter(course => {
// Returns an array of booleans corresponding to whether or not each filter condition is satisfied
return Object.keys(filters).map(key => {
return filters[key].map(filter => {
// Special case here because lang is an array
if (key == 'language' && course.lang != undefined) {
return course.lang.some(lang => lang[key].includes(filter))
}
if (course[key] == undefined) {
return false
}
return course[key].includes(filter)
}).every(result => result == true)
}).every(result => result == true)
})

Categories

Resources