How to build a Set of unique Arrays? - javascript

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()]);

Related

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)

Why array loops works in Python to remove duplicated entries, but JS don't?

I'm new in programming and I already have a little bit of background in Python.
At the moment, I'm studying Javascript and I was doing an exercise that should remove duplicate entries in an array.
I don't understand why the logic bellow doesn't work in JS, but works in Python. Can someone explain to me?
Javascript:
let array = [3, 3, 3, 4, 5];
let noRepetition = [];
for (let i of array) {
if (!(i in noRepetition)) {
noRepetition.push(i)
}
}
console.log('Before ', array);
console.log('After ', noRepetition);
Output:
Before [ 3, 3, 3, 4, 5 ]
After [ 3, 3, 3, 4, 5 ]
Python:
array = [3, 3, 3, 4, 5]
noRepetition = []
for i in array:
if not i in noRepetition:
noRepetition.append(i)
print(f'Before {array}')
print(f'After {noRepetition}')
Output:
Before [3, 3, 3, 4, 5]
After [3, 4, 5]
Actually, the in operator in javascript is not working as in python and checks keys (for an array, indexes).
> "a" in ["a", "b"]
false
> 1 in ["a", "b"]
true
> 2 in ["a", "b"]
false
You can use includes:
> ["a", "b"].includes("a")
true
And this should work as you expect.
You should also check libraries like lodash which provide a function for this.
Also you might want to consider using Sets if you want your data structure to be duplicate-free:
Array.from(new Set([2, 1, 3, 1]))
[ 2, 1, 3 ]
JavaScript: in operator parameters description:
A string or symbol representing a property name or array index (non-symbols will be coerced to strings).
which mean:
// Arrays
let trees = ['redwood', 'bay', 'cedar', 'oak', 'maple']
0 in trees // returns true
3 in trees // returns true
6 in trees // returns false
'bay' in trees // returns false (you must specify the index number, not the value at that index)
'length' in trees // returns true (length is an Array property)
Symbol.iterator in trees // returns true (arrays are iterable, works only in ES2015+)
// Predefined objects
'PI' in Math // returns true
// Custom objects
let mycar = {make: 'Honda', model: 'Accord', year: 1998}
'make' in mycar // returns true
'model' in mycar // returns true
in operator - JavaScript | MDN

Just for kicks: Creating/deep-copying multidimensional arrays in JavaScript and accounting for variable length?

I volunteer teaching coding to young girls to code and one of the more advanced ones was trying to use a 2D array for her JavaScript project, but was struggling with the concept of multidimensional arrays. I started putting together a tutorial on arrays and multidimensional arrays to review with her next week, got a little carried away with writing a matrix searching demo, and then realized I don't know a great way of deep copying or creating filled multidimensional arrays that copy the potentially variable-length dimensions of another array (e.g., for storing visited cell data when searching) in JavaScript, which I've only really learned/used within the last year-ish. This is what I came up with:
/**
* #param mdArray A multidimensional array that may contain variable length arrays
* #param fillValue The value to fill the cells with
*
* #return A multidimensional array with the same dimensions as mdArray where
* each cell is filled with fillValue
*/
function createFilledMultidimensionalArray(mdArray, fillValue) {
// Create a new array with mdArray.length rows
return new Array(mdArray.length).fill().map( function (elt, row) {
// Populate each row with a new filled array containing fillValue
return new Array(mdArray[row].length).fill(fillValue);
}
);
}
/**
* #param mdArray A multidimensional array that may contain variable length arrays
*
* #return A deep copy of mdArray
*/
function multidimensionalArrayCopy(mdArray) {
return JSON.parse(JSON.stringify(mdArray));
// note: I'm aware this isn't a universally perfect deep copy *shrug*
}
/* Testing */
// Create a ridiculous array containing variable-length arrays
var multidimensionalArray = [[6, { a: '1', b: 2 }], [1, 2], [3, 4, 5], ['seven']];
// Copy and print the array
var copied = multidimensionalArrayCopy(multidimensionalArray);
console.log(multidimensionalArray);
// Prints: [ [ 6, { a: '1', b: 2 } ], [ 1, 2 ], [ 3, 4, 5 ], [ 'seven' ] ]
// Modify a value
multidimensionalArray[0][1].b = 'hi';
// Print both arrays, observe copy is deep
console.log(multidimensionalArray);
console.log(copied);
/* Prints:
[ [ 6, { a: '1', b: 'hi' } ], [ 1, 2 ], [ 3, 4, 5 ], [ 'seven' ] ]
[ [ 6, { a: '1', b: 2 } ], [ 1, 2 ], [ 3, 4, 5 ], [ 'seven' ] ]
*/
// Create a new array with same dimensions as 'copied' where each cell is filled with 'false'
console.log(createFilledMultidimensionalArray(copied, false));
/* Prints:
[ [ false, false ],
[ false, false ],
[ false, false, false ],
[ false ] ]
*/
Does anyone else out there with more JS experience have any other ideas? (Please don't suggest slice, which shallow copies.)
You could use a recursive clone function instead of stringifying your data. Then for filling multi-dimension arrays, you could use a recursive deepMap function that calls itself on nested arrays:
function clone(value) {
if(Array.isArray(value)) return value.map(clone);
if(typeof value === 'object') {
return Object.entries(value).reduce((cloned, [key, value]) => {
cloned[key] = clone(value);
return cloned;
}, {});
}
return value;
}
function deepMap(array, fn) {
return array.map(value =>
Array.isArray(value)
? deepMap(value, fn)
: fn(value)
);
}
function deepFill(array, fillValue) {
return deepMap(array, () => fillValue);
}
const original = [
[1, 2, 3],
[{value: 'unchanged'}, 5, 6],
[false, true],
[7, 8, [9, 10], 11, 12],
];
const cloned = clone(original);
const filled = deepFill(original, 'fill');
cloned[1][0].value = 'changed';
console.log(
original[1][0].value,
cloned[1][0].value,
);
console.log(filled);

Merging many arrays from Promise.all

When Promise.all completes it returns an array of arrays that contain data. In my case the arrays are just numbers:
[
[ 1, 4, 9, 9 ],
[ 4, 4, 9, 1 ],
[ 6, 6, 9, 1 ]
]
The array can be any size.
Currently I'm doing this:
let nums = []
data.map(function(_nums) {
_nums.map(function(num) {
nums.push(num)
})
})
Is there an alternative way of doing this? Does lodash have any functions that are able to do this?
ES2019 introduced Array.prototype.flat which significantly simplifies this to:
const nums = data.flat();
const data = [
[ 1, 4, 9, 9 ],
[ 4, 4, 9, 1 ],
[ 6, 6, 9, 1 ]
];
const nums = data.flat();
console.log(nums);
Original Answer
Use reduce and concat:
data.reduce(function (arr, row) {
return arr.concat(row);
}, []);
Or alternatively, concat and apply:
Array.prototype.concat.apply([], data);
I would do as follows;
var a = [
[ 1, 4, 9, 9 ],
[ 4, 4, 9, 1 ],
[ 6, 6, 9, 1 ]
],
b = [].concat(...a)
console.log(b)
You actually don't need any sort of library to do it, you can use concat with apply:
Promise.all(arrayOfPromises).then((arrayOfArrays) => {
return [].concat.apply([], arrayOfArrays);
});
If you are using lodash, though, you can use _.flatten(arrayOfArrays) for the same effect.
If using async/await, to expand on #Retsam's answer, you can do it like so
const mergedArray = []
.concat
.apply([], await Promise.all([promise1, promise2, promiseN]));
A real world example I did using the AWS SDK, getting a list of usernames from multiple IAM user groups
const users = await getActiveUsersByGroup(['group1', 'group2'])
async function getActiveUsersByGroup(groups = []) {
getUsersByGroupPromises = groups.map(group => getUsersByGroup(group));
const users = []
.concat
.apply([], await Promise.all(getUsersByGroupPromises)) // Merge (concat) arrays
.map(users => users.UserName); // Construct new array with just the usernames
return users;
}
async function getUsersByGroup(group) {
const params = {
GroupName: group,
MaxItems: 100 // Default
};
const { Users: users } = await iam.getGroup(params).promise();
return users;
}

What is the difference between these two bits of code? (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.

Categories