Related
I need to remove similar duplicates as well as real duplicates from 2D array in JavaScript.
let a = [
[5, 6],
[1,1],
[6,5],
[1,1],
[3,2],
[2,3]
]
function makeUnique(arr) {
var uniques = [];
var itemsFound = {};
for(var i = 0, l = arr.length; i < l; i++) {
var stringified = JSON.stringify(arr[i]);
if(itemsFound[stringified]) continue;
uniques.push(arr[i]);
itemsFound[stringified] = true;
}
return uniques;
}
a=makeUnique(a)
console.log(a);
I have got this output:
[ [ 5, 6 ], [ 1, 1 ], [ 6, 5 ], [ 3, 2 ], [ 2, 3 ] ]
Correct should be:
[ [ 5, 6 ], [ 1, 1 ], [ 2, 3 ] ]
My code removes correctly duplicates, but I need to remove similar duplicates also.
For example if I have [3,2] and [2,3] I should remove [3,2] (the one which has bigger starting index value.)
Could you help me to fix this?
Here is an example of how you can do it:
function makeUnique(arr) {
var uniques = [];
var itemsFound = {};
arr.sort((a, b) => a[0] + a[1] - (b[0] + b[1]))
for (var i = 0, l = arr.length; i < l; i++) {
if (!itemsFound[arr[i]] && !itemsFound[[arr[i][1], arr[i][1]]]) {
uniques.push(arr[i]);
itemsFound[arr[i]] = true;
itemsFound[[arr[i][1], arr[i][0]]] = true;
}
}
return uniques;
}
I hope it helps.
There are two parts
similar should be considered
among similar, one with smaller first key should stay
1. Similar should be considered
Here you can just make the key for hashmap in such a way that similar items produce same key.
One way to do that is sort the items in the tuple and then form the key, as there are two items only, first one will be min and second one will be max
let a = [
[5, 6],
[1,1],
[6,5],
[1,1],
[3,2],
[2,3]
]
function makeUnique(arr) {
var uniques = [];
var itemsFound = {};
for(var i = 0, l = arr.length; i < l; i++) {
let [a,b] = arr[i];
const hashKey = [ Math.min(a,b), Math.max(a,b)];
var stringified = JSON.stringify(hashKey);
if(itemsFound[stringified]) continue;
uniques.push(arr[i]);
itemsFound[stringified] = true;
}
return uniques;
}
let ans1=makeUnique(a)
console.log(ans1);
2. Among similar, the one with smaller first key should stay
Now you can remember in the hashmap what the value for a key was and keep updating it based on the correct candidate
let a = [
[5, 6],
[1,1],
[6,5],
[1,1],
[3,2],
[2,3]
]
function makeUniqueSmallerFirst(arr) {
var items = {};
for(var i = 0, l = arr.length; i < l; i++) {
let [a,b] = arr[i];
const hashKey = [ Math.min(a,b), Math.max(a,b)];
var stringified = JSON.stringify(hashKey);
if (stringified in items) {
let previous = items[stringified];
if (previous[0] > arr[i][0]) {
items[stringified] = arr[i];
}
} else {
items[stringified] = arr[i] // I am just storing the array because if I see a similar item next time, I can compare if that has first item smaller or not
}
}
return Object.values(items); // this doesn't guarantee output order though
// if you want order as well . you can iterate over input array once more and arrange the items in the preferred order.
}
let ans2=makeUniqueSmallerFirst(a)
console.log(ans2);
UPDATED (More simple and faster example for ES5+):
function makeUnique(arr) {
return new Set(a.map(
arr => JSON.stringify(arr.sort((a, b) => a - b)))
)
}
const m = makeUnique(a)
console.log(m) //
OLD:
This is an example of code that makes a two-dimensional array with arrays of any length unique.
let a = [
[5, 6],
[1, 1],
[6, 5],
[1, 5],
[3, 2],
[2, 3],
[6, 5, 3],
[3, 5, 6]
]
function isUnique(uniqueArray, checkedArray) {
let checked = [...checkedArray];
let unique = [...uniqueArray];
let uniqueValue = 0;
unique.forEach(value => {
if (checked.includes(value)) {
checked.splice(checked.indexOf(value), 1)
} else uniqueValue++;
})
return uniqueValue > 0;
}
function makeUnique(array2d) {
let unique = [array2d[0]]
array2d.forEach(checkedArray => {
if (unique.some(uniqueArray => {
if (checkedArray.length !== uniqueArray.length) return false;
return !isUnique(uniqueArray, checkedArray)
}
)) return 0;
else unique.push(checkedArray)
})
return unique
}
console.log(makeUnique(a)) // [ [ 5, 6 ], [ 1, 1 ], [ 1, 5 ], [ 3, 2 ], [ 6, 5, 3 ] ]
isUnique() this function checks if the numbers in both arrays are unique, and if they are, it outputs true. We use the copy through spread operator, so that when you delete a number from an array, the array from outside is not affected.
makeUnique() function makes the array unique, in the following way:
It checks if our unique two-dimensional array has at least one array that is identical to checkedArray
The first check if the arrays are of different lengths - they are unique, skip and check for uniqueness, if !isUnique gives out true, then the array is skipped by return 0
I have a tricky question, at least for me, today. First of all I want to present you the code example which I want to acomplish:
var numbers= [1,2,3,4];
var newNumbers = values.map((v1, v2) => {
return v1+v2;
});
console.log(newNumbers ); //[wanted output: 3, 7]
The idea behind that is that I can map over an array but not with one item like usualy, this time I want to do this with pairs. Also I want to handle this little problem incase I have an odd amount of numbers:
var numbers= [1,2,3,4,5];
var newNumbers = values.map((v1, v2) => {
return v1*v2; //when v1 is 5 and v2 out of range, treat v2 as 0
});
console.log(newNumbers ); //[wanted output: 2, 12, 0]
Is it possible to handle this in JS or does anybody have an idea how I could map over an array in this special way?
.map is only for when the input array and output array items are one-to-one. Since you're looking to go from 4 items to 2, you'll need a different method.
A plain for loop works.
const numbers = [1, 2, 3, 4];
const output = [];
for (let i = 0; i < numbers.length; i += 2) {
output.push(numbers[i] + (numbers[i + 1] || 0));
}
console.log(output);
The || 0 at the end there will work for when the last number isn't paired with another.
This is not extremely practical but a possible way to solve it using ES6 iterators:
function sumPairs(arr) {
const result = [];
const iterator = arr.values(); //1
for (const x of iterator) { //2
const [y = 0] = iterator; //3 and 4
result.push(x+y);
}
return result;
}
console.log(sumPairs([1, 2, 3, 4]));
console.log(sumPairs([1, 2, 3, 4, 5]));
Array#values() gets an iterator over the values of an array.
for..of will go through the iterator. That is how it works by spec - even if you use for (const x of arr) internally the for..of will get the default iterator of arr which is the same as .values().
Using array destructuring also works on iterators by spec. The name is actually a bit misleading - it looks like an array because of the [ and ] and you can use it on arrays like const [a, b, c] = someArray.
However destructuring assignment using square brackets will always draw from an iterator. If it is already given an iterator, it is the same as calling iterator.next().value. In the case of const [y] = iterator it would draw a second value after x. So, each iteration of the loop advances through two of the items of the array.
The destructuring assignment allows using some syntax sugar - in this case specifying default values. With [y = 0] the second item drawn from the array will have a default if it happens to be undefined. The for..of will stop once there are no more items the iterator can produce.
if the array had an even number of items, then getting a y will be the last item, so the next iteration the loop ends:
if the array has an odd number of items, then x will be the last item of the iterator and y will get a value from an already exhausted iterator and thus the last iteration will just use the default for y.
function pairs(arr) {
const iterator = arr.values();
for (const x of iterator) {
const [y = 0] = iterator;
console.log(x, y);
}
}
pairs([1, 2]);
console.log("-----");
pairs([1, 2, 3]);
See also: Iteration protocols on MDN
The above approach can be generalised with a generator function that accepts any iterable and returns pairs of items until it is exhausted.
function* pairs(iterable) {
const iterator = iterable[Symbol.iterator]();
for (const x of iterator) {
const [y] = iterator;
yield [x, y];
}
}
The sumPairs function then just needs to consume those pairs by summing them and then adding them to an array:
function* pairs(iterable) {
const iterator = iterable[Symbol.iterator]();
for (const x of iterator) {
const [y] = iterator;
yield [x, y];
}
}
function sumPairs(arr) {
const result = [];
for (const [x, y = 0] of pairs(arr)) {
result.push(x+y);
}
return result;
}
console.log(sumPairs([1, 2, 3, 4]));
console.log(sumPairs([1, 2, 3, 4, 5]));
With a generator in place, the sumPairs can furrther be condensed to just an Array.from(), since it accepts any iterable as a first parameter and a mapping function for a second parameter.
const sumPairs = arr =>
Array.from(pairs(arr), ([x, y = 0]) => x+y);
function* pairs(iterable) {
const iterator = iterable[Symbol.iterator]();
for (const x of iterator) {
const [y] = iterator;
yield [x, y];
}
}
const sumPairs = arr =>
Array.from(pairs(arr), ([x, y = 0]) => x+y);
console.log(sumPairs([1, 2, 3, 4]));
console.log(sumPairs([1, 2, 3, 4, 5]));
I think I'm misunderstanding something about variable scope in JavaScript. The goal is, given an array of numbers, to generate a 2D array that contains all the rotations of that array. So, the array [1,2,3,4] should yield a 2D array of:
[ [1,2,3,4],[2,3,4,1],[3,4,1,2],[2,3,4,1] ]
I'm coming from Ruby, where the following would work just fine:
row = [3,1,6,4];
function rotations(arr) {
var rotations = [];
var i = 0;
var k = arr.length;
while(i < k) {
arr.unshift(arr.pop());
rotations.push(arr);
i++;
};
return rotations;
};
console.log(rotations(row));
However, what this logs is a 2D array containing 4 iterations of the original array:
[ [ 3, 1, 6, 4 ], [ 3, 1, 6, 4 ], [ 3, 1, 6, 4 ], [ 3, 1, 6, 4 ] ]
So it appears that the original array row is not being modified in the scope of the function - only in the scope of the nested while loop.
You just need to copy your array before modifying it with unshift, because it modifies origin array
var row = [3,1,6,4];
function rotations(arr) {
var rotations = [arr];
var i = 1;
var k = arr.length;
var copiedArr = arr
while(i < k) {
copiedArr = [...copiedArr]
copiedArr.unshift(copiedArr.pop());
rotations.push(copiedArr);
i++;
};
return rotations;
};
console.log(rotations(row));
console.log(rotations([1,2,3,4]))
More about unshift is here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
Or if you need the first element also being rotated
var row = [3,1,6,4];
function rotations(arr) {
var rotations = [];
var i = 0;
var k = arr.length;
var copiedArr = arr
while(i < k) {
copiedArr = [...copiedArr]
copiedArr.unshift(copiedArr.pop());
rotations.push(copiedArr);
i++;
};
return rotations;
};
console.log(rotations(row));
console.log(rotations([1,2,3,4]))
function findMin(array) {
return Math.min.apply(Math, array);
}
function rearrange(matrix) {
let min = 0;
let newMatrix = [];
for (let i = 0; i < matrix.length; i++) {
let min = findMin(matrix[i]);
newMatrix[i].push(min);
}
return newMatrix;
}
Example input:
let matrix = [[2,7,1],
[0,2,0],
[1,3,1]]
rearrange(matrix);
Log:
Uncaught TypeError: Cannot read property 'push' of undefined
at reArrange (test.js:11)
at test.js:23
I'm trying to have the nested arrays sorted in an increasing order. If I didn't get it wrong, it doesn't happen because newMatrix[i] is not defined. But can't JS just create it and push the element? Do we need an extra step prior to doing this? Could you please suggest me another way, if this method won't work?
That's because you don't initialize the second dimension in your output Array. In JavaScript, if you haven't explicitly assigned to a certain element of an array, it evaluates to undefined, which obviously is neither an Array, nor an array like object and does not have a push() method.
The quickest solution to your problem should be declaring the inner arrays as well.
let newMatrix = [[], [], []];
A better, generic way would be to append an empty array to newMatrix every time you encounter a row that does not exist.
I also suspect that you algorithm is incorrect. Could you specify what exactly you intend to achieve by 'rearranging' the array? Because all your current code does is populate newMatrix with the minimum of each row. You're going to end up with [[1], [0], [1]] with the current fix. Is that intentional? Check your logic.
EDIT: Apparently, you're trying to rearrange the maxtix in such a way that the result contains each row in sorted order. Here's how to do that:
function rearrange(matrix) {
let min = 0;
let newMatrix = [];
for (let i = 0; i < matrix.length; i++) {
let sortedRow = matrix[i].sort((a, b) => a > b)
newMatrix.push(sortedRow);
}
return newMatrix;
}
console.log(rearrange([
[7, 6, 8],
[1, 9, 9],
[8, 5, 1]
]))
You need to make shure that matrix[i] is an array:
(newMatrix[i] || (newMatrix[i] = [])).push(min);
Or you set it to an array directly:
newMatrix[i] = [min];
You need to assign a new array before using Array#push
newMatrix[i] = [];
function findMin(array) {
return Math.min.apply(Math, array);
}
function rearrange(matrix) {
let min = 0;
let newMatrix = [];
for (let i = 0; i < matrix.length; i++) {
let min = findMin(matrix[i]);
newMatrix.push(min);
}
return newMatrix;
}
let matrix = [[2, 7, 1], [0, 2, 0], [1, 3, 1]];
console.log(rearrange(matrix));
.as-console-wrapper { max-height: 100% !important; top: 0; }
As an alternative solution, you could just map the result of findMin.
function findMin(array) {
return Math.min.apply(Math, array);
}
function rearrange(matrix) {
return matrix.map(findMin);
}
let matrix = [[2, 7, 1], [0, 2, 0], [1, 3, 1]];
console.log(rearrange(matrix));
Your code is not working because you miss to initialize the array before to push the min.
In your case you have newMatrix which is an empty array.
But you expect than that the array should have arrays inside:
So before this line:
newMatrix[i].push(min);
You could do:
newMatrix[i] = [];
But in this way what you are going to achieve is a new array with arrays, and each array inside has just the min of the input arrays.
If you want a new array ordered inside, you could do that in this way:
var result = [[5,3,1], [4,5,1]].reduce(function(a, b) {
a.push(b.sort());
return a;
}, []);
console.log(result);
Trying to create a function mCreate() that given a set a numbers returns a multidimensional array (matrix):
mCreate(2, 2, 2)
// [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]
When this functions handles just 2 levels of depth ie: mCreate(2, 2) //[[0, 0], [0, 0]] I know to do 2 levels, you can use 2 nested for loops but the problem I'm having is how to handle an n'th number of arguments.
Would this problem be better approached with recursion, otherwise how can I dynamically determine the number of nested for loops I'm going to need given the number of arguments?
ps: the most performant way would be great but not essential
RE-EDIT - After using Benchmark.js to check perf the results were as follows:
BenLesh x 82,043 ops/sec ±2.56% (83 runs sampled)
Phil-P x 205,852 ops/sec ±2.01% (81 runs sampled)
Brian x 252,508 ops/sec ±1.17% (89 runs sampled)
Rick-H x 287,988 ops/sec ±1.25% (82 runs sampled)
Rodney-R x 97,930 ops/sec ±1.67% (81 runs sampled)
Fastest is Rick-H
#briancavalier also came up with a good solution JSbin:
const mCreate = (...sizes) => (initialValue) => _mCreate(sizes, initialValue, sizes.length-1, 0)
const _mCreate = (sizes, initialValue, len, index) =>
Array.from({ length: sizes[index] }, () =>
index === len ? initialValue : _mCreate(sizes, initialValue, len, index+1))
mCreate(2, 2, 2)(0)
One simple recursive answer is this (in ES2015):
const mCreate = (...sizes) =>
Array.from({ length: sizes[0] }, () =>
sizes.length === 1 ? 0 : mCreate(...sizes.slice(1)));
JS Bin here
EDIT: I think I'd add the initializer in with a higher order function though:
const mCreate = (...sizes) => (initialValue) =>
Array.from({ length: sizes[0] }, () =>
sizes.length === 1 ? initialValue : mCreate(...sizes.slice(1))(initialValue));
Which could be used like:
mCreate(2, 2, 2)('hi');
// [[["hi", "hi"], ["hi", "hi"]], [["hi", "hi"], ["hi", "hi"]]]
JSBin of that
Here's a non-recursive solution:
function mCreate() {
var result = 0, i;
for(i = arguments.length - 1; i >= 0 ; i--) {
result = new Array(arguments[i]).fill(result);
}
return JSON.parse(JSON.stringify(result));
}
The JSON functions are used to mimic a deep clone, but that causes the function to be non-performant.
function mCreate() {
var result = 0, i;
for(i = arguments.length - 1; i >= 0 ; i--) {
result = new Array(arguments[i]).fill(result);
}
return JSON.parse(JSON.stringify(result));
}
console.log(JSON.stringify(mCreate(2, 2, 2)));
console.log(JSON.stringify(mCreate(1, 2, 3, 4)));
console.log(JSON.stringify(mCreate(5)));
console.log(JSON.stringify(mCreate(1, 5)));
console.log(JSON.stringify(mCreate(5, 1)));
var m = mCreate(1, 2, 3, 4);
m[0][1][1][3] = 4;
console.log(JSON.stringify(m));
Recursive algorithms may be easier to reason about, but generally they're not required. In this particular case the iterative approach is simple enough.
Your problem consists of two parts:
creating an array with variable number of 0-value elements
creating variable number of arrays of previously created arrays
Here's an implementation of what I think you're trying to create:
function nested() {
// handle the deepest level first, because we need to generate the zeros
var result = [];
for (var zeros = arguments[arguments.length - 1]; zeros > 0; zeros--) {
result.push(0);
}
// for every argument, walking backwards, we clone the
// previous result as often as requested by that argument
for (var i = arguments.length - 2; i >= 0; i--) {
var _clone = [];
for (var clones = arguments[i]; clones > 0; clones--) {
// result.slice() returns a shallow copy
_clone.push(result.slice(0));
}
result = _clone;
}
if (arguments.length > 2) {
// the shallowly copying the array works fine for 2 dimensions,
// but for higher dimensions, we need to compensate
return JSON.parse(JSON.stringify(result));
}
return result;
}
Since writing the algorithm is only half of the solution, here's the test to verify our function actually performs the way we want it to. We'd typically use one of the gazillion test runners (e.g. mocha or AVA). But since I don't know your setup (if any), we'll just do this manually:
var tests = [
{
// the arguments we want to pass to the function.
// translates to nested(2, 2)
input: [2, 2],
// the result we expect the function to return for
// the given input
output: [
[0, 0],
[0, 0]
]
},
{
input: [2, 3],
output: [
[0, 0, 0],
[0, 0, 0]
]
},
{
input: [3, 2],
output: [
[0, 0],
[0, 0],
[0, 0]
]
},
{
input: [3, 2, 1],
output: [
[
[0], [0]
],
[
[0], [0]
],
[
[0], [0]
]
]
},
];
tests.forEach(function(test) {
// execute the function with the input array as arguments
var result = nested.apply(null, test.input);
// verify the result is correct
var matches = JSON.stringify(result) === JSON.stringify(test.output);
if (!matches) {
console.error('failed input', test.input);
console.log('got', result, 'but expected', rest.output);
} else {
console.info('passed', test.input);
}
});
It's up to you to define and handle edge-cases, like nested(3, 0), nested(0, 4), nested(3, -1) or nested(-1, 2).
As suggested by #Pranav, you should use arguments object.
Recursion + arguments object
function mCreate() {
var args = arguments;
var result = [];
if (args.length > 1) {
for (var i = 1; i < args.length; i++) {
var new_args = Array.prototype.slice.call(args, 1);
result.push(mCreate.apply(this, new_args));
}
} else {
for (var i = 0; i < args[0]; i++) {
result.push(0)
}
}
return result;
}
function print(obj) {
document.write("<pre>" + JSON.stringify(obj, 0, 4) + "</pre>");
}
print(mCreate(2, 2, 2, 2))
The gist is to pass in the result of a create as the second argument of create except for the last (or the first depending on how you look at it) instance:
function create(n, v) {
let arr = Array(n || 0);
if (v !== undefined) arr.fill(v);
return arr;
}
create(2, create(2, 0)); // [[0,0],[0,0]]
create(2, create(2, create(2, 0))); // [[[0,0],[0,0]],[[0,0],[0,0]]]
DEMO
Using a loop we can build up array dimensions:
function loop(d, l) {
var out = create(d, 0);
for (var i = 0; i < l - 1; i++) {
out = create(d, out);
}
return out;
}
loop(2,2) // [[0,0],[0,0]]
loop(2,3) // [[[0,0],[0,0]],[[0,0],[0,0]]]
loop(1,3) // [[[0]]]
DEMO