I know I can do it using loops, but I'm trying to find an elegant way of doing this:
I have two jagged arrays (array of arrays):
var array1 = [['a', 'b'], ['b', 'c']];
var array2 = [['b', 'c'], ['a', 'b']];
I want to use lodash to confirm that the above two jagged arrays are the same. By 'the same' I mean that there is no item in array1 that is not contained in array2. Notice that the items in jagged array are actually arrays. So I want to compare between inner arrays.
In terms of checking equality between these items:
['a', 'b'] == ['b', 'a']
or
['a', 'b'] == ['a', 'b']
Both work since the letters will always be in order.
UPDATE: Original question was talking about to "arrays" (instead of jagged arrays) and for years many people discussed (and added answers) about comparing simple one-dimensional arrays (without noticing that the examples provided in the question were not actually similar to the simple one-dimensional arrays they were expecting).
If you sort the outer array, you can use _.isEqual() since the inner array is already sorted.
var array1 = [['a', 'b'], ['b', 'c']];
var array2 = [['b', 'c'], ['a', 'b']];
_.isEqual(array1.sort(), array2.sort()); //true
Note that .sort() will mutate the arrays. If that's a problem for you, make a copy first using (for example) .slice() or the spread operator (...).
Or, do as Daniel Budick recommends in a comment below:
_.isEqual(_.sortBy(array1), _.sortBy(array2))
Lodash's sortBy() will not mutate the array.
You can use lodashs xor for this
doArraysContainSameElements = _.xor(arr1, arr2).length === 0
If you consider array [1, 1] to be different than array [1] then you may improve performance a bit like so:
doArraysContainSameElements = arr1.length === arr2.length && _.xor(arr1, arr2).length === 0
There are already answers here, but here's my pure JS implementation. I'm not sure if it's optimal, but it sure is transparent, readable, and simple.
// Does array a contain elements of array b?
const union = new Set([...a, ...b]);
const contains = (a, b) => union.size === a.length && union.size === b.length;
// Since order is not important, just data validity.
const isEqualSet = (a, b) => union.contains(a, b) || union.contains(b, a)
The rationale in contains() is that if a does contain all the elements of b, then putting them into the same set would not change the size.
For example, if const a = [1,2,3,4] and const b = [1,2], then new Set([...a, ...b]) === {1,2,3,4}. As you can see, the resulting set has the same elements as a.
From there, to make it more concise, we can boil it down to the following:
const isEqualSet = (a: string[], b: sting[]): boolean => {
const union = new Set([...a, ...b])
return union.size === a.length && union.size === b.length;
}
Edit: This will not work with obj[{a: true}, true, 3] but does compare array contents probably as long as they are primitive elements. Method fixed and tested against strings two arrays using the same values in different orders. Does not work with object types. I recommend making a universal helper which calls a helper function depending on the type which needs to be compared. Try _.isEqual(a. b); from the very fantastic lodash library.
By 'the same' I mean that there are is no item in array1 that is not contained in array2.
You could use flatten() and difference() for this, which works well if you don't care if there are items in array2 that aren't in array1. It sounds like you're asking is array1 a subset of array2?
var array1 = [['a', 'b'], ['b', 'c']];
var array2 = [['b', 'c'], ['a', 'b']];
function isSubset(source, target) {
return !_.difference(_.flatten(source), _.flatten(target)).length;
}
isSubset(array1, array2); // → true
array1.push('d');
isSubset(array1, array2); // → false
isSubset(array2, array1); // → true
PURE JS (works also when arrays and subarrays has more than 2 elements with arbitrary order). If strings contains , use as join('-') parametr character (can be utf) which is not used in strings
array1.map(x=>x.sort()).sort().join() === array2.map(x=>x.sort()).sort().join()
var array1 = [['a', 'b'], ['b', 'c']];
var array2 = [['b', 'c'], ['b', 'a']];
var r = array1.map(x=>x.sort()).sort().join() === array2.map(x=>x.sort()).sort().join();
console.log(r);
I definitely feel very unclean for posting this solution, but:
var array1 = [['a', 'b'], ['b', 'c']];
var array2 = [['b', 'c'], ['a', 'b']];
_.isMatch([array1], [array2]) && _.isMatch([array2], [array1]) // true
array1 = [['b', 'a'], ['c', 'b']];
array2 = [['b', 'c'], ['a', 'b']];
_.isMatch([array1], [array2]) && _.isMatch([array2], [array1]) // also true
Note that you have to wrap array1 and array2 into a container (array, object) in order for this to work? Why? There's probably a perfectly stupid reason for this.
import { differenceBy } from 'lodash'
export default function (arr1, arr2) {
return !differenceBy(arr1, arr2).length && arr1.length === arr2.length
}
if there are no different characters and the array length is the same, it makes them the same.
Edit: I missed the multi-dimensional aspect of this question, so I'm leaving this here in case it helps people compare one-dimensional arrays
It's an old question, but I was having issues with the speed of using .sort() or sortBy(), so I used this instead:
function arraysContainSameStrings(array1: string[], array2: string[]): boolean {
return (
array1.length === array2.length &&
array1.every((str) => array2.includes(str)) &&
array2.every((str) => array1.includes(str))
)
}
It was intended to fail fast, and for my purposes works fine.
We can use _.difference function to see if there is any difference or not.
function isSame(arrayOne, arrayTwo) {
var a = _.uniq(arrayOne),
b = _.uniq(arrayTwo);
return a.length === b.length &&
_.isEmpty(_.difference(b.sort(), a.sort()));
}
// examples
console.log(isSame([1, 2, 3], [1, 2, 3])); // true
console.log(isSame([1, 2, 4], [1, 2, 3])); // false
console.log(isSame([1, 2], [2, 3, 1])); // false
console.log(isSame([2, 3, 1], [1, 2])); // false
// Test cases pointed by Mariano Desanze, Thanks.
console.log(isSame([1, 2, 3], [1, 2, 2])); // false
console.log(isSame([1, 2, 2], [1, 2, 2])); // true
console.log(isSame([1, 2, 2], [1, 2, 3])); // false
I hope this will help you.
Adding example link at StackBlitz
Related
The question I am trying to answer is: Combos of Any length
Modify this function so that it returns all combinations of the elements of arr as an array of arrays. Use Recursion!
This is what I have so far:
function getAllCombos(arr, newArr = []) {
if (arr[0] === undefined) return newArr
newArr.push(arr)
return getAllCombos(arr.slice(1), newArr)
}
console.log(getAllCombos(['a', 'b']));
console.log(getAllCombos(['a', 'b', 'c']))
it gives me this result:
[['a', 'b'], ['b']]
[['a', 'b', 'c'], ['b', 'c'], ['c']]
I am looking to get this result:
[['a','b'], ['a'], ['b'], []]
[['a', 'b', 'c'],['a', 'b'], ['a', 'c'], ['a'], ['b', 'c'], ['b'], ['c'], [],]
How do I iterate through the array getting different combos? While also not repeating arrays that have already been pushed?
Here are the test cases:
console.log(getAllCombos(['a', 'b']));
console.log(getAllCombos(['a', 'b', 'c']))
Short answer
const getAllCombosShort= array => array.reduce((acc, curr) => acc.flatMap(e => [e, [...e, curr]]), [[]])
console.log(getAllCombosShort(['a','b','c']));
Detailed answer
How recursion is structured
Recursion implies structuring your solution in three steps:
Base Case: All the combos of an empty array is simply an array with an empty array inside.
Induction Hypothesis (I.H.): Suppose it's known how to get all the combos for any array of length n-1.
General Case (G.C.): Proof that you can solve for any array of length n using previous step.
How to construct a recursive algorithm
The key to reach the solution S through recursion is to find out how to extend the truth from I.H. to G.C.
Assume that you give some arbitrary input to one of your best friends and, in return, he gives you the solution S' for the problem. However, as he doesn't want to give the solution S to you on a silver plate, he accepted to do all the part of the work which have one unit less than the original input.
With his solution S' in hands you're not only able to solve the problem with the complete input, but also construct the algorithm naturally.
For instance, ['a','b', 'c']
Give him ['b', 'c'] and get:
[ ['b','c'], ['b'], ['c'], [] ]
For each element of it, create a new element which contains itself plus 'a' and push it to his solution array, i.e.:
[ ['b','c'], ['b'], ['c'], [] ,
['a','b','c'], ['a','b'], ['a','c'], ['a'] ]
Pseudocode
algorithm allCombos
input: array of length n
output: array of all combinations
if array is empty
return an array with empty inside
head := head(array) -- head
tail := tail(array)
friend := allCombos(tail) -- your friend knows how to solve for one smaller
for each element of friends do
new_elem := [...element, head]
friend.push(new_elem)
return friend
Correspondent in Javascript:
function getAllCombos(array) {
if (array && array.length === 0) return [[]]
let head = array[0]
let tail = array.slice(1)
friend = getAllCombos(tail)
friend.forEach(elem => friend.push([head, ...elem]))
return friend
}
console.log(getAllCombos(['a','b']))
console.log(getAllCombos(['a', 'b', 'c']))
This question already has answers here:
Create copy of multi-dimensional array, not reference - JavaScript
(4 answers)
Closed 8 months ago.
I want to copy an array of arrays at a different allocation.
I know that I can copy an array in the following way:
a = [[1, 2], [3, 4]]
b = a.slice() // this makes sure that a and b are allocated differently in memory
Now if I change something inside b, then of course,
b[0] = 'abc'
console.log(a, b) // expect a = [[1,2], [3,4]] and b = ['abc', [3,4]]
But when I do the below, a gets changed as well...!
b[0][0] = 'abc'
console.log(a, b) // now it gives a = [['abc', 2], [3, 4]] and b = [['abc', 2], [3, 4]]
Why is this happening, and how can I avoid mutating a?
Thanks so much!
If you know you are only copying 2D arrays you could use a function like the following and avoid using JSON:
function copy2D(array){
result = []
array.forEach((subArray) => {
result.push(subArray.slice())
})
return result
}
One way would be by using map combined with the spread operator. This would be the easiest approach if you can assume that you have a 2D array only
const a = [[1, 2], [3, 4]]
const b= a.map(item => ([...item]))
b[0][0]= "abc"
console.log('a:', a, 'b: ', b)
I am trying to build unique array of arrays such that whenever I have new array to add it should only add if it doesn't already exist in collection
E.g. store all unique permutations of [1,1,2]
Actual : [[1,1,2],[1,2,1],[1,1,2],[1,2,1],[2,1,1],[2,1,1]]
Expected : [[1,1,2],[1,2,1],[2,1,1]]
Approaches I tried:
Array.Filter: Doesn't work because arrays are object and each value in uniqueArrComparer is a unique object reference to that array element.
function uniqueArrComparer(value, index, self) {
return self.indexOf(value) === index;
}
result.filter(uniqueArrComparer)
Set/Map: Thought I can build a unique array set but it doesn't work because Set internally uses strict equality comparer (===), which will consider each array in this case as unique.
We cannot customize object equality for JavaScript Set
Store each array element as a string in a Set/Map/Array and build an array of unique strings. In the end build array of array using array of unique string. This approach will work but doesn't look like efficient solution.
Working solution using Set
let result = new Set();
// Store [1,1,2] as "1,1,2"
result.add(permutation.toString());
return Array.from(result)
.map(function(permutationStr) {
return permutationStr
.split(",")
.map(function(value) {
return parseInt(value, 10);
});
});
This problem is more of a learning exercise than any application problem.
One way would be to convert the arrays to JSON strings, then use a Set to get unique values, and convert back again
var arr = [
[1, 1, 2],
[1, 2, 1],
[1, 1, 2],
[1, 2, 1],
[2, 1, 1],
[2, 1, 1]
];
let set = new Set(arr.map(JSON.stringify));
let arr2 = Array.from(set).map(JSON.parse);
console.log(arr2)
If you are ok to use a library, try lodash uniqWith. This will recursively find groups of arrays OR objects with the comparator of your choice: equal in your case.
var arrayofarrays = [ [1,1,2], [1,2,1], [1,1,2], [1,2,1], [2,1,1], [2,1,1] ]
const uniqarray = _.uniqWith(arrayofarrays, _.isEqual);
console.log(uniqarray) //=> [[1, 1, 2], [1, 2, 1], [2, 1, 1]]
Bonus: it works on array of objects too
var objects = [{ 'x': 1, 'y': {b:1} }, { 'x': 1, 'y': {b:1} },
{ 'x': 2, 'y': {b:1} }, { 'x': 1, 'y': 2 } ];
const uniqarray = _.uniqWith(objects, _.isEqual);
console.log(uniqarray)
// => [{x: 1, y: {b: 1}}, {x: 2, y: {b: 1}}, {x: 1, y: 2}]
To get around the problem of each array being a unique object, you can stringify it so it's no longer unique, then map it back to an array later. This should do the trick:
var arr = [
[1, 1, 2],
[1, 2, 1],
[1, 1, 2],
[1, 2, 1],
[2, 1, 1],
[2, 1, 1]
];
var unique = arr.map(cur => JSON.stringify(cur))
.filter(function(curr, index, self) {
return self.indexOf(curr) == index;
})
.map(cur => JSON.parse(cur))
console.log(unique);
The fastest method I've found is:
const points = [
[0,0],
[100,100],
[400,400],
[200,200],
[200,200],
[200,200],
[300,300],
[400,400],
]
const uniquePoints = Array.from(
new Map(points.map((p) => [p.join(), p])).values()
)
All of the methods in this thread are fast. This one is faster than the Set method, however, as we never need to convert the stringified array back into a array.
To find unique objects, replace p.join() with JSON.stringify(p).
Note
In my case, the method shown above turned out to be the wrong strategy, as I was only really needing to check against identical adjacent points. For example, the test array used above includes the value [400,400] two times, though these values are not consecutive. The method shown above would have removed the second instance, while the code below would have kept it.
points = points.filter(
(point, i) =>
i === 0 ||
!(points[i - 1][0] === point[0] && points[i - 1][1] === point[1])
)
You can subclass Set for more flexibility in storing objects by storing the result of calling JSON.stringify on added objects.
class ObjectSet extends Set{
add(elem){
return super.add(typeof elem === 'object' ? JSON.stringify(elem) : elem);
}
has(elem){
return super.has(typeof elem === 'object' ? JSON.stringify(elem) : elem);
}
}
let set = new ObjectSet([[1,1,2],[1,2,1],[1,1,2],[1,2,1],[2,1,1],[2,1,1]]);
console.log([...set]);
console.log([...set].map(JSON.parse));//get objects back
This works, but I'm wondering if there is a better way to filter by index:
a = [10,20,30,40]
b = [1,3]
a.filter((x,i) => b.includes(i))
// [20, 40]
Another way would be b.map(aIndex => a[aIndex]). If b is shorter than a this could also be faster. However if there are indexes in b that do not belong in a, you would end up with undefined "holes" in the array.
EDIT
Looking a bit into Array.includes, looks like it will run in O(n) for unsorted arrays.
If we say A = a.length and B = b.length, your solution from the question should run in O(A * B).
The second solution (with map) will run in O(B). To fix the undefined holes, you could add a .filter(element => typeof element !== 'undefined').
The final solution would then be b.map(i => a[i]).filter(e => typeof e !== 'undefined'). This now runs in O(2 * B), which should still be better than O(A * B).
I think your solution is just great. map is a great solution too.
For the record, you could use a for...of loop, but it becomes much more complex for nothing...
let a = [10, 20, 30, 40],
b = [1, 3];
let res = [];
for (const entry of a.entries()) {
if (b.includes(entry[0])) {
res.push(entry[1]);
}
}
console.log(res);
This (and more) can be done with Lodash's at function:
_.at([10, 20, 30, 40], [1, 3]) // [20, 40]
_.at(['a', 'b'], [0, 1, 1, 0]) // ['a', 'b', 'b', 'a']
I want to replace elements in some array from 0 element, with elements of another array with variable length. Like:
var arr = new Array(10), anotherArr = [1, 2, 3], result;
result = anotherArr.concat(arr);
result.splice(10, anotherArr.length);
Is there some better way?
You can use the splice method to replace part of an array with items from another array, but you have to call it in a special way as it expects the items as parameters, not the array.
The splice method expects parameters like (0, anotherArr.Length, 1, 2, 3), so you need to create an array with the parameters and use the apply method to call the splice method with the parameters:
Array.prototype.splice.apply(arr, [0, anotherArr.length].concat(anotherArr));
Example:
var arr = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
var anotherArr = [ 1, 2, 3 ];
Array.prototype.splice.apply(arr, [0, anotherArr.length].concat(anotherArr));
console.log(arr);
Output:
[ 1, 2, 3, 'd', 'e', 'f', 'g', 'h', 'i', 'j']
Demo: http://jsfiddle.net/Guffa/bB7Ey/
In ES6 with a single operation, you can do this to replace the first b.length elements of a with elements of b:
let a = [1, 2, 3, 4, 5]
let b = [10, 20, 30]
a.splice(0, b.length, ...b)
console.log(a) // -> [10, 20, 30, 4, 5]
It could be also useful to replace the entire content of an array, using a.length (or Infinity) in the splice length:
let a = [1, 2, 3, 4, 5]
let b = [10, 20, 30]
a.splice(0, a.length, ...b)
// or
// a.splice(0, Infinity, ...b)
console.log(a) // -> [10, 20, 30], which is the content of b
The a array's content will be entirely replaced by b content.
Note 1: in my opinion the array mutation should only be used in performance-critical applications, such as high FPS animations, to avoid creating new arrays. Normally I would create a new array maintaining immutability.
Note 2: if b is a very large array, this method is discouraged, because ...b is being spread in the arguments of splice, and there's a limit on the number of parameters a JS function can accept. In that case I encourage to use another method (or create a new array, if possible!).
In ES6, TypeScript, Babel or similar you can just do:
arr1.length = 0; // Clear your array
arr1.push(...arr2); // Push the second array using the spread opperator
Simple.
For anyone looking for a way to replace the entire contents of one array with entire contents of another array while preserving the original array:
Array.prototype.replaceContents = function (array2) {
//make a clone of the 2nd array to avoid any referential weirdness
var newContent = array2.slice(0);
//empty the array
this.length = 0;
//push in the 2nd array
this.push.apply(this, newContent);
};
The prototype function takes an array as a parameter which will serve as the new array content, clones it to avoid any weird referential stuff, empties the original array, and then pushes in the passed in array as the content. This preserves the original array and any references.
Now you can simply do this:
var arr1 = [1, 2, 3];
var arr2 = [3, 4, 5];
arr1.replaceContents(arr2);
I know this is not strictly what the initial question was asking, but this question comes up first when you search in google, and I figured someone else may find this helpful as it was the answer I needed.
You can just use splice, can add new elements while removing old ones:
var arr = new Array(10), anotherArr = [1, 2, 3];
arr.splice.apply(arr, [0, anotherArr.length].concat(anotherArr))
If you don't want to modify the arr array, you can use slice that returns a shallow copy of the array:
var arr = new Array(10), anotherArr = [1, 2, 3], result = arr.slice(0);
result.splice.apply(result, [0, anotherArr.length].concat(anotherArr));
Alternatively, you can use slice to cut off the first elements and adding the anotherArr on top:
result = anotherArr.concat(arr.slice(anotherArr.length));
I'm not sure if it's a "better" way, but at least it allows you to choose the starting index (whereas your solution only works starting at index 0). Here's a fiddle.
// Clone the original array
var result = arr.slice(0);
// If original array is no longer needed, you can do with:
// var result = arr;
// Remove (anotherArr.length) elements starting from index 0
// and insert the elements from anotherArr into it
Array.prototype.splice.apply(result, [0, anotherArr.length].concat(anotherArr));
(Damnit, so many ninjas. :-P)
You can just set the length of the array in this case. For more complex cases see #Guffa's answer.
var a = [1,2,3];
a.length = 10;
a; // [1, 2, 3, undefined x 7]