Comparing arrays of arrays with Lodash - javascript

I can't understand how to pull arrays from one array out of another.
I tried using plain JavaScript (ES6):
let openTiles = [[1, 1], [2, 2], [1, 3]]
let usedTiles = [[1, 1], [2, 2]]
openTiles = openTiles.filter((item) => !usedTiles.includes(item))
I expected the final openTiles to be: [[1, 3]] but it is unchanged. The problem is, the code above uses JavaScript's standard comparison (===) which can't compare one array with another. Lodash has the _.isEqual() function but I can't understand how to implement it.
I tried:
openTiles = openTiles.filter((item) => {
return usedTiles.every((el) => {
_.isEqual(item, el)
})
})
but that gives me an empty array. I would like to see how people incorporate Lodash's _.isEqual() function so that all the arrays in usedTiles can be removed from openTiles.

Rather than trying to write a general-purpose object- or array-equals function, or using the ones from lodash, Ramda, underscore, etc, you can write one specific to your type. If your tiles are just two-element arrays, then simply write an equals function that reflects that. Then you can use it in some:
const tilesEqual = (a) => (b) => a[0] == b[0] && a[1] == b[1]
const removeTiles = (open, used) => open.filter(
tile => !used.some(tilesEqual(tile))
)
let openTiles = [[1, 1], [2, 2], [1, 3]]
let usedTiles = [[1, 1], [2, 2]]
console.log(removeTiles(openTiles, usedTiles))
As to why your code above didn't work, there are two problems. You don't want to know if every used tile matches your current one. You only want to know is some of them do. Then because those are the ones you want to remove, you need to negate this for filter. So you want something like !usedTiles.some(...).
But there is another problem. You don't return anything in the callback to every/some:
return usedTiles.every((el) => {
_.isEqual(item, el)
})
You need to switch this to either
return usedTiles.every((el) => _.isEqual(item, el))
or
return usedTiles.every((el) => {
return _.isEqual(item, el)
})
It's an easy mistake to make, and it's quite common. If you're using an arrow function with a {-} delimited block, you need a return statement.

A simple method would be to stringify the arrays you want to compare against, so that .includes will work properly:
let openTiles = [[1, 1], [2, 2], [1, 3]]
let usedTiles = [[1, 1], [2, 2]]
const usedTilesStringified = usedTiles.map(JSON.stringify);
openTiles = openTiles.filter((item) => !usedTilesStringified.includes(JSON.stringify(item)))
console.log(openTiles);
Or you can compare every value explicitly:
let openTiles = [[1, 1], [2, 2], [1, 3]];
let usedTiles = [[1, 1], [2, 2]];
openTiles = openTiles.filter((item) => {
const { length } = usedTiles;
outer:
for (let i = 0, { length } = usedTiles; i < length; i++) {
const usedTile = usedTiles[i];
if (usedTile.length !== item.length) {
continue;
}
for (let j = 0, { length } = usedTile; j < length; j++) {
if (usedTile[j] !== item[j]) {
// The current subarrays being compared are not identical:
continue outer;
}
}
// We've iterated through all indicies while comparing one subarray to another
// they all match, so the arrays are the same, so this filter fails:
return false;
}
return true;
})
console.log(openTiles);
For a less generic solution that just checks whether index 0 is equal to index 1:
let openTiles = [[1, 1], [2, 2], [1, 3]];
let usedTiles = [[1, 1], [2, 2]];
openTiles = openTiles.filter((item) => {
for (let i = 0, { length } = usedTiles; i < length; i++) {
const usedTile = usedTiles[i];
if (usedTile[0] === item[0] && usedTile[1] === item[1]) {
return false;
}
}
return true;
})
console.log(openTiles);

To subtract one array from another lodash contains a series of difference methods.
Since the value in your case is an array, simple equality won't work because [] !== []. Lodash's _.isEqual() performs a deep comparison between two values.
To combine a difference subtraction with an _.isEqual() check use _.differenceWith().
let openTiles = [[1, 1], [2, 2], [1, 3]]
let usedTiles = [[1, 1], [2, 2]]
openTiles = _.differenceWith(openTiles, usedTiles, _.isEqual)
console.log(openTiles)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

Related

Using reduce method to get the multiplication of the last elements of each subarray

This is the array: [[1, 2], [1, 3], [1, 4]]
This is how it looks like with a regular for loop:
let multiplication = 1;
for (let i = 0; i < l.length; i++) {
multiplication *= l[i][1];
}
How do I do the same thing with reduce instead?
Initiate value at the second parameter of reduce, and with the callback function, remember to return the accumulated value
Below solution could help you
const arr = [
[1, 2],
[1, 3],
[1, 4],
];
const res = arr.reduce((acc, el) => acc * el[1], 1);
console.log(res);

Is there an efficient algorithm in JavaScript to find the number of distinct arrays within a larger array set?

Given the following array
let array = [[1, 2], [1, 2], [3, 4], [5, 6], [2, 1]]
I want to return the number of distinct arrays in this set. So the example above should return 3. How do I achieve this? I tried the code below, but it does not give the right answer
let distinct = 0
for (let i = 0; i < array.length; i++) {
for (let j = i + 1; j < array.length - i; j++) {
let difference = ingredients[i].filter(x => !array[j].includes(x))
if (difference.length > 0) {
distinct += 1;
}
}
}
return distinct;
If the order inside a sub item matters
Use Array.map() to convert each sub-array into a string (I've used String() as suggested by #trincot), create a Set from the array to remove duplicates, and get the size of the Set:
const array = [[1, 2], [1, 2], [3, 4], [5, 6]]
const distinct = new Set(array.map(String))
console.log(distinct.size)
If the order doesn't matter
Sort each sub item, and then convert to string:
const array = [[2, 1], [1, 2], [3, 4], [5, 6]]
const distinct = new Set(array.map(o => String(o.sort())))
console.log(distinct.size)
I didn't check your algorithm at all, just corrected your syntax. Is this what you meant to write?:
let array = [
[1, 2],[1, 2],[3, 4],[5, 6]
];
console.log(countDistinctArrays(array));
function countDistinctArrays(parentArray) {
let distinct = 0;
for (let i = 0; i < parentArray.length; i++) {
for (let j = i + 1; j < parentArray.length - i; j++) {
let difference = parentArray[i].filter(x => !parentArray[j].includes(x))
if (difference.length > 0) {
distinct += 1;
}
}
}
return distinct;
}
Here is how you could have tried. Try converting the inner arrays to a string, then filter the dupes and parse the string again
var pp = [[1, 2], [1, 2] ,[3, 4] ,[5, 6]];
var distict = pp.map(ar=>JSON.stringify(ar))
.filter((itm, idx, arr) => arr.indexOf(itm) === idx)
.map(str=>JSON.parse(str));
console.log(distict.length);

Alternating values from two arrays into a new third array

I'm on my third week of learning Javascript and got an assignment that's giving me some trouble.
I'm supposed to create a function called mix that takes two parameters that are two arrays. When called the function should return a new list which alternates between the two previous arrays (see example below).
This is about arrays and loops so I need to use those. also, I'm only allowed to use the array functions: push, pop, shift & unshift.
My teacher said that this is solved the easiest using a while loop.
Example
mix([], []) === []
mix([2, 4], []) === [2, 4]
mix([], [8, 16]) === [8, 16]
mix([1, 3, 5], [2, 4]) === [1, 2, 3, 4, 5]
mix([10, 9], ['a', 'b', 'c']) === [10, 'a', 9, 'b', 'c']
Before I got the tip about the easiest being a while loop I started trying with a for a loop. The problem I'm having here is that it works as long as the arrays are the same length, but I'm having trouble understanding how I can solve it so the arrays can have different lengths.
Since I'm trying to learn I want pointers in the right direction and not the whole answer!
Please excuse my chaotic beginner code :)
My current code
function mix(array1, array2) {
let newList = [];
for(i = 0; i < array1.length || i < array2.length; i++) {
if(array1.length > 0 || array2.length > 0){
newList.push( array1[i] );
newList.push( array2[i] );
}
}
return newList;
}
mix([10, 9],['a', 'b', 'c'])
I would also like a pointer for how a while loop would be easier and how i would go about using that instead.
Thanks in advance!
To fix your current code, you need to separately check whether i < array1.length (and if so, push array1[i]), and also do the same sort of test for array2:
function mix(array1, array2) {
let newList = [];
for (let i = 0; i < array1.length || i < array2.length; i++) {
if (i < array1.length) {
newList.push(array1[i]);
}
if (i < array2.length) {
newList.push(array2[i]);
}
}
return newList;
}
console.log(mix([10, 9], ['a', 'b', 'c']));
Make sure to declare the i with let i, else you'll implicitly create a global variable (or throw an error in strict mode).
To do this with a while loop, I'd loop while either array has a length, and shift (remove the [0]th item) from them:
function mix(array1, array2) {
const newList = [];
while (array1.length || array2.length) {
if (array1.length) {
newList.push(array1.shift());
}
if (array2.length) {
newList.push(array2.shift());
}
}
return newList;
}
console.log(mix([10, 9], ['a', 'b', 'c']));
You can do much better with array shift, it takes first element from array and returns its value, so for example
const firstElement = [1, 2, 4].shift();
// firstElement - 1
// array [2, 4]
with this info you can now write your function like so:
function (arr1, arr2) {
const resultArr = [];
while(arr1.length && arr2.length) {
resultArr.push(arr1.shift());
resultArr.push(arr2.shift());
}
return resultArr.concat(arr1, arr2);
}
You can achieve it using Array.prototype.shift(), Array.prototype.push() and Spread syntax
function mix(arr1,arr2) {
var newArr=[];
while(arr1.length>0&&arr2.length>0) {
newArr.push(arr1.shift(),arr2.shift());
}
newArr.push(...arr1,...arr2);
return newArr;
}
An alternative approach could be to consider the input arrays as a two-dimensional array.
You can then:
rotate/transpose the two-dimensional array (rows become columns); and
flatten the result (rows are concatenated into a one-dimensional array).
The transformation looks like this for the example input [1, 3, 5], [2, 4]:
Rotate Flatten
[1, 3, 5], ⇒ [1, 2], ⇒ [1, 2, 3, 4, 5]
[2, 4] [3, 4],
[5]
Or, in code:
const mix = (...arrays) => {
const transposed = arrays.reduce((result, row) => {
row.forEach((value, i) => result[i] = [...result[i] || [], value]);
return result;
}, []);
return transposed.flat();
};
console.log(mix([], [])); // === []
console.log(mix([2, 4], [])); // === [2, 4]
console.log(mix([], [8, 16])); // === [8, 16]
console.log(mix([1, 3, 5], [2, 4])); // === [1, 2, 3, 4, 5]
console.log(mix([10, 9], ['a', 'b', 'c'])); // === [10, 'a', 9, 'b', 'c']
Benefits of this approach are that it automatically scales to allow more than two input arrays, and, unlike the shift operations, does not mutate the input arrays.

How to get distinct values from an array of arrays in JavaScript using the filter() method? [duplicate]

This question already has answers here:
How to remove duplicates from a two-dimensional array? [closed]
(3 answers)
Closed 3 years ago.
I have an array like this:
let x = [[1, 2], [3, 4], [1, 2], [2, 1]];
What should I do to retrieve an array without the duplicates?
[[1, 2], [3, 4], [2, 1]];
I would like to use the filter method. I tried this but it doesn't work:
x.filter((value,index,self) => (self.indexOf(value) === index))
EDIT: as I specified to use the filter method, I don't think this question is a duplicate. Also, I got several interesting answers.
Try converting the inner arrays to a string, then filter the dupes and parse the string again.
let x = [[1, 2], [3, 4], [1, 2]];
var unique = x.map(ar=>JSON.stringify(ar))
.filter((itm, idx, arr) => arr.indexOf(itm) === idx)
.map(str=>JSON.parse(str));
console.log(unique);
Filter just causes things to get into O(n^2).
The currently accepted answer uses .filter((itm, idx, arr) => arr.indexOf(itm) === idx) which will cause the array to be iterated each time during each iteration... n^2.
Why even go there? Not only that, you need to parse in the end. It is a lot of excess.
There is no real good way to use filter without hitting O(n^2) here, so if performance is the goal is should probably be avoided.
Instead, just use reduce. It is very straightforward and fast easily accomplishing O(n).
"Bin reduce the set to unique values."
let x = [[1, 2], [3, 4], [1, 2], [2, 1]];
let y = Object.values(x.reduce((p,c) => (p[JSON.stringify(c)] = c,p),{}));
console.log(y);
In case it isn't as clear, here is a more readable version of the bin reduction.
// Sample Data
let dataset = [[1, 2], [3, 4], [1, 2], [2, 1]];
// Create a set of bins by iterating the dataset, which
// is an array of arrays, and structure the bins as
// key: stringified version of the array
// value: actual array
let bins = {};
// Iteration
for(let index = 0; index < dataset.length; index++){
// The current array, from the array of arrays
let currentArray = dataset[index];
// The JSON stringified version of the current array
let stringified = JSON.stringify(currentArray);
// Use the stringified version of the array as the key in the bin,
// and set that key's value as the current array
bins[stringified] = currentArray;
}
// Since the bin keys will be unique, so will their associated values.
// Discard the stringified keys, and only take the set of arrays to
// get the resulting unique set.
let results = Object.values(bins);
console.log(results);
If you were to have to go the route of filter, then n^2 must be used. You can iterate each item looking for existence using every.
"Keep every element which does not have a previous duplicate."
let x = [
[1, 2],
[3, 4],
[1, 2],
[2, 1]
];
let y = x.filter((lx, li) =>
x.every((rx, ri) =>
rx == lx ||
(JSON.stringify(lx) != JSON.stringify(rx) || li < ri))
);
console.log(y);
Okay, the string hash idea is brilliant. Props to I wrestled a bear once. I think the code itself could be a bit better though, so here's how I tend to do this type of thing:
let x = [[1, 2], [3, 4], [1, 2]];
const map = new Map();
x.forEach((item) => map.set(item.join(), item));
console.log(Array.from(map.values()));
And if you want an ugly one liner:
let x = [[1, 2], [3, 4], [1, 2]];
const noRepeats = Array.from((new Map(x.map((item) => [item.join(), item]))).values());
console.log(noRepeats);
This is a solution with time complexity of O(n) where n is the number of elements in your array.
Using the filter method as the OP wants it:
const x = [[1, 2], [3, 4], [1, 2], [2, 1]];
const s = new Set();
const res = x.filter(el => {
if(!s.has(el.join(""))) {
s.add(el.join(""));
return true;
}
return false
})
console.log(res)
My personal preference here is to use ForEach as it looks more readable.
const x = [[1, 2], [3, 4], [1, 2], [2, 1]];
const s = new Set();
const res = [];
x.forEach(el => {
if(!s.has(el.join(""))) {
s.add(el.join(""));
res.push(el)
}
})
console.log(res);
We are using a Set and a simple combination of the elements of the array to make sure they are unique. Otherwise this would become O(n^2).
The equivalent to
x.filter((value,index,self) => (self.indexOf(value) === index))
would be
x.filter((v,i,self) => {
for1:
for (let j = 0; j < self.length; j++) {
if (i == j) {
return true;
}
if (self[j].length != v.length) {
continue;
}
for (let k = 0; k < v.length; k++) {
if (self[j][k] != v[k]) {
continue for1;
}
}
return false;
}
return true;
})
Unlike some of the other answers, this does not require a conversion to string and can thus work with more complex values.
Use === instead of == if you want.
The time complexity is not great, of course.
indexOf does not work on identical instances of arrays/objects type elements within an array, as such arrays just hold references.
In filter function instance you get via parameter v (in below code) is not the same instance as stored in array, making indexOf unable to return the index of it.
In below code, by converting objects to strings we can use indexOf to find duplicates.
let x = [[1, 2], [3, 4], [1, 2], [2, 1]];
console.log(x.
map(function(v){
return JSON.stringify(v)
})
.filter(function(v, i, o) {
return o.length == i ? true : o.slice(i + 1).indexOf(v) == -1;
})
.map(function(v) {
return JSON.parse(v)
})
);

JS - How to remove items from Array that are present on other Array?

I have two array with the following structure:
Array A: [[1, 10], [2, 4], [5,22], [3, 10]]
Array B: [2, 5]
The result array should be:
Array C: [[1, 10], [3, 10]]
I'm trying to use includes() but it's very slow when I have big arrays. What is best solution to remove items on this case? Here is what I'm trying:
let activeCSs = css.filter(cs => !vacant_css.includes(cs[0]));
Assuming, you want only to check the first element of the inner arrays, you could take a Set for the check.
var arrayA = [[1, 10], [2, 4], [5, 22], [3, 10]],
arrayB = [2, 5],
setB = new Set(arrayB)
result = arrayA.filter(([v]) => !setB.has(v));
console.log(result);
Another way is using reduce and filter
var A = [
[1, 10],
[2, 4],
[5, 22],
[3, 10]
];
var B = [2, 5];
var result = B.reduce((carry, current) => carry.filter(list => !list.includes(current)), A);
console.log(result)
Assumption: base on the !vacant_css.includes(cs[0])) code in the question, the comparison is for each integer in ArrayB to the first integer in each array contained in arrayA. arrayA[x][y] === arrayB[z]. Coded to solve that problem.
Poster mentions in comments this is from an exercise, so pretty good bet they were looking for time complexity sensitive solution...
I am not a big O expert (yeah, I hear it), but I think my answer ends up something like: O(n log n) + O(n log n) + O(n). It is not immediately clear to me what the Set based answer would result in big O. O(n) * O(n) + whatever the Set(arrayB) costs I think?
let arrayA = [[1, 10], [2, 4], [5, 22], [3, 10]]
let arrayB = [2, 5]
arrayA.sort((a, b) => a[0] - b[0])
arrayB.sort((a, b) => a - b)
let i = 0
let j = 0
let arrayAnswer = []
while (i<arrayA.length&&j<arrayB.length) {
let d = arrayA[i][0]-arrayB[j];
console.log(`i ${i} j ${j} d ${d}`)
if (d === 0) {
i++
} else if (d<0) {
arrayAnswer.push(arrayA[i])
i++
} else {
j++
}
}
console.log(arrayAnswer)
Warning: This code is not tested for edge cases and is janky and old school.

Categories

Resources