Map object, remove elements in a single loop - javascript

What is a concise and the efficient way to remove elements of a Map object in a single loop upon some condition?
Iterating on the map's entries() can do it but calling entries() at each iteration doesn't look efficient
let name_value_map = new Map([['One', 1], ['Two', 2], ['Three', 3], ['Four', 4]])
for (let pair of name_value_map.entries())
if (pair[1] <= 3)
name_value_map.delete(pair[0])
Other ideas?

Use the Map.forEach() method:
const name_value_map = new Map([['One', 1], ['Two', 2], ['Three', 3], ['Four', 4]]);
name_value_map.forEach((v, k, m) => v <= 3 && m.delete(k));
console.log([...name_value_map.entries()]); // SO console doesn't display Map

Use entries and filter along with spread operator ...
name_value_map = new Map([...name_value_map.entries()].filter( s => s[1] > 3 ));
Demo
var name_value_map = new Map([
['One', 1],
['Two', 2],
['Three', 3],
['Four', 4]
]);
name_value_map = new Map([...name_value_map.entries()].filter(s => s[1] > 3));
console.log(name_value_map); //check the browser's console

You can use Array.from, then use .filter, and convert back to Map. This won't mutate the orginal Map.
The Array.from() method creates a new Array instance from an
array-like or iterable object.
let name_value_map = new Map([['One', 1], ['Two', 2], ['Three', 3], ['Four', 4]])
const filtered = new Map(
Array.from(name_value_map).filter(([,value]) => value > 3)
);
console.log([...filtered.entries()])

Why not to use an object ?
const ob = {
One: 1,
Two: 2,
Three: 3,
Four: 4
};
for (const key of Object.keys(ob)) {
if (ob[key] <= 3)
delete ob[key];
}

Related

How to add extra values from 2 arrays to end of new array

For example, if I have 2 arrays being:
let array1 = [1,2,3]; and let array2 = [5,6,7,8,9], how would I create a new 3rd array that only contains the elements from array2 that are at the extra indexes (so the new array would only contain [8,9] since they are at index 3 and 4).
Thanks!
You can use the Javascript slice() function
arr1 = [1,2,3]
arr2=[5,6,7,8,9]
arr3 = arr2.slice(arr1.length , arr2.length)
console.log(arr3)
There are plenty of ways to do that, here's a solution that uses slice method with a negative index. That negative index is (array2.length - array1.length) * -1.
const array1 = [1, 2, 3],
array2 = [5, 6, 7, 8, 9],
sliceArray = (a1, a2) => a2.length <= a1.length ? [] : a2.slice((a2.length - a1.length) * -1)
console.log(sliceArray([], [])); // returns: []
console.log(sliceArray([1, 3, 6], [1, 2])); // returns: []
console.log(sliceArray(array1, array2)); // returns: [8, 9]
let array1 = [1,2,3,8,9];
let array2 = [5,6,7];
let array3=array1.length > array2.length ?array1.slice(array2.length,array1.length):array2.slice(array1.length,array2.length)
console.log(array3);
Use ternary operator and slice method
You can use a function that will find the longer array and slice it for you. That way, you don't need to know in advance which array is longer:
function collectExtra (arrayA, arrayB) {
if (arrayA.length === arrayB.length) return [];
const [shorter, longer] = [arrayA, arrayB]
.sort((a, b) => a.length - b.length);
return longer.slice(shorter.length);
}
const array1 = [1,2,3];
const array2 = [5,6,7,8,9];
const result = collectExtra(array1, array2);
console.log(result); // [8, 9]
const result2 = collectExtra(array2, array1);
console.log(result2); // [8, 9]

How to rearrange 3 arrays with according to the elements and return as one array?

I have 3 arrays. For example, given the arrays are
arr1 = [2, 7]
arr2 = [0, 1, 16]
arr3 = [3, 6, 9]
And I would like to rearrange them according to the numbers and output as an array.
result = ['arr2', 'arr2', 'arr1', 'arr3', 'arr3', 'arr1', 'arr3', 'arr2']
I think it might have something to do with looping, but I've no luck after struggling for a while. Is there any way to get the expected result?
You could move the values to an object and take another object for the indices and sort an array of key.
Then take the key at index zero and go on until the index is equal to the length of the array, then stop the iteration.
const
arr1 = [2, 7],
arr2 = [0, 1, 16],
arr3 = [3, 6, 9],
values = { arr1, arr2, arr3 },
result = [],
keys = Object.keys(values),
indices = Object.fromEntries(keys.map(k => [k, 0]));
while (true) {
keys.sort((a, b) => (values[a][indices[a]] ?? Number.MAX_VALUE) - (values[b][indices[b]] ?? Number.MAX_VALUE));
if (indices[keys[0]] === values[keys[0]].length) break;
result.push(keys[0]);
indices[keys[0]]++;
}
console.log(...result);
A shorter approach by mapping entries, sorting and mapping again.
const
arr1 = [2, 7],
arr2 = [0, 1, 16],
arr3 = [3, 6, 9],
result = Object
.entries({ arr1, arr2, arr3 })
.flatMap(([k, a]) => a.map(v => [k, v]))
.sort(([, a], [, b]) => a - b)
.map(([k]) => k);
console.log(...result);
Option 1. Allocate an array whose size is equal to the total count of elements in the 3 arrays. Populate the newly created array with the elements from your 3 small arrays. Then sort the created array.
Option 2. Merge 2 of the 3 arrays to produce a sorted array with elements from the chosen 2 small arrays. Then merged the sorted array from the previous step with the 3rd array to get the array that you need.
Not an elegant solution but maybe something like can help
arr1 = [2, 7]
arr2 = [0, 1, 16]
arr3 = [3, 6, 9]
let concatedArray = [...arr1, ...arr2, ...arr3].sort((a, b) => a - b);
let finalArr = []
concatedArray.forEach(val => {
let doesNumberExist = false;
let arrName = ''
doesNumberExist = arr1.includes(val);
arrName = 'arr1';
if (!doesNumberExist) {
doesNumberExist = arr2.includes(val);
arrName = 'arr2'
}
if (!doesNumberExist) {
doesNumberExist = arr3.includes(val);
arrName = 'arr3'
}
finalArr.push(arrName);
}
)
console.log(finalArr)

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)
})
);

How to only allow an array to be pushed into an array once?

I have an array that has other arrays in it which have been pushed in. For an example:
const Arrays = [ [1,2,3], [4,5,6], [7,8,9], [2,1,3] ];
let myArr = [];
Arrays.map(arr => {
if(myArr.indexOf(arr)){
return
}
myArr.push(arr)
})
const myArr = [ [1,2,3], [4,5,6], [7,8,9], [2,1,3] ];
In this array you can see that there are two arrays with the same set of numbers 1, 2 and 3. I want to somehow set a condition saying:
If this array already exist then do not add this array in any order again to prevent this from happening. So that when it comes in the loop that this set of numbers comes up again it will just skip over it.
You can use some() and every() methods to check if same array already exists before push().
const myArr = [ [1,2,3], [4,5,6], [7,8,9] ];
let input = [2,1,3]
function check(oldArr, newArr) {
return oldArr.some(a => {
return a.length == newArr.length &&
a.every(e => newArr.includes(e))
})
}
if(!check(myArr, input)) myArr.push(input)
console.log(myArr)
You can make temp array with sorted element with joined and check by indexOf
const myArr = [ [1,2,3], [4,5,6], [7,8,9], [2,1,3],[6,5,4] ];
var newArr = [];
var temp = [];
for(let i in myArr){
let t = myArr[i].sort().join(",");
if(temp.indexOf(t) == -1){
temp.push(t);
newArr.push(myArr[i]);
}
}
console.log(newArr);
The accepted answer does not respect the special case where only two values are in the array and the array has to check against two values in a different count like
[1, 1, 2]
and
[1, 2, 2]
which are different arrays.
For a working solution, I suggest to use a Map and count the occurences of same values of the first array and subtract the count of the values for the other arrray.
As result return the check if all elements of the Map are zero.
function compare(a, b) {
var map = new Map;
a.forEach(v => map.set(v, (map.get(v) || 0) + 1));
b.forEach(v => map.set(v, (map.get(v) || 0) - 1));
return [...map.values()].every(v => !v);
}
var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [2, 1, 3], [1, 1, 2], [1, 2, 2], [2, 1, 1], [2, 1, 2]],
unique = array.reduce((r, a) => (r.some(b => compare(a, b)) || r.push(a), r), []);
console.log(unique);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way is to sort them numerically with .sort() then compare them.

Categories

Resources