Comparing Objects and getting only ones that exist in all - javascript

I'm building a filtering method for Mixitup and I need to be able to correctly filter against x selected parameters. (The plugin here isn't important, its how I get my correct end object)
I currently have an object, that for each unique search is sending a unique key, and its matching objects (obtained via the filter method) into my filtering function.
This is where I am lost.
I need to be able to loop over my object, and it's associated key => values (objects), and fetch out only the objects that exist in each.
For example, (instead of numbers I have jQuery objects)
var filter = {
x : {1,3,5,6},
y : {1,4,7,8},
z : {1,9}
}
Based on the above example, the only returned object would be - 1 (as its the only one to exist in all three keys.
Any help would be greatly appreciated!

A short approach with Array.reduce and Array.filter:
Basically it starts with the first array as start value for the reduce. Then the result set is filtered by the lookup of the indices, and if found, the value remains, otherwise the value is skipped. This continues until the object has no more property.
var filter = {
x: [1, 3, 5, 6],
y: [1, 4, 7, 8],
z: [1, 9]
};
var filtered = Object.keys(filter).reduce(function (r, a, i) {
return i ? r.filter(function (b) {
return ~filter[a].indexOf(b);
}) : filter[a];
}, []);
document.write('<pre>' + JSON.stringify(filtered, 0, 4) + '</pre>');
Bonus with objects, returns the common key/s:
var filter = {
x: { a: 'ah', c: 'ce', e: 'eh', f: 'ef' },
y: { a: 'ah', d: 'de', g: 'ge', h: 'ha' },
z: { a: 'ah', i: 'ie' }
};
var filtered = Object.keys(filter).reduce(function (r, a, i) {
return i ? r.filter(function (b) {
return b in filter[a];
}) : Object.keys(filter[a]);
}, []);
document.write('<pre>' + JSON.stringify(filtered, 0, 4) + '</pre>');

So, I would do this in 2 parts:
1 Find the common items
2 Extract the common items
var filter = {
x : [1,3,5,6],
y : [1,4,7,8],
z : [1,9]
}
// Find the common
var common;
Object.keys(filter).forEach(function (k) {
if (common) {
for (var i=0; i<common.length; i++) {
// Check if the common value exists on the key
if (filter[k].indexOf(common[i]) === -1) {
// If it does not, it is not common
common.splice(i, 1);
i--;
}
}
} else {
// On the first item, we assume all are common
common = filter[k]
}
})
// Additional processing can take place to extract the data you need here
//Should print "[1]"
console.log(common)

This is a solution: Here I used the same structure of your data, without converting it to arrays
HTML
<p id="result"></p>
JavaScript
var value = 'value',
filter = {
x : {1:value,3:value,5:value,6:value,9:value},
y : {1:value,4:value,7:value,8:value,9:value},
z : {1:value,9:value}
};
function getCommon(data) {
var subObjects = [],
common = [];
if(typeof data === 'object'){
// collect all sub-keys into an array
Object.keys(data).forEach(function(key){
if(typeof data[key] === 'object') {
subObjects = subObjects.concat(Object.keys(data[key]));
}
});
// get the common keys
Object.keys(data[Object.keys(data)[0]]).forEach(function(subKey){
if(getCount(subObjects, subKey) === Object.keys(data).length) {
common.push(subKey);
}
});
function getCount(data, target){
return data.filter(function(item){
return item === target;
}).length;
}
return common;
}
}
document.getElementById('result').innerHTML = getCommon(filter);
JSFiddle: http://jsfiddle.net/LeoAref/bbnbfck7/

Related

Trying to write a inverse version of Javascript's filter method that will work on both arrays and objects

So as the question states, I'm attempting to write a function for which the input can be either an Array or an Object (as well as a callback to test them against).
The desired return value is an Array / Object of the elements (value/key pairs in the case of the Object) that passed the test - but the original Array / Object should be updated to remove those elements.
For example - if you passed in an "isEven" callback function that determines if numbers are even, then the expected results would be:
let a = [1, 2, 3, 4]
reject(a)
//Output:
[2, 4]
console.log(a);
//expected output
[1, 3]
So far I've been trying to write an conditional scenario based on Array.isArray(input), to use one set of code for handling arrays, another set for handling objects. However, it's been not working properly for objects, and I'm curious if there'd be one way to write it that would work for both cases? If not what might be the best approach here?
My rough attempt to this point, if the code is helpful:
function reject(collection, callback) {
let newArr;
if (!collection.constructor == Array) {
newArr = {};
for (let [key, value] of Object.entries(collection)) {
if (!callback(value)) {
newArr[key] = value;
}
}
} else {
newArr = [];
for (let el of collection) {
if (!callback(el)){
newArr.push(el);
}
}
}
return newArr;
}
You can use Array.isArray to check if an object is an array. To remove elements from the array, you can iterate backwards and use Array#splice.
function reject(o, f) {
if (Array.isArray(o)) {
let res = [];
for (let i = o.length - 1; i >= 0; i--)
if (f(o[i])) res.push(o.splice(i, 1)[0]);
return res.reverse();
}
let res = {};
for (const [k, v] of Object.entries(o))
if (f(v)) {
res[k] = v;
delete o[k];
}
return res;
}
let a = [1, 2, 3, 4];
console.log(reject(a, x => x % 2 === 0), a);
let obj = {a : 1, b : 2, c : 3, d : 4};
console.log(reject(obj, x => x > 2), obj);

Grouping items with more that one indexes into sub arrays

Trying to create a function that groups repeated items in an array into sub arrays, and also grouping strings (should there be any) into another subarray.
I tried using the findIndex method to define i and then iterate it and push in into an [], using reduce
let roughArray = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20];
function sortArray() {
roughArray.map(num => {
if (num[i] > 1) {
roughArray.reduce((acc, num) => {
return acc.concat(num)
}, [])
}
})
sortArray()
I also tried:
const cleanArray = roughArray.reduce((acc, num) => {
let i = acc.findIndex(num);
if (i) {
return acc.concat(num);
}
}, [])
cleanArray();
I expect this in case of only numbers
[[1,1,1,1],[2,2,2], 4,5,10,[20,20], 391, 392,591]
And this in case of some included strings:
[[1,2], ["2", "3"]]
let roughArray = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20];
let results = {}
roughArray.map( num => {
if(results[num])
results[num].push(num)
else
results[num] = [num]
})
let bigArray = []
bigArray = Object.values(results)
let final_result = []
bigArray.map(array => {
if(array.length == 1)
final_result.push(array[0])
else
final_result.push(array)
})
console.log(final_result)
You could declare some callbacks for the various types of grouping and get the wanted type by checking the array and take an object for the grouped values.
function sortOf(array) {
const
mixed = (r, v) => {
var key = typeof v;
r[key] = r[key] || [];
r[key].push(v);
return r;
},
numbers = (r, v) => {
if (v in r) r[v] = [].concat(r[v], v);
else r[v] = v;
return r;
};
return Object.values(array.reduce(array.some(v => typeof v === 'string')
? mixed
: numbers,
Object.create(null)
));
}
console.log(sortOf([1, '1', '2', 3]));
console.log(sortOf([5, 2, 3, 3, 4, 5, 5, 1]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
First separate the strings from the rest. Sort the numbers, group them, then add the strings back in at the end.
If you want to, you can then map all the single item arrays into just single items, but that seems like it would make the output confusing.
let start = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20, '2', '3'];
let strings = start.filter(v => typeof(v) === 'string');
let notStrings = start.filter(v => typeof(v) !== 'string');
let sortedNotStrings = notStrings.sort((a,b) => a > b);
let grouped = sortedNotStrings.reduce((acc, value) =>
{
if(acc.length > 0)
{
if(acc[0][0] === value)
{
acc[0].push(value);
}
else
{
acc.unshift([value]);
}
}
else
{
acc.unshift([value]);
}
return acc;
}, []);
let sortedGrouped = grouped.sort((g1, g2) => g1[0] > g2[0]);
let withStrings = [sortedGrouped, strings];
console.log(withStrings);
let lonelySingleItems = sortedGrouped.map(arr => arr.length > 1 ? arr : arr[0]);
console.log([lonelySingleItems, strings]);
Regarding the if statement:
if(acc.length > 0)
{
if(acc[0][0] === value)
{
acc[0].push(value);
}
else
{
acc.unshift([value]);
}
}
else
{
acc.unshift([value]);
}
What I'm doing with the reduce function is passing in a default value [], so if we're at the start (i.e. the result is empty) then we put the first item in the sortedNotStrings array into the accumulating result (acc). This is what is happening in the outermost else.
If this isn't the beginning (i.e. acc is not empty) then we need to check if the value is the same as the last value added. If it is the same, put it into the array, otherwise start a new array in acc.
acc is an array of arrays, which is why [value] is being unshifted to start, rather than value.
In order to not have to access the last array of acc, I'm using unshift to put things on the front of the array. This is just to make the code look cleaner, by not using of acc[acc.length-1]. On the other hand you can do acc[acc.length-1].push([value]), and that means the grouped.sort is unnecessary, because the values won't be back to front.
If you have a really large array, eliminating the second sort is probably preferable to not having to type acc.length - 1.
Here I use an object literal {} as the accumulator for Array.prototype.reduce, in case of strings in the array I used str as the key of the object {} accumulator and added the strings as the value. So if a string is encountered the accumulator will be {str: "23"}.
In case of numbers I checked if the value is repeated or not, if repeated I created an array and added the new duplicate number to it with the key being the number itself e.g. {1: [1,1]}
Finally when the accumulator object is constructed I just take the values part of the accumulator object using Object.values which I return:
let roughArray = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20, "23", "23", "34"];
function group(roughArray) {
return Object.values(roughArray.reduce((r, e) => {
if(r['str'] && typeof e === "string"){ r['str'] = Array.isArray(r['str']) ? [...r['str']].concat(e): [r['str'], e]; }
else if(typeof e === "string"){r['str'] = [e]}
else if(r[e]){r[e] = Array.isArray(r[e]) ? [...r[e]].concat(e): [r[e], e];}
else{ r[e] = e}
return r;
}, {}));
}
console.log(group(roughArray));
Note: Array.isArray(r['str']) checks whether the value of the str key is an array if so I can use the es6 spread operator ... to get the old values of the array and also append the new one to the existing array.

Custom implementation of comparing two non ordered array of object in Javascript

I want to compare two array of objects, they should be considered equal if their elements are same but not in same order. e.g. [{a:1}, {b:2}] and [{b:2}, {a:1}]
I am using lodash-v3's isEqual which can compare two values but it only gives true if the order in array is same, so I've implement one function that compares elements recursively.
function deepEqual(data1, data2) {
data1 = _.cloneDeep(data1);
data2 = _.cloneDeep(data2);
function isDeepEqual(val1, val2) {
if (_.isArray(val1) && _.isArray(val2)) {
if (val1.length === val2.length) {
for (let id1 in val1) {
let hasMatch = false;
let matchOnIndex = -1;
for (let id2 in val2) {
if (isDeepEqual(val1[id1], val2[id2])) {
hasMatch = true;
matchOnIndex = id2;
break;
}
}
if (hasMatch) {
val2.splice(matchOnIndex, 1);
} else {
return false;
}
}
return true;
} else {
return false;
}
}
if (_.isPlainObject(val1) && _.isPlainObject(val2)) {
if (Object.keys(val1).length === Object.keys(val2).length) {
for (let temp1 in val1) {
if (!isDeepEqual(val1[temp1], val2[temp1])) {
return false;
}
}
return true;
} else {
return false;
}
}
return _.isEqual(val1, val2);
}
return isDeepEqual(data1, data2);
}
Above function works, but how can i improve it performance wise?
If there is any simple implementation with lodash3 that works for me as well.
Link to above function's fiddle.
EDIT:
The two array of objects can be nested,
e.g.
[{
a:1,
b:[{
c: [1, 2]
},
{
d: [3, 4]
}]
},{
e:1,
f:[{
g: [5, 6]
},
{
h: [7, 8]
}]
}]
and
[{
e:1,
f:[{
h: [8, 7]
},{
g: [6, 5]
}]
},{
a:1,
b:[{
d: [4, 3]
},{
c: [2, 1]
}]
}]
Arrays can also not have unique values(as users are creating this arrays).
This might be possible with _.isEqualWith as #Koushik and #tokland suggested. Unfortunately it's available from lodashv4 so I can't use it.
Similar solution is also mentioned in this comment.
Really sorry for not clearly specifying the examples. The fiddle has all different type of cases.
i think this is what you want
var first = [{ a: 1 }, { b: 2 }];
var second = [{ b: 2 }, { a: 1 }];
function comparer(otherArray){
return function(current){
return otherArray.filter(function(other){
return other.a == current.a && other.b == current.b
}).length == 0;
}
}
var onlyInA = first.filter(comparer(second));
var onlyInB = second.filter(comparer(first));
result = (onlyInA.concat(onlyInB)).length===0;
console.log(result);
how about simply check each Object of one array presents in another array and having same length (both array)?
let isEqualArray = (arr1, arr2) => (
arr1 === arr2 ||
arr1.length === arr2.length &&
!arr1.some(a=> !arr2.find(b=>_.isEqual(a,b)))
)
let a1 = [{a:1}, {b:2}],
a2 = [{b:2}, {a:1}];
let isEqualArray = (arr1, arr2) => (
arr1 === arr2 ||
arr1.length === arr2.length &&
!arr1.some(a=> !arr2.find(b=>_.isEqual(a,b)))
)
console.log(isEqualArray(a1,a2));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Usage of _.isEqual or _.isEqualWith with a customizer depending on how you want to compare two object!
Note: this will not work 100% when you have duplicate items in your array, but it will atleast confirm that all the items of one array is present on another array. You can do a reverse check, or apply unique to perform this compare for duplicate arrays.
Function _.isEqualWith should be the starting point. Now, there are many ways you can implement it. You could make it pretty efficient by defining an ordering for the items in the arrays. An example using id as a key:
function isEqualConsideringArraysAsSets(obj1, obj2, key) {
const customizer = (objValue, othValue) => {
if (_(objValue).isArray() && _(othValue).isArray()) {
return _.size(objValue) === _.size(othValue) &&
_.zip(_.sortBy(objValue, key), _.sortBy(othValue, key))
.every(([val1, val2]) => isEqualConsideringArraysAsSets(val1, val2));
}
};
return _.isEqualWith(obj1, obj2, customizer);
}
console.log(isEqualConsideringArraysAsSets([{id: 1}, {id: 2}], [{id: 2}, {id: 1}], "id"))

Javascript: Find a key with maximum value in a object after filtering keys based on an array

My js object:
data_obj = {'p1': 1, 'p2':2, 'p3':3}
my array
data_array = ['p1', 'p3']
Now, I want to filter the object based on the array. Expected result is
fil_obj = {'p1': 1, 'p3':3}
Now then, find the key having a maximum value. Expected result is
p3
Since I have object with thousands of items, I expect a very efficient solution.
Since I'm using d3js for this project, solution based on d3js like d3.max would be great.
You could iterate the wanted properties and return the max key.
var data_obj = { p1: 1, p2: 2, p3: 3},
data_array = ['p1', 'p3'],
result = data_array.reduce(function (r, a, i) {
return !i || data_obj[r] < data_obj[a] ? a : r;
}, undefined);
console.log(result);
I've never used d3, but it seems to me you can get the result pretty efficiently with a single call to .reduce():
var data_obj = {'p1': 1, 'p2':2, 'p3':3};
var data_array = ['p1', 'p3'];
var results = data_array.reduce((r,v)=>{
if (v in data_obj) {
r.data[v] = data_obj[v];
if (data_obj[v] > r.maxVal) {
r.maxKey = v;
r.maxVal = data_obj[v];
}
}
return r;
}, {data:{}, maxKey:null, maxVal:Number.NEGATIVE_INFINITY});
console.log(results);

How to create a partition function in javascript. using the following guidelines

I've been trying to create a generic partition function that returns an array of arrays. the function should be made under the following guidelines:
Arguments:
An array
A function
Objectives:
Call <function> for each element in <array> passing it the arguments:
element, key, <array>
Return an array that is made up of 2 sub arrays:
0. An array that contains all the values for which <function> returned something truthy
1. An array that contains all the values for which <function> returned something falsy
Here is what I have so far. I get the return of two. I feel like maybe I just have to do the filter function on two separate occasions, but I'm not sure how to put it together. Thoughts and suggestions are highly appreciated.
_.partition = function (collection, test){
var allValues = [];
var matches = [];
var misMatches = [];
_.filter(collection.value, function(value, key, collection){
if (test(value[key], key, collection) === "string"){
matches.push(value[key]);
}else{
misMatches.push(value[key]);
}
});
return allValues.push(matches, misMatches);
}
Here is a version which uses reduce:
function partition(arr, filter) {
return arr.reduce(
(r, e, i, a) => {
r[filter(e, i, a) ? 0 : 1].push(e);
return r;
}, [[], []]);
}
Here's an alternative version which uses Array#filter to find the matches, and builds an array of non-matches as it goes along:
function partition(arr, filter) {
var fail = [];
var pass = arr.filter((e, i, a) => {
if (filter(e, i, a)) return true;
fail.push(e);
});
return [pass, fail];
}
You're correct about calling the filter method on separate occasions. One filter call would obtain the truthy values; the other would obtain the falsy values:
_.partition = function(collection, testFunc) {
var matches = collection.filter(function(elem) {
return test(elem) === 'string';
});
var misMatches = collection.filter(function(elem) {
return test(elem) !== 'string';
});
return [matches, misMatches];
}
You are close, but there are a couple issues I see:
You are returning the result of allValues.push which is not allValues itself, but rather the new length of the array.
You are using _.filter to iterate over array elements and sort them into two arrays. This is strange, since it's not the intended use of _.filter.
If you want a quick and readable solution using _.filter, this will work:
_.mixin({
partition: function(collection, test) {
return [
_.filter(collection, test), // items which satisfy condition
_.filter(collection, _.negate(test)) // items which don't
];
}
});
A more efficient solution which makes only one pass over the collection is below (this is almost what you already have):
_.mixin({
partition: function(collection, test) {
var matches = [], misMatches = [], value;
// can replace this loop with _.each
for (var i = 0, len = collection.length; i < len; ++i) {
value = collection[i];
// push the value into the appropriate array
if (test(value, i, collection)) {
matches.push(value);
} else {
misMatches.push(value);
}
}
return [matches, misMatches];
}
});
Usage examples (and Plunker):
function isOdd(x) {
return x % 2;
}
// _.mixin allows you to do either one of these
_.partition([1, 2, 3, 4, 5, 6], isOdd); // result: [[1, 3, 5], [2, 4, 6]]
_([1, 2, 3, 4, 5, 6]).partition(isOdd); // result: [[1, 3, 5], [2, 4, 6]]
// this is a use case you brought up in the comments
_.partition([1, "a", 2, "b", 3, "c"], _.isString); // result: [["a", "b", "c"], [1, 2, 3]]
This is generally known as partition ing in functional languages. You suply an array (xs) and a predicate function (p) to a reduceing function with initial value [[],[]].
var partition = (xs,p) => xs.reduce( (r,e) => ( p(e) ? r[0].push(e)
: r[1].push(e)
, r
)
, [[],[]]
);
Such that;
> partition([1,2,3,4,5,6,7,8,9,0], x => x < 5)
> [[1, 2, 3, 4, 0],[5, 6, 7, 8, 9]]

Categories

Resources