What is the difference between these two bits of code? (javascript) - javascript

I'm trying to figure out what the difference between the following two snippets of code. They both flatten an array of subarrays and both output the same thing.
Array.prototype.concatAll = function() {
var results = [];
this.forEach(function(subArray) {
subArray.forEach(function(element) {
results.push(element);
});
});
return results;
}; // [ [1,2,3], [4,5,6], [7,8,9] ] -> [1, 2, 3, 4, 5, 6, 7, 8, 9]
and
Array.prototype.concatAll = function() {
var results = [];
this.forEach(function(subArray) {
results.push.apply(results, subArray);
});
return results;
}; // [ [1,2,3], [4,5,6], [7,8,9] ] -> [1, 2, 3, 4, 5, 6, 7, 8, 9]
How does apply work? Why does it results have to be written twice?

apply is a method of a function, allowing to pass explicit this argument (which may differ from the object the function is a member of) and array of arguments. In your example apply is used for its ability to accept array of arguments, as a substitute for spread operator.

Related

Javascript Get the common elements of three arrays

I am trying to filter the common elements of 3 arrays. But instead of getting the common elements of 3 arrays, it only reads the 2 arrays and not the 3rd array. Here is my code, thank you:
function commonElementsOfArray(arr1, arr2, arr3) {
return arr1.filter(function (n) {
return arr2.indexOf(n) !== -1;
return arr3.indexOf(n) !== -1;
});
}
As mentioned by #Titus, the issue in your code is the double return statements - once the first return is found the filter function will exit.
However, there is also an issue worth pointing out in your approach to finding common elements regarding Array.indexOf. The problem is that Array.indexOf is an O(n) operation, meaning the parameter will be checked against every element of arr2 and every element of arr3. On face value that sounds like the right approach, but if the arrays are large then this will be a very slow function. For instance, if each array has 1,000 entries (n) then your function will take each element and compare against everything in arr2 and arr3, resulting in thousands of operations per element (O(n^2) time complexity).
One alternative is to create a Map and populate it as you iterate through each array to track the number of times an entry has been seen. Finding values now has O(1) runtime. There is still the cost of iterating through each array which yields O(n) but because of the fast lookup this becomes n * 1 operations or O(n) time complexity.
function commonElementsOfArray(...arrays) {
const size = arrays.length;
const map = new Map();
arrays.forEach(arr => {
arr.forEach(entry => {
if (!map.has(entry)) {
map.set(entry, 1);
} else {
let timesSeen = map.get(entry);
map.set(entry, ++timesSeen);
}
});
});
const commonElements = [];
map.forEach((count, key) => {
if (count === size) {
commonElements.push(key);
}
});
return commonElements;
}
console.log(commonElementsOfArray([1, 2, 3], [1, 2, 4], [2, 4, 5]));
How about refactoring the OP's code into a generic intersection functionality which implements a simplified intersection function of two arrays and generates the overall intersection of more than 2 arrays by a reduce task which processes the generic function's (array type) arguments?
Thus the intersection of two arrays would be based on the OP's code filter approach but using includes instead of indexOf. Something like ...
function getIntersectionOfTwo(a, b) {
return a.filter(function (n) {
return b.includes(n);
});
}
A generic getIntersection then just needs to assure the type safety of its arguments and the correct return value for too less arguments as well as the intersection result for the minimum amount of correctly provided arguments ...
function getIntersection(...listOfArrays) {
function getIntersectionOfTwo(a, b) {
return a.filter(function (n) {
return b.includes(n);
});
}
// assure only array type arguments.
listOfArrays = listOfArrays.filter(Array.isArray);
return (listOfArrays[1] ?? listOfArrays[0])
&& listOfArrays.reduce(getIntersectionOfTwo);
}
console.log(
'getIntersection() ...',
getIntersection()
);
console.log(
'getIntersection(9, "foo", 0) ...',
getIntersection(9, "foo", 0)
);
console.log(
'getIntersection([2, 7, 0], "bar") ...',
getIntersection([2, 7, 0], "bar")
);
console.log(
'getIntersection([2, 7, 0, 4], [6, 2, 7, 3]) ...',
getIntersection([2, 7, 0, 4], [6, 2, 7, 3])
);
console.log(
'getIntersection([2, 7, 0, 4], [6, 2, 7, 3], [9, 1, 2]) ...',
getIntersection([2, 7, 0, 4], [6, 2, 7, 3], [9, 1, 2])
);
console.log(
'getIntersection([2, 7, 0, 4], [6, 2, 7, 3], [9]) ...',
getIntersection([2, 7, 0, 4], [6, 2, 7, 3], [9])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The above example's implementation of getIntersectionOfTwo of cause is kept simple for a better understanding of the overall task's refactoring process.
Within a next refactoring step this function could be improved too in order to handle/process vast array data more efficiently. Thus one would use within the filter callback a Map based lookup instead of searching within each filter iteration whether b.includes(n).
function getIntersection(...listOfArrays) {
function getIntersectionOfTwo(intersection, iterableItem) {
// in order to compare huge arrays more efficiently access ...
const [
comparisonBase, // ... the shorter one as comparison base
comparisonList, // ... and the longer one to filter from.
] = [intersection, iterableItem]
.sort((a, b) => a.length - b.length);
// create a `Map` based lookup table from the shorter array.
const itemLookup = comparisonBase
.reduce((map, item) => map.set(item, true), new Map)
// the intersection is the result of following filter task.
return comparisonList.filter(item => itemLookup.has(item));
}
// assure only array type arguments.
listOfArrays = listOfArrays.filter(Array.isArray);
return (listOfArrays[1] ?? listOfArrays[0])
&& listOfArrays.reduce(getIntersectionOfTwo);
}
console.log(
'getIntersection() ...',
getIntersection(),
);
console.log(
'getIntersection(9, "foo", 0) ...',
getIntersection(9, "foo", 0),
);
console.log(
'getIntersection([2, 7, 0], "bar") ...',
getIntersection([2, 7, 0], "bar"),
);
console.log(
'getIntersection([2, 7, 0, 4], [6, 2, 7, 3]) ...',
getIntersection([2, 7, 0, 4], [6, 2, 7, 3]),
);
console.log(
'getIntersection([2, 7, 0, 4], [6, 2, 7, 3], [9, 1, 2]) ...',
getIntersection([2, 7, 0, 4], [6, 2, 7, 3], [9, 1, 2]),
);
console.log(
'getIntersection([2, 7, 0, 4], [6, 2, 7, 3], [9]) ...',
getIntersection([2, 7, 0, 4], [6, 2, 7, 3], [9]),
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

How to build a Set of unique Arrays?

I want to add many arrays to a Javascript set, and ensure that only unique arrays are added to the set. However, when I try adding the same array multiple times, it is always added instead of rejected. The .has() method always returns false as well. How do I fix this?
const mySet = new Set();
mySet.add([1, 2, 3]);
mySet.add([4, 5]);
mySet.add([1, 2, 3]);
console.log(mySet);
// Gives: Set(3) { [ 1, 2, 3 ], [ 4, 5 ], [ 1, 2, 3 ] }
// I want: Set(2) { [ 1, 2, 3 ], [ 4, 5 ] }
console.log(mySet.has([1, 2, 3]));
// Gives: false
// I want: true
I'd use a Map instead, indexed by the stringified version of the array:
const map = new Map();
const addToMap = arr => map.set(JSON.stringify(arr), arr);
addToMap([1, 2, 3]);
addToMap([4, 5]);
addToMap([1, 2, 3]);
console.log([...map.values()]);

Can someone explain what is going on with `push` here?

It feels like push is behaving funny. Rather than just push to 1 index inside the forEach, it seems to be pushing to all 3 indexes. Am I missing something obvious?
let arrayToReduce = [ [ 1, 2, 3 ] ]
let reduced = arrayToReduce.reduce((arr, inner) => {
const copied = arr.slice()
inner.forEach((num, idx) => {
copied[idx].push(num)
})
return copied
}, Array(arrayToReduce[0].length).fill([]))
console.log(reduced)
Expected output: [[1], [2], [3]]
Actual output: [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
push isn't the culprit, it is fill.
You've created an array the same length as the original and then filled it with a value.
That value is an array.
The same array.
So when you push a value to copied[0] you get a reference to that array and put a value into it.
And when you push a value to copied[1] you get a reference to that same array and put another value into it.
let arr = [ [ 7, 3, 47 ] ]
let reduced = arr.flat().map(e=>[e])
console.log(reduced)
//output: [[7], [3], [47]]
if you want your Expected output : [[1], [2], [3]]
Simply return index instead of item in inner array
let arr = [ [ 7, 3, 47 ] ]
let reduced = arr.flat().map((e,i)=>[i+1])
console.log(reduced)

Using indexOf to get index of array in a collection of arrays (Javascript)

What's the best way to find the index of an array in a collection of arrays? Why doesn't indexOf() return the correct index? I'm guessing it's something to do with object equality?
I've seen other solutions loop through the collection and return the index reached when the equality check is met, but I'm still curious as to why indexOf() doesn't do the same thing. Additionally I can't use ES6's find / findIndex due to IE 11 support (as always). I've included my test code below. Many thanks.
var numbers = [ [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12] ];
function getIndex (numbersToTest) {
return numbers.indexOf(numbersToTest);
};
function test() {
console.log( getIndex( [1, 2, 3, 4, 5, 6] ) ); // Except 0
console.log( getIndex( [7, 8, 9, 10, 11, 12] ) ); // Expect 1
console.log( getIndex( [2, 1, 3, 4, 5, 6] ) ); // Expect -1 (not in same order)
}
test();
Object references (including array references) are compared as reference values; one object reference is equal to another only if both references are to the exact same object. Comparison is not performed based on the content of the arrays, in your case. Even though those arrays you pass in have the same values, they're distinct arrays, and so are not equal to any of the arrays in the original list.
Instead, you need to use something like Array#find (to find the entry) or Array#findIndex (to find the entry's index), passing in a callback that compares the array in numbers with numbersToTest to see if they're equivalent arrays. This question's answers talk about various ways to efficiently compare arrays for equivalence.
For example:
var numbers = [ [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12] ];
function getIndex (numbersToTest) {
return numbers.findIndex(function(entry) {
// Simple comparison that works for an array of numbers
return entry.length === numbersToTest.length && entry.every(function(number, index) {
return numbersToTest[index] === number;
});
});
};
function test() {
console.log( getIndex( [1, 2, 3, 4, 5, 6] ) ); // Expect 0
console.log( getIndex( [7, 8, 9, 10, 11, 12] ) ); // Expect 1
console.log( getIndex( [2, 1, 3, 4, 5, 6] ) ); // Expect -1 (not in same order)
}
test();
Note that both Array#find and Array#findIndex are newish (ES2015, aka "ES6"), but can be polyfilled for older JavaScript engines.

JavaScript: passing built-in objects' methods as callback functions

I've been working through Eloquent JavaScript's exercises and found out something I think is odd. I wrote a trivial array-flattening piece of code:
var arrays = [[1, 2, 3], [4, 5], [6]];
var out = arrays.reduce(function(acc, next){ return acc.concat(next); });
console.log(out);
// → [1, 2, 3, 4, 5, 6]
So far so good. But that didn't seem pretty to me, so I rewrote it as:
var arrays = [[1, 2, 3], [4, 5], [6]];
var my_concat = function(acc, next){ return acc.concat(next); }
var out = arrays.reduce(my_concat);
console.log(out);
// → [1, 2, 3, 4, 5, 6]
It was better, but do we really need to introduce a function, be it anonymous or named, to do such a basic thing? Array.prototype.concat.call's call signature is exactly what we need! Feeling smart, I rewrote the code again:
var arrays = [[1, 2, 3], [4, 5], [6]];
var out = arrays.reduce([].concat.call);
// → TypeError: arrays.reduce is not a function (line 2)
Well, that haven't turned out as I expected. The error message seemed cryptic to me.
I decided to investigate. This works:
var arrays = [[1, 2, 3], [4, 5], [6]];
var my_concat = function(acc, next){ return [].concat.call(acc,next); }
var out = arrays.reduce(my_concat);
console.log(out);
// → [1, 2, 3, 4, 5, 6]
And this also works:
var arrays = [[1, 2, 3], [4, 5], [6]];
arrays.my_concat = function(acc, next) { return [].concat.call(acc, next); }
var out = arrays.reduce(arrays.my_concat);
console.log(out);
// → [1, 2, 3, 4, 5, 6]
More tinkering in the console:
[].concat.call
// → call() { [native code] }
typeof [].concat.call
// → "function"
[].concat.call([1, 2, 3], [4, 5])
// → [1, 2, 3, 4, 5]
var cc = [].concat.call
cc
// → call() { [native code] }
typeof cc
// → "function"
cc([1, 2, 3], [4, 5])
// → Uncaught TypeError: cc is not a function(…)
And even this works:
Array.prototype.my_concat = function(acc, next) { return [].concat.call(acc, next); }
// → function (acc, next) { return [].concat.call(acc, next); }
[[1, 2, 3], [4, 5], [6]].reduce([].my_concat)
// → [1, 2, 3, 4, 5, 6]
[[1, 2, 3], [4, 5], [6]].reduce([].concat.call)
// → Uncaught TypeError: [[1,2,3],[4,5],[6]].reduce is not a function(…)
Is there something special about built-in functions like .call?
call is just a method that most functions inherit from Function.prototype. That is,
arrays.reduce.call === Function.prototype.call
The call method knows which function you want to call because that function is passed as the this value.
When you pass call as the callback, it will be called passing undefined as the this value. Since undefined is not a function, it throws. On Firefox I get this error:
TypeError: Function.prototype.call called on incompatible undefined
Instead, you could try one of these callbacks
Function.call.bind([].concat);
[].concat.bind([]);
However, the problem is that this won't work properly, because the callback is called with 4 arguments, not 2:
previousValue
currentValue
currentIndex
array
You want to get rid of the last two, so you need a custom function anyways.
However, these are not good approaches. Each time you call concat, it creates a new array. Therefore, if you want to flatten an array, you should call concat only once instead of per each item in the array:
[].concat.apply([], arrays); // this works

Categories