Spread Syntax with Map and Filter - javascript

I found this interesting problem and wanted to share with you guys.
The question is :
[...[0,1,...[-1,0,1].map((x)=> x+1)].filter((x)=>x)),7]
I easily solved the first section upto the filter as [0,1,(-1+1),(0+1),(1+1)] = [0,1,0,1,2].
I was surprised to find the 7 hanging at the end. I thought it was some typo but copying the problem into the console gave me [1,1,2,7]. I couldn't quite understand 2 things.
why were the 0's left out of filter
what's the 7 doing there

The first operation here is Array#map [-1, 0, 1].map(x => x + 1) which basically adds 1 to each element, returning [0, 1, 2] array.
Next one is Array#filter operation, [0, 1, ...[0, 1, 2]].filter(x => x) which actually returns a new array, without every falsy value (false, 0, undefined, null, "") out of the array.
The last operation looks like [...[1, 1, 2], 7] and gets rid of the nested array with the spread operator returning [1, 1, 2, 7].

[...[0,1,...[-1,0,1].map((x)=> x+1)].filter((x)=>x),7] broken down:
[-1,0,1].map((x)=> x+1) // [0,1,2]
[0,1,...[-1,0,1].map((x)=> x+1)] // [0,1,0,1,2]
[0,1,...[-1,0,1].map((x)=> x+1)].filter((x)=>x) // [1,1,2]
[...[0,1,...[-1,0,1].map((x)=> x+1)].filter((x)=>x),7] // [1,1,2,7]

this part [-1,0,1].map((x)=> x+1) results in this list [0,1,2] then this part [0,1,...[-1,0,1].map((x)=> x+1)] results in [0,1,1,2] which after the filter part drops the 0 so it results into [1,1,2], finally the last element of the list is 7. So, altogether the result is [1,1,2,7]

The code evaluates in the following steps:
[...[0, 1, ...[-1, 0, 1].map((x)=>x+1)].filter((x)=>x)), 7] // map
[...[0, 1, ...[(x=>x+1)(-1), (x=>x+1)(0), (x=>x+1)(1)]].filter((x)=>x)), 7] // function application
[...[0, 1, ...[0, 1, 2]].filter((x)=>x)), 7] // spread
[...[0, 1, 0, 1, 2].filter((x)=>x)), 7] // filter
[...[...(x=>x)(0)?[0]:[], ...(x=>x)(1)?[1]:[], ...(x=>x)(0)?[0]:[], ...(x=>x)(1)?[1]:[], ...(x=>x)(2)?[2]:[]], 7] // function application
[...[...0?[0]:[], ...1?[1]:[], ...0?[0]:[], ...1?[1]:[], ...2?[2]:[]], 7] // conditional
[...[...[], ...[1], ...[], ...[1], ...[2]], 7] // spread (from filter)
[...[1, 1, 2], 7] // spread
[1, 1, 2, 7]

Related

Why .splice() method deletes elements of different indexes?

This is my first question on stackoverflow, I am new :) learning JS. I have a question. I wrote this function:
function inverseSlice(items, a, b) {
return items.splice(a, b);
}
inverseSlice([1, 2, 3, 4, 5, 6], 2, 4)
(4) [3, 4, 5, 6]
Why this function returns last 4 digits, when according to docs on MDN (which I read 10 times already :P) splice() method should remove here only 2 middle ones (3, 4)? It should return [1, 2, 5, 6]. Am I right? Thank You for all Your help :)
It's doing exactly what it advertises, it "returns an array containing the deleted elements."
function inverseSlice(items, a, b) {
return items.splice(a, b);
}
let array = [1, 2, 3, 4, 5, 6, 7, 8];
// Deletes 4 entries starting at index 2,
// or in other words [3,4,5,6] are snipped
inverseSlice(array, 2, 4);
console.log(array);
Unless you keep a reference to the array you're passing in you'll never observe anything about how it ends up, you'll only get the deleted elements.
splice will
Mutate the original array: remove N items, where N is the third parameter, starting from the start index (first parameter) to the number specified (so here, it'll remove indicies 2 through 5 from the array; indicies 2, 3, 4, and 5, a total of 4 get removed)
Return the removed elements - so, here, that's [3, 4, 5, 6].
The original array is now [1, 2], but you're logging what was returned by .splice, not the original array.
If you wanted [1, 2, 5, 6], you'd want to specify 2 for the 3rd argument (2 items to remove), and then log the original array:
function inverseSlice(items, a, b) {
return items.splice(a, b);
}
const arr = [1, 2, 3, 4, 5, 6];
const removedItems = inverseSlice(arr, 2, 2);
console.log(arr);
console.log(removedItems);
What you are confused about is the arguments to splice, The two arguments that you pass to splice are not the start and end index but the start index and the count of items to be deleted.
Hence in your example it deleted items from 2 to 5 index and returned you the resultant array i.e [3, 4, 5, 6]
As per the docs:
Syntax:
let arrDeletedItems = arr.splice(start[, deleteCount[, item1[, item2[, ...]]]])
Parameters
Start : The index at which to start changing the array.
deleteCount: n integer indicating the number of elements in the array to remove from start.
item1, item2, ... : The elements to add to the array, beginning from start. If you do not specify any elements, splice() will only remove
elements from the array.

Splitting and Adding Arrays

Trying to remove the last two elements then add a 2 to the end of the array. But, keep getting an error. First test works then my second fails.
var userArray = [4, 2, 8, 5, 0, 1, 6]; // Tests may use different array values
/* Your solution goes here */
userArray.splice(5, 2, 2);
CORRECT: Testing the final value of userArray when the initial array is [4, 2, 8, 5, 0, 1, 6]
Yours
4,2,8,5,0,2
INCORRECT: Testing the final value of userArray when the initial array is [-5, 3]
Yours and expected differ. See highlights below.
Yours
-5,3,2
Expected
2
// Tests may use different array values
Your answer should be: userArray.splice(userArray.length-2, 2, 2);
var arr = [1, 2, 3, 4, 5, 3, 1]
arr.splice(-2, 2, 2)
console.log(arr)
tried this and workd.. had to ensure the array had more than 2 elements first..
function trimLast2elements(ar){
if(ar.length > 2){
index = ar.length - 2 //get first index of last 2 elements..
ar.splice(index, 2, 2);
return ar; //return array.
}else{
//function if array count is less
}
}
apply function when needed.

JavaScript ensure all numbers are unique and if not plus one (or more)

I'm trying to place some labels on a d3 chart.
I have a DATA object with a DATA.rowOffset value for each. Basically my code calculates the DATA.rowOffset and sets it like this: d.rowOffset = Math.floor(d.total/heightSquares); but sometimes the rowOffset is the same and so the labels render on top of each other.
I need to cycle through this DATA and check for duplicates and then just +1 any duplicates.
I tried a method that looked at the previous .rowOffset and then added 1 to the current rowOffset, but that doesn't work if there are more than 2 duplicates.
I'm sure there's an easier way.... perhaps.
Edit: here's some code I tried mainly if (d.rowOffset === DATA[i-1].rowOffset) d.rowOffset++; so it checks the previous row offset. I think I need to cycle through all the data and then restart the cycle if a duplicate is found.
DATA.forEach(function(d, i) {
d.amt = +d.amt;
d.units = Math.floor(d.amt / squareValue);
sumTotal = sumTotal + d.units;
d.total = sumTotal;
d.rowOffset = Math.floor(d.total / heightSquares);
if (i > 0) {
console.log(DATA[i - 1].rowOffset);
if (d.rowOffset === DATA[i - 1].rowOffset) d.rowOffset++;
}
Here's one approach you can take.
You initialize an empty Set data structure to keep track of unique values you've encountered so far. You iterate through the array and for each value do the following:
If the value was previously encountered, increment it until it doesn't match any previously encountered values
Update the encountered values to include this new value
Replace the old value with the new value
Here's how that would look in code:
function incrementDups(arr) {
// a set of encountered unique values
const encounters = new Set();
// increment each duplicate until it has no duplicates
return arr.map(num => {
while (encounters.has(num)) {
num += 1;
}
encounters.add(num);
return num;
});
}
console.log(
incrementDups(
[1, 2, 2, 3, 2, 2, 4] // [1, 2, 3, 4, 5, 6, 7]
)
);
console.log(
incrementDups(
[1, 1, 1, 1, 1, 1, 1] // [1, 2, 3, 4, 5, 6, 7]
)
);
console.log(
incrementDups(
[1, 99, 55, 4, 55, 2] // [1, 99, 55, 4, 56, 2]
)
);
The solution above has quadratic worst-case time complexity. The input that generates this situation is an array containing only duplicates, for example [1, 1, 1, 1], where the last iteration of the nested while loop will run N increments. Despite that, on average, this algorithm should perform quite well.
A further optimization could be made by using more space to remember the last increment value for a certain duplicate and use that as the start value for incrementing, rather than the number itself.
Right now, the code above actually does a fair amount of repetition. If we had [2, 2, 2, ...], for every 2 we would start incrementing from through 2, 3, 4, etc. even though technically the previous 2 already did our work for us. Ideally, we want the first 2 to start counting from 2, the second 2 to start counting from 3, etc. This will be particularly useful for large arrays of consecutive values. For example, if we had [1, 2, ... 99, ... 2, 2, 2, 2 /* 100 times */], using the first algorithm, each 2 would count from 2 to 99 plus how ever many increments to the next unique value. On the other hand, using this new approach only the first 2 would do this. The next 2 would just increment 99 to 100, the next one 100 to 101, and so on. If we were given an array of only duplicates as before, [1, 1, 1 ...], each 1 would only need to get incremented once now rather than going through the entire range.
This tightens the time complexity to O(N*max(array)) which is still quadratic but only depends on the range of values, not the number of duplicates like before. It is also more optimized for your particular situation since you will expect an array of low numbers that are close to each other in value.
To keep track of this info, we can use a Map of a number to the latest unique value it was incremented to.
function incrementDups(arr) {
// a set of encountered unique values
const encounters = new Set();
// a map of the last unique non-duplicate for each value
const lastNonDup = new Map();
// increment each duplicate until it has no duplicates
return arr.map(num => {
let updatedNum = lastNonDup.has(num) ? lastNonDup.get(num) : num;
while (encounters.has(updatedNum)) {
updatedNum += 1;
}
encounters.add(updatedNum);
lastNonDup.set(num, updatedNum);
return updatedNum;
});
}
console.log(
incrementDups(
[1, 2, 2, 3, 2, 2, 4] // [1, 2, 3, 4, 5, 6, 7]
)
);
console.log(
incrementDups(
[1, 1, 1, 1, 1, 1, 1] // [1, 2, 3, 4, 5, 6, 7]
)
);
console.log(
incrementDups(
[1, 99, 55, 4, 55, 2] // [1, 99, 55, 4, 56, 2]
)
);

How come I can't use the length number from this array?

I created an array, and when I try to get the length of the array it works fine.
var map = [
[3, 0, 0, 2],
[7, 6, 6, 8],
[7, 6, 6, 8],
[5, 1, 1, 4]
];
var i = map.length;
i outputs 4.
When I try to use the i variable to get the column using var j = map[i].length; the console returns "map[i] is undefined". How come this won't work, but replacing i with an actual number works?
Here is an example jsfiddle, just uncomment line 11.
i is equal to 4, as you said. JS array indices start from 0, so the last element in your array is map[3] which means there is no element at map[4]
You need to do map[i-1] - this code should work:
var j = map[i-1].length;
And here is it working in your jsfiddle: https://jsfiddle.net/zk7f8Ls2/2/
Because table index are zero-based. The table length is 4 but indexes are 0, 1, 2 and 3. When you try to access index 4, you will get an error.
It's because i is 4, and remember that arrays start with 0 if you want to see the last item of the array just add -1 map[i-1]

initialize all the values in a javascript array to 0 without iteration

is there any way to make javascript array to initialize all the values to 0 without iteration like as shown below
var array = [1, 2, 3, 4, 5];
to
[0, 0, 0, 0, 0]
You could, in compliant browsers, use Array.prototype.fill():
var array = [1, 2, 3, 4, 5];
array.fill(0); // [0, 0, 0, 0, 0]
References:
Array.prototype.fill().
Array.apply(null, new Array(5)).map(Number.prototype.valueOf, 0))
Useful article Initializing arrays
Its a bit tricky. But it works
var array = [1, 2, 3, 4, 5];
array = JSON.parse(JSON.stringify(array).replace(/(\d+)/g,0)); // Returns [0,0,0,0,0]
i guess you don't need eval if you use JSON.parse() to build the empties and splice() to mutate the existing array instead of just making a new array full of zeros:
var r=[1, 2, 3, 4, 5];
[].splice.apply(r,
[0, r.length].concat(
JSON.parse("[0"+new Array(r.length).join(",0")+"]")
));
alert(r); // shows: "0,0,0,0,0"
Answers based on map()/fill() will not affect the orig array as desired, but those solutions could use splice like the above answer to do so, the only difference then is how one build the zero-filled array.
EDIT: kudos to Gilsha, i was working on an eval-based answer when you reminded me that JSON would be enough.

Categories