Alternating values from two arrays into a new third array - javascript

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.

Related

summing multiple arrays using one index as key

I am importing data from multiple csv files, and the last one needs a little mapping and reducing.
Promise.all([
d3.csv("x.csv"),
d3.csv("y.csv"),
d3.csv("z.csv")
]).then(function(data) {
var alpha = data.map(row=>[row.key, row.item1, row.item2]);
var beta = data.filter(row=> (row.key=='x'))
.map(row=> [row.key, row.item1, row.item2]);
var gamma = data.filter(row=> (row.key=='x') ||
(row.key=='y'))
.map(row=>[row.key, row.item1, row.item2]);
});
Now I have to first convert all number strings to integers as well as perform a reduction:
I have multiple arrays in JavaScript:
[[x, 5, 4],
[x, 5, 6],
[y, 3, 9],
[y, 4, 6]]
which I want to reduce/sum based on index[0] as the key and using reduce function specifically.
So the result:
[[x,10,10],
[y,7,15]]
I am confused as to how do I select a particular index as a key in summation process.
.map(row=>[row.key, row.item1, row.item2]) //produces the array mentioned above
.reduce(row=>[row.item1, row.item2]); //to sum up every row?
I think my mistake is on the reduce part.
i hope this helps
var arr = [['a', 5, 4],
['a', 5, 6],
['b', 3, 9],
['b', 4, 6]];
const newArr = arr.reduce((acc, cur) => {
const prev = acc.find(elem => elem[0] === cur[0]);
if(prev) {
prev[1] += cur[1];
prev[2] += cur[2];
}
else {
acc.push(cur);
}
return acc;
}
, []);
console.log(newArr);

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

Comparing arrays of arrays with Lodash

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>

How to compare multiple arrays and return an array with unique values in javascript?

I would like to compare multiple array and finally have an array that contain all unique values from different array. I tried to:
1,Use the filter method to compare the difference between 2 arrays
2,Call a for loop to input the arrays into the filter method
and the code is as follows
function diffArray(arr1, arr2) {
function filterfunction (arr1, arr2) {
return arr1.filter(function(item) {
return arr2.indexOf(item) === -1;
});
}
return filterfunction (arr1,arr2).concat(filterfunction(arr2,arr1));
}
function extractArray() {
var args = Array.prototype.slice.call(arguments);
for (var i =0; i < args.length; i++) {
diffArray(args[i],args[i+1]);
}
}
extractArray([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3]);
However it does not work and return the error message "Cannot read property 'indexOf' of underfined" .... What's wrong with the logic and what should I change to make it works?
Many thanks for your help in advance!
Re: For all that mark this issue as duplicated ... what I am looking for, is a solution that can let me to put as many arrays as I want for input and reduce all the difference (e.g input 10000 arrays and return 1 array for unique value), but not only comparing 2 arrays .. The solutions that I have seen are always with 2 arrays only.
I don't use filters or anything of the sort but it will get the job done. I first create an empty array and concat the next array to it. Then I pass it to delete the duplicates and return the newly "filtered" array back for use.
function deleteDuplicates(a){
for(var i = a.length - 1; i >= 0; i--){
if(a.indexOf(a[i]) !== i){
a.splice(i, 1);
}
}
return a;
}
function extractArray() {
var args = Array.prototype.slice.call(arguments), arr = [];
for (var i = 0; i < args.length; i++) {
arr = deleteDuplicates(arr.concat(args[i]));
}
return arr;
}
var arr = extractArray([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3]);
console.log(arr) //[3, 2, 5, 1, 7, 4, 6]

Javascript comparing multiple arrays to get unique value across all

I have the following array:
var array = [
[1, 2, 3, 4, 5],
[2, 3],
[3, 4],
[3]
];
I'm trying to end up with a unique set of numbers from the arrays that appear in all arrays.
Therefore in this case returning
[3]
Any suggestions?
Many thanks :)
Store the value of array[0] in a variable (let's call it result).
Loop from array[1] to the end.
In this loop, run through all the values of result. If the current value of result is not in array[x] remove this value from result.
At the end of the loop, result only contains the desired values.
Aside from the obvious "iterate over every array and find matching numbers in every other array" you could flatten (concat) the original array, sort it, then look for numbers that occur at four consecutive indexes. I'm not a fan of questions where OP doesn't show any effort, but this was quite fun, so here it goes
array.reduce(function(prev, cur){
return prev.concat(cur);
})
.sort()
.filter(function(item, i, arr){
return arr[ i + array.length - 1 ] === item;
});
Or ES2015:
array.reduce((prev, cur)=>prev.concat(cur))
.sort()
.filter((i, idx, arr)=>(arr[idx+array.length-1]===i));
After learning i was using the wrong javascript method to remove from an array (pop) and some more tinkering. I got it working many thanks for those who responded.
var array = [
[2, 3, 5, 1],
[3, 4, 2, 1],
[3, 2],
[3, 4, 2, 5]
];
var result = array[0]
for (var i = 1; i < array.length; i++) {
for (var j = 0; j < result.length; j++) {
var arrayQuery = $.inArray(result[j], array[i]);
if(arrayQuery == -1){
result.splice(j, 1)
}
};
};
Try this:
var array = [
[1, 2, 3, 4, 5],
[2, 3],
[3, 4],
[3]
];
var arr = [];
for(var x in array){
for(var y in array[x]){
if(arr.indexOf(array[x][y]) === -1){
arr.push(array[x][y]);
}
}
}
console.log(arr);
Output:
[1, 2, 3, 4, 5]
Working Demo

Categories

Resources