finding the difference in multiple arrays. using reduce - javascript

I am trying to recreate the underscore js function difference using reduce.
Difference takes in multiple arrays and returns all values that are similiar to the first array. so [1,2],[2,3],[1,3] should spit out [1,2,2,1].
I was thinking of looping through each of my subarray(rest) and if the value of my sub elements has an index in my first array then I will push that element onto my accumulator value(which is an empty array).
Some reason I do not get my intended output where I was expecting [1,2,3,2,1,2].
instead I am getting [ [ 1, 2 ], [ 2, 4 ], [ 1, 2 ] ]. Can anyone help me with this rewrite using reduce. Thanks.
function difference(arrays){
var arrayed=Array.prototype.slice.call(arguments);
var first=arrayed[0];
var rest=arrayed.slice(1);
return reduce(first,function(acc,cur){
forEach(rest,function(sub){
if (sub.indexOf(cur)>-1){
acc.push(sub);
}});
return acc;
},[]);
}
console.log(difference([1,2,3],[2,4],[1,2],[4,5]));
i know the way im calling forEach is different but it is because my own version of forEach accepts two arguments.

As for your previous question, you need to call array methods on arrays, not pass them as parameters so not
return reduce(first,function(acc,cur){...})
but
return first.reduce(function(acc,cur){...});
In your function:
var first=arrayed[0];
var rest=arrayed.slice(1);
can be replaced with one step using splice:
var first = arrayed.splice(0, 1)
and use arrayed instead of rest. But there's no need for that anyway. If no accumulator is provided, then the first value is used, so (with some renaming of variables):
function difference(){
var args = Array.prototype.slice.call(arguments);
return args.reduce(function(acc, cur){
cur.forEach(function(value){
if (acc.includes(value)){
acc.push(value);
}
});
return acc;
});
}
console.log(difference([1,2,3],[2,4],[1,2],[4,5]));

Could try this
function difference(arrays){
var arrayed=Array.prototype.slice.call(arguments);
var first=arrayed[0];
var rest=arrayed.slice(1);
return first.concat(rest.reduce(function(acc, cur, curIndex, array){
first.forEach(function(eachEle){
if(cur.indexOf(eachEle) > -1 )
acc.push(cur[cur.indexOf(eachEle)]);
});
return acc;
}, []));
}
console.log(difference([1,2,3],[2,4],[1,2],[4,5]));

Here's a solution :
function difference(arrays){
var arrayed = Array.prototype.slice.call(arguments);
var first = arrayed.splice(0,1)[0]; // edit, thanks robG
var tmp = [];
return arrayed.reduce(function(acc, arr) {
return acc.concat(arr.filter(function(el) {
return first.includes(el);
}));
}, first);
}
console.log(difference([1,2,3],[2,4],[1,2],[4,5]));
Use the javascript reduce method on the 'rest' array, makes the first array be the default value of the accumulator, filter the arrays with the first one and concat the result to your accumulator.
Hope it helps,
best regards

Related

Javascript - Workaround for flatmap

So I was looking for some workaround for flat map as it doesn't work on IE and I find this one:
But I don't really understand why does it work
var gadjets = [
{computers:['asus', 'hp'],
sellphones:['Galaxy', 'Nokia']
},
{computers:['dell', 'insys'],
sellphones:['iphone', 'samsung']
}
];
const getValues = gadjets.reduce((acc, gadjet) => acc.concat(gadjet[computers]), []) // instead of gadjets.flatMap(gadjet=> gadjet[computers])
This code returns:
['asus','hp','dell','insys']
But shouldn't it return:
['asus','hp'],['dell', 'insys']
This is because reduce adds up the elements you give it. For example, take the following code:
let arr = [1,2,3,4,5];
console.log(arr.reduce((before, value)=>before+value));
This code takes each value and adds it to before. It then passes that added value into the next iteration of reduce, in the before variable.
In your code, you were passing an array into before, or in your case acc, and concatenates (merge) a new array from gadgets['computers'] and returns that array. This creates a list of the computers from the array of objects.
More info on reduce here.
But shouldn't it return
I'm not sure what you're trying to show us there, but if you mean
[['asus','hp'],['dell', 'insys']]
then no, it shouldn't. concat flattens arrays you pass it (to a single level):
const a = [].concat(['asus','hp'], ['dell', 'insys']);
console.log(a); // ["asus", "hp", "dell", "insys"]
So acc.concat(gadjet[computers]) flattens out each of those computers arrays into a new array, which is the accumulation result of the reduce.
In case you want the output to be array of arrays. Try this:
var gadjets = [
{ computers: ["asus", "hp"], sellphones: ["Galaxy", "Nokia"] },
{ computers: ["dell", "insys"], sellphones: ["iphone", "samsung"] }
];
const groupBy = key => {
let res = gadjets.reduce((objectsByKeyValue, obj) => {
let arr = [];
arr.push(obj[key]);
return objectsByKeyValue.concat(arr);
}, []);
return res;
};
console.log(groupBy("computers"));

How to achieve map functionality with the use of filter in JavaScript, as we can use reduce as map and filter?

Suppose I have an array var arr = [1,2,3] and if I do var result = arr.filter(callback) I want value of result would be [2,4,6] by the use of filter. I want to only define callback function in order to do so. It can be easily done with map but I want to use only filter.
Array.prototype.filter only filters existing values in an array, effectively creating a new array that can hold the same values, their subset, or an empty array, based on the filtering function.
You could do it using filter():
const arr = [1, 2, 3];
arr.filter((c, i) => {
arr[i] = +arr[i] * 2;
return true;
});
console.log(arr)
we are always returning true, so filter() makes no sense in this case.
As stated many times, there is no reason why you should do it.
It is impossible to do it like map because map returns a new array. You can either alter on the original array or you have to make a clone of the original array so you do not change it.
// this modifies the orginal array
var arr1 = [1,2,3]
arr1.filter((v,index,arr)=>{
arr[index] = v * 2;
return true
})
console.log(arr1)
// or you can clone it and do the same thing
// this modifies the cloned array
var arr2 = [1,2,3]
var arr3 = arr2.slice()
arr3.filter((v,index,arr)=>{
arr[index] = v * 2;
return true
})
console.log(arr2, arr3)
So no, you can not recreate map with filter since you HAVE to modify the original array or cheat and use a copy of the array.
So I'm not sure I understand the second part of your question, but as for the first part:
The callback for filter has three arguments, two of which are optional.
The first argument is the current element in the traversal, and the second and third arguments (the optional ones) are the 0-based index of the current element, and a reference to the original array.
This third parameter is useful for what you're trying to do.
let myArr = [1, 2, 3];
myArr.filter((el, ind, orig) => {
orig[ind] = orig[ind] + 1; // Or, whatever map logic you want to use
return true; // since a Boolean condition must be returned here
});
This way you can do it without even even having to break the scope of the function!
If you want to do it without necessarily having a variable to originally call filter on (you do have to pass an array), you can use the prototype and the call method:
Array.prototype.filter.call([1, 2, 3], (el, ind, orig) => {
orig[ind] = orig[ind] + 1;
return true;
});

Using the reduce method to eliminate any duplicated numbers

I am trying to use the reduce method as follows to eliminate duplicates however, it is not quite working:
var unique = function(array) {
array = array.sort(function(a,b) {return a-b;});
var noDup = [array[0]];
array.reduce(function(c,d) {
if(c!==d) {
noDup.push(d);
return d;
}
});
return noDup;
};
var x = [9,2,1,5,9,1,1,4,2,9];//==>[1, 1, 2, 4, 5, 9, 9]
function unique(values) {
return values.reduce(function(prev, cur) {
if (prev.indexOf(cur) == -1) {
prev.push(cur);
}
return prev;
}, []);
}
unique([9,2,1,5,9,1,1,4,2,9]) // --> [9, 2, 1, 5, 4]
fiddle
You are using the "intermediate value" of reduce to hold the previous value, so you can check against it the next time through. But that leaves you with no way to calculate the real intermediate value you want, which is the unique array you are building, so you having to declare it outside (noDup), which sort of defeats the whole purpose. Then your code has issues such as not providing an initial value to reduce. In that case, reduce has a special behavior which is that it calls the callback with the first two values of the array; a situation you are not handling properly.
Anyway, since it seems you are willing to sort the array, you can avoid doing an indexOf each time through the loop, by just remembering the previous value and checking against it:
function unique(values) {
var prev;
return values . sort() . reduce(function(result, cur) {
if (cur !== prev) result.push(cur);
prev = cur;
return result;
}, []);
}
But it turns out actually we don't need to keep the value of prev; instead, we can simply refer to the previous element directly, since filter passes additional arguments of index and array to the callback, so:
function unique(values) {
return values . sort() . reduce(function(result, cur, index, array) {
if (cur !== array[index-1]) result.push(cur);
return result;
}, []);
}
But if you think about it, this is nothing more than a filter written using reduce. It's just filtering out numbers that are the same as the previous one. So just write it as a filter to start with:
function unique(values) {
return values . sort() . filter(value, i, arr) { return value !== arr[i-1]; });
}
There are other approaches to removing duplicates using filter which don't require the sort. Here's a simple one:
values . filter(function(value, i, arr) { return arr.indexOf(value) === i; });
What this says is, filter out a number if the location where it is first found in the array is its location. In other words, filter out numbers that occur earlier in the array.

Get object properties and values from array using lodash/underscore.js

Fiddle Example
I have an array like this:
var array = [ {
'data-price': '0.00',
'data-term': '532',
'data-model_id': '409',
},
{
'data-price': '0.00',
'data-term': '483',
'data-model_id': '384',
},
{ text: 'dffdfddgfdgf' } ];
I want to filter out the last object and extract [{data-model_id:409},{data-model_id:384}] from the first two objects. I have tried this code:
var k = _(array).filter('data-model_id').pluck('data-model_id').value();
console.log(k);
and it returns an array of the values only, ["409", "384"] . Is there a function to return the whole objects in lodash or underscore?
Using plain JS to show the logic: you need to filter out the elements that don't have the key, then map the new collection to another form:
array.filter( function(item){
return 'data-model_id' in item;
}).map( function( item ){
return { 'data-model_id' : item['data-model_id'] }
});
http://jsfiddle.net/dn4tn6xv/7/
What if I told you this is possible using just native javascript? Just use Array.filter and Object.keys, using the former to filter and the latter to get the keys and then returning a Boolean by comparing the index of the Array returned by Object.keys
var k = array.filter(function(obj){
return Object.keys(obj).indexOf("data-model_id") > -1;
});
In lodash you can do like this:
get full object
console.log(_.filter(array, 'data-model_id'));
get only data-model_id property
var res = _.chain(array).filter('data-model_id').map(function (el) {
return _.pick(el, 'data-model_id');
}).value();
Example

Get first element of a sparse JavaScript array

I have an array of objects in javascript. I use jquery.
How do i get the first element in the array? I cant use the array index - as I assign each elements index when I am adding the objects to the array. So the indexes arent 0, 1, 2 etc.
Just need to get the first element of the array?
If you don't use sequentially numbered elements, you'll have to loop through until you hit the first one:
var firstIndex = 0;
while (firstIndex < myarray.length && myarray[firstIndex] === undefined) {
firstIndex++;
}
if (firstIndex < myarray.length) {
var firstElement = myarray[firstIndex];
} else {
// no elements.
}
or some equivalently silly construction. This gets you the first item's index, which you might or might not care about it.
If this is something you need to do often, you should keep a lookaside reference to the current first valid index, so this becomes an O(1) operation instead of O(n) every time. If you're frequently needing to iterate through a truly sparse array, consider another data structure, like keeping an object alongside it that back-maps ordinal results to indexes, or something that fits your data.
The filter method works with sparse arrays.
var first = array.filter(x => true)[0];
Have you considered:
function getFirstIndex(array){
var result;
if(array instanceof Array){
for(var i in array){
result = i;
break;
}
} else {
return null;
}
return result;
}
?
And as a way to get the last element in the array:
function getLastIndex(array){
var result;
if(array instanceof Array){
result = array.push("");
array.pop;
}
} else {
return null;
}
return result;
}
Neither of these uses jquery.
Object.keys(array)[0] returns the index (in String form) of the first element in the sparse array.
var array = [];
array[2] = true;
array[5] = undefined;
var keys = Object.keys(array); // => ["2", "5"]
var first = Number(keys[0]); // => 2
var last = Number(keys[keys.length - 1]); // => 5
I was also facing a similar problem and was surprised that no one has considered the following:
var testArray = [];
testArray [1245]= 31;
testArray[2045] = 45;
for(index in testArray){
console.log(index+','+testArray[index])
}
The above will produce
1245,31
2045,45
If needed you could exist after the first iteration if all that was required but generally we need to know where in the array to begin.
This is a proposal with ES5 method with Array#some.
The code gets the first nonsparse element and the index. The iteration stops immediately with returning true in the callback:
var a = [, , 22, 33],
value,
index;
a.some(function (v, i) {
value = v;
index = i;
return true;
});
console.log(index, value);
If you find yourself needing to do manipulation of arrays a lot, you might be interested in the Underscore library. It provides utility methods for manipulating arrays, for example compact:
var yourArray = [];
yourArray[10] = "foo";
var firstValue = _.compact(yourArray)[0];
However, it does sound like you are doing something strange when you are constructing your array. Perhaps Array.push would help you out?

Categories