Inconsistent native Array.sort() behavior in JS - javascript

I want to sort an array of integers by moving all elements with a value of 2 or greater to the end of the array. However, if I'm comparing two elements that are both 2 or greater, or that are both less than 2, then I want to keep them in their current order. I'm using the following comparison function with the native .sort():
function customSort(a, b) {
return (a >= 2 && b < 2) ? 1 : -1;
}
It seems to work as intended for the following cases:
[2, 3, 2, 0] => [0, 2, 3, 2]
[2, 3, 2, 0, 0, 0, 1, 0, 0, 0] => [0, 0, 0, 1, 0, 0, 0, 2, 3, 2]
But it looks like once I get past 10 elements, it seems to randomly order the elements that are less than 2, and the elements that are 2 or greater:
[2, 3, 2, 0, 0, 0, 1, 0, 0, 0, 0] => [0, 0, 0, 0, 0, 1, 0, 0, 3, 2, 2]
(expected result) => [0, 0, 0, 1, 0, 0, 0, 0, 2, 3, 2]
I assumed the -1 in the ternary operator would always keep the left element left and thus keep the order (as opposed to 0 which would understandably order them randomly). I'm aware there's a better way to sort without using the native .sort(), but I'm just curious about the behavior and if there's some way I could change the comparison function to get this working correctly with the native .sort().

A way to do it without sorting is to loop over it and append items greater that one to one array and other items to another and join them.
var arr = [2, 3, 2, 0, 0, 0, 1, 0, 0, 0, 0];
var shifted = [].concat.apply([],arr.reduce( function (arr, val, ind){
var ind = val > 1 ? 1 : 0;
arr[ind].push(val);
return arr;
},
[[],[]]));

I've done some console logging of what happens in the sort method and I've come to the conclusion that when the sort iterates over the array, it will reiterate over any elements that have moved position again with your comparison function. The fact that your conditional is a bit more specific, and in a large array makes it very hard to keep track of what's going on. I've used your first example and logged the result of the ternary operator and the array as it's being sorted. You get the following:
[2, 3, 2, 0]
-1
[2, 3, 2, 0]
-1
[2, 3, 2, 0]
1
[2, 3, 2, 2]
1
[2, 3, 3, 2]
1
[0, 2, 3, 2]
When you sort through [2, 3, 2, 0, 0, 0, 1, 0, 0, 0], it goes through 29 iterations. When you add that extra 0 at the end of the array, it goes through 58 iterations. During that extra 29 I'd imagine it'll shuffle the elements backwards and forwards using your conditionals. That may explain why you're not getting the results you expect.

Very similar to epascarello's answer, only it uses reduceRight to loop over the array backwards. Where a member >= 2 is encountered, it's spliced and pushed to the end of the array.
This should be efficient as the array is modified in place, no new array is created whereas epascarello's creates 6 additional arrays (including the result of concat).
var data = [2, 3, 2, 0, 0, 0, 1, 0, 0, 0, 0].reduceRight(function(acc, n, i, data) {
if (n >= 2) data.push(data.splice(i, 1));
return data;
}, null);
document.write(data); // [0, 0, 0, 1, 0, 0, 0, 0, 2, 3, 2]

Related

how to solve targetArrayInGivenOrder in JavaScript

Write a function that takes two arrays of integers (nums and index) and
returns a target array under the following rules:
Initially target array is empty.
From left to right read nums[i] and index[i], insert at index index[i] the
value nums[i] in target array.
Repeat the previous step until there are no elements to read in nums and index.
Example 1
Input: nums = [0, 1, 2, 3, 4], index = [0, 4, 1, 2, 3]
Output: [0, 4, 1, 2, 3]
Example 2
Input: nums = [1, 2, 3, 4, 0], index = [0, 1, 2, 3, 0]
Output: [1, 2, 3, 4, 1]
Your example does not match your description. According to your example, you want to insert the nums[index[i]] in your target array's i'th position.
You can do this using Javascript's array.map function like this -
const targetArray = index.map(i => nums[i]);
You may need to add necessary sanity checks as well depending on the contexts.
You can simply achieve this by using Array.forEach() along with Array.splice() method.
Live Demo :
const numsArr = [1, 2, 3, 4, 0];
const indexArr = [0, 1, 2, 3, 0];
let outputArr = [];
numsArr.forEach((elem, index) => {
outputArr.splice(index, 0, numsArr[indexArr[index]]);
});
console.log(outputArr);

How to move all elements that are zero to the end of an array?

Write a function that takes an array of values and moves all elements that are zero to the end of the array, otherwise preserving the order of the array. The zero elements must also maintain the order in which they occurred.
Zero elements are defined by either 0 or "0". Some tests may include elements that are not number literals.
NOT allowed to use any temporary arrays or objects. Also not allowed to use any Array.prototype or Object.prototype methods.So no array.push or splice() is allowed.
I tried this:
function removeZeros(nums) {
for(let i = nums.length - 1; i >= 0 ; i--){
if(nums[i] === 0){
nums.splice(i, 1);
nums.push(0);
}
}
return nums;
}
input:
[7, 2, 3, 0, 4, 6, 0, 0, 13, 0, 78, 0, 0, 19, 14]
Expected:
[7, 2, 3, 4, 6, 13, 78, 19, 14, 0, 0, 0, 0, 0, 0]
Actual:
[7, 2, 3, 4, 6, 13, 78, 19, 14, 0, 0, 0, 0, 0, 0]
Result is coming as correct but I need to do it without array.push() or array.splice()
Take length of original array
Filter nonZero values from original array
add zeros at the end as per difference in length of filtered array and original array
function removeZeros(nums) {
let originalLen = nums.length
let nonZero = nums.filter(Boolean)
return [...nonZero,...new Array(originalLen-nonZero.length).fill(0)]
}
console.log(removeZeros([7, 2, 3, 0, 4, 6, 0, 0, 13, 0, 78, 0, 0, 19, 14]))
Also not allowed to use any Array.prototype or Object.prototype
methods
function removeZeros(nums) {
let originalLen = nums.length
let final = []
for(let i=0;i<nums.length; i++){
if(nums[i]) final = [...final, nums[i]]
}
let lenDiff = originalLen-final.length
while(lenDiff){
final = [...final,0]
lenDiff--
}
return final
}
console.log(removeZeros([7, 2, 3, 0, 4, 6, 0, 0, 13, 0, 78, 0, 0, 19, 14]))
You could use sort:
const arr = [7, 2, 3, 0, 4, 6, 0, 0, 13, 0, 78, 0, 0, 19, 14]
arr.sort((a, b) => (a === 0) - (b === 0))
console.log(arr)
Subtracting booleans returns a 1, -1 or 0.
true - false === 1
false - true === -1
true - true === 0
If a is 0 and b isn't, 1 will be returned and a will be moved to the end of the array. If both a and b are zero or non-zero, they aren't moved relative to each other.
The simplest way to move certain items to one end is to collect the items to 2 separate arrays based on the condition and concatenate them in the end.
const arr = [7, 2, 3, 0, 4, 6, 0, 0, 13, 0, 78, 0, 0, 19, 14],
zeros = [],
nonZeros = []
for (const n of arr) {
if (n === 0)
zeros.push(n)
else
nonZeros.push(n)
}
const output = nonZeros.concat(zeros);
console.log(output)
Use a for loop, iterate through the array, and use a simple conditional to check zeroes.
var arr = [7, 2, 3, 0, 4, 6, 0, 0, 13, 0, 78, 0, 0, 19, 14];
var prev_zero_index = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] != 0 && arr[i - 1] == 0) {
prev_zero_index++;
arr[prev_zero_index] = arr[i];
arr[i] = 0;
}
}
function pushZeros(arr) {
let count = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== 0) arr[count++] = arr[i];
}
while (count < arr.length) arr[count++] = 0;
return arr;
}
Basically I am looping through all elements of array, picking nonzero element as I loop and storing it from 0th position in same array. After I am done looping, count will be equal to total nonzero elements in array. Rest all can be then initialized to zero.
You can simply do something like this:
let data = [7, 2, 3, 0, 4, 6, 0, 0, 13, 0, 78, 0, 0, 19, 14]
let result = data.sort((a,b) => !!b-!!a)
console.log(result)
Where you would sort on the boolean value of each of the array entries. We cast to boolean via the double negation operator. Since !!0 would be false and every other number would be true and we sort in a descending order (hence b-a and not a-b) we get the desired result.
Please note that Array.sort mutates the current array and if you want to create a new one simply use Array.slice like this:
let data = [7, 2, 3, 0, 4, 6, 0, 0, 13, 0, 78, 0, 0, 19, 14]
let sortZeroesLast = arr => arr.slice().sort((a,b) => !!b-!!a)
console.log(data) // <-- original array
console.log(sortZeroesLast(data)) // <-- new sorted array
You could use the sort() to sort the list based on its equality to 0, like this!
let numbers = [7, 2, 3, 0, 4, 6, 0, 0, 13, 0, 78, 0, 0, 19, 14];
numbers.sort((a, b) => {
if (b === 0) return -1;
return 1;
});
console.log(numbers); //[7, 2, 3, 4, 6, 13, 78, 19, 14, 0, 0, 0, 0, 0, 0]

Javascript: Search for an array in an array of arrays

I am looking at the best way to search for an instance of an array containing the elements of a given array, in an array of arrays.
Now, I understand that that's a confusing line. So here's an example to illustrate the scenario.
I have a search set which is an array with 9 items, representing a game board of 9 cells. The values can be 1, 0 or null:
var board = [1, 0, 1, 1, 0, 1, 0, 0, null];
I also have a result set, which is an array of arrays:
var winningCombos = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]]
Each array in winningCombo represents indices in the board array, that are winning combinations.
There are 8 winning combinations.
Each winning combination is a group of 3 indices, that would win, if their values are all 1.
i.e. to win, the board could be:
board = [1,1,1,0,0,0,null,null,0]; // Index 0,1, and 2 are 1, matching winningCombos[0]
or
board = [null,null,1,0,1,0,1,null,0]; // Index 2,4, and 6 are 1, matching winningCombos[7]
My question is:
What is the way in Javascript to perform this operation (maybe with ES6)?
What I have come up with so far is this:
const win = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
let board = [null,null,1,0,1,0,1,null,0];
let score = [];
board.forEach(function(cell, index)
{
if(cell === 1)
score.push(index);
});
console.log(score);
console.log(win.indexOf(score) > -1)
But I'm having a tough time finding the array in the array of arrays. Although the score is [2,4,6] and this exact array exists in win, it doesn't show up in the result, because of the way object equality works in Javascript I assume.
In a nutshell, I'm trying to see if score exists in win
I found this solution, but it seems quite hacky. Is there a better way to handle this?
You can use Array.prototype.some(), Array.prototype.every() to check each element of win, score
const win = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
let board = [null, null, 1, 0, 1, 0, 1, null, 0];
let score = [];
board.forEach(function(cell, index) {
if (cell === 1)
score.push(index);
});
console.log(score);
let bool = win.some(function(arr) {
return arr.every(function(prop, index) {
return score[index] === prop
})
});
console.log(bool);
Using ES6 you can map the win array to the actual values at each one of those locations:
const win = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
let board = [null,null,1,0,1,0,1,null,0];
let winning_spots = win.map((spots) => spots.map((i) => board[i]));
>>> winning_spots
[[null, null, 1], [0, 1, 0], [1, null, 0], [null, 0, 1], [null, 1, null], [1, 0, 0], [null, 1, 0], [1, 1, 1]]
Then we can filter by which ones have all of either 1's or 0's:
let one_winners = winning_spots.filter((spots) => spots.every((e) => e == 1));
let zero_winners = winning_spots.filter((spots) => spots.every((e) => e == 0));
>>> one_winners
[[1, 1, 1]]
>>> zero_winners
[]
Finally, if we want to find whether there is a winner, just check the lengths:
let is_winner = (one_winners.length + zero_winners.length) > 0

MapReduce algorithm to find continuous sequence

I need to write a small map/reduce function that should return 1 if there are at least 4 continuous 0 in an array, otherwise it should return something different than 1.
Example:
[6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1] => 1
[6, 7, 5, 0, 0, 0, 3, 4,0, 0, 0, 0, 0, 1] => 1
[5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1] => something # 1
This is my algorithm:
[6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
.map((i) => i === 0 ? 1 : 0)
.reduce((prev, i) => {
if (i !== 0) {
return prev + i;
} else {
if (prev >= 4) {
return 4;
}
return 0;
}
}, 0);
The .map method will mark 0 as 1 and non-0 as zero. So [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1] becomes [0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0]. Then the .reduce method will collect the number of zeroes.
If the current element is 1 (meaning zero in the input array) it returns the previous value plus the current value (1). This means it represents the number of zeroes. If the current item is 0 (meaning not 0 in the input array), it resets prev to 0 when it is less than 4, otherwise it carries on.
At the end, if the value is 4, it means that there are at least 4 continuous 0.
The algorithm seems to run, but not meet the requirement. It requires returning 1 if the input has 4 consecutive 0, otherwise it should return any non-zero number.
Do you think of any way to do this?
Here's an ES6 function that uses a 4-bit representation, where each 1-bit represents a non-zero value in the input array.
Only the most recent 4 bits are retained: each iteration the next bit is shifted in from the right, while the left most is dropped, always keeping 4 bits. If the outcome becomes zero at any time, the loop stops:
function has0000(arr) {
let c = 1;
return +!arr.every( n => c = (c*2+!!n) & 0xF );
}
console.log(has0000([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]));
console.log(has0000([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 1]));
Your code
Although the algorithm for counting zeroes is correct, the return value is not a 1 when needed, and vice versa.
The return value is whatever is the last returned value in the reduce callback, so it represents a count, which could be any non-negative number.
Even when there are 4 consecutive zeroes found, this count can start from zero again, and so you lose the knowledge that you actually had a match.
The main fix to this is to swap the if statements, and do the if (prev >= 4) first. That way you will keep the count at 4 once it reaches that, no matter what follows. It will never get reset to 0 anymore.
The second fix is to detect the final return is exactly 4 and transform that to a one. This must be done in a way that 4 is the only value that gets transformed to a 1, nothing else. This you could do by dividing by 4.
So without changing anything else, with minimal changes, this is the corrected code:
var res = [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
.map((i) => i === 0 ? 1 : 0)
.reduce((prev, i) => {
if (prev >= 4) {
return 4;
} else if (i !== 0) {
return prev + i;
} else {
return 0;
}
}, 0) / 4;
console.log(res);
The downside of sticking to only map and reduce is that you cannot exit before having gone through all input elements. This is a pity, because once you have found 4 zeroes, there really is no reason to keep going. The result is already final at that moment.
And here is your corrected code in a more condensed format, where I also left out the map as it is a trivial thing to incorporate in the reduce callback:
var res = [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
.reduce((prev, i) => prev >= 4 ? 4 : i ? 0 : prev + 1, 0)
/ 4;
console.log(res);
You could use Array#some and a this object for counting.
Array#some allows to end the iteration if the right count is found, as opposite of Array#reduce, which iterates all elements.
function isContinuous(array) {
return +array.some(function (a, i) {
if (a === 0) {
this.count++;
return this.count === 4;
}
this.count = 0;
}, { count: 0 });
}
console.log(isContinuous([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]));
console.log(isContinuous([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 1]));
console.log(isContinuous([5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1]));
In general, your implementation looks good to me, except the small nuance: you should specify -3 as an initial value for the reduce function (and modify its body accordingly in order to satisfy your requirements).
Below provided the modified version of your implementation, which returns 1 in case if there are present at least 4 consecutive zeroes, and otherwise returns one of the following values: -3, -2, -1 or 0
var result = [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
.map((i) => i === 0 ? 1 : 0)
.reduce((prev, i) => {
if (prev === 1) {
return 1;
}
if(i === 0) {
return -3;
}
return prev + (i === 1);
}, -3);
console.log(result)
Using .map and .reduce to meet your requirement:
//the following returns 1 if there is at least 4 consecutive 0's
//otherwise returns 0, 2, 4 or 6:
function cons4zeroes(arr) {
return arr
.map((i) => i ^ 0 ? 0 : 2)
.reduce((p, i) => p ^ 1 ?
i ^ 0 ?
(p + i) % 7 :
0 :
1,
0)
}
console.log(
cons4zeroes([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]), " => 1"
);
console.log(
cons4zeroes([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 1]), " => 1"
);
console.log(
cons4zeroes([5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1]), " => something # 1"
);
This solution builds on your logic, but maps each 0 to 2 instead of 1, reserving 1 for when 4 consecutive 0's have been found. Then, summing the consecutive 2's, resetting to 0 if a non-zero value is found before the 4th zero, taking the sum modulo 7 to convert the sum to 1 when 4 zeroes are found (4 × 2 = 1 mod 7).
Alternative without .map():
//the following returns 1 if there is at least 4 consecutive 0's
//otherwise returns 0, 2, 4 or 6:
function cons4zeroes(arr) {
return arr.reduce((p, i) => p ^ 1 ?
i ^ 0 ? 0 : (p + 2) % 7 : 1, 0)
}
console.log(
cons4zeroes([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]), " => 1"
);
console.log(
cons4zeroes([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 1]), " => 1"
);
console.log(
cons4zeroes([5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1]), " => something # 1"
);

javascript, forEach and removeChild unpredictable behavior

I wish to remove all items from a list and replace them with others
var list = document.querySelector("ul");
[].forEach.call(list.childNodes, list.removeChild.bind(list));
The code above does not work as expected, instead it removes only half the items (leaving every second item in the list).
If i change it to
var list = document.querySelector("ul");
[].slice.call(list.childNodes).forEach(list.removeChild.bind(list));
Then it works as expected,
can anyone explain ?
In the first one you are mutating an array you are iterating over.
In the second you are making a copy and then iterating over it.
The following is another option that doesn't require making a copy:
for(; list.firstChild; list.removeChild(list.firstChild));
This removes the firstChild while it is not null.
Concept
To explain the "unpredictable" behavior in the first scenario, consider this case:
var array = [0, 1, 2, 3, 4, 5, 6, 7];
This makes the behavior easier to explain without the distracting .call() and .bind() methods to wrap your head around.
array.forEach(function(num, index) {
console.log(num, index);
array.splice(index, 1);
});
You may be wondering why the output is:
0 0
2 1
4 2
6 3
But it's actually really simple. .forEach() iterates over the indices until i < array.length is no longer satisfied, while at the beginning of each iteration, your array looks like this:
[0, 1, 2, 3, 4, 5, 6, 7];
^
0
[1, 2, 3, 4, 5, 6, 7];
^
1
[1, 3, 4, 5, 6, 7];
^
2
[1, 3, 5, 6, 7];
^
3
[1, 3, 5, 7];
^
(4 < array.length) !== true
This is what happens when you manipulate an array being iterated over within a call to .forEach().
For the case where you execute [].slice.call(array), all you're doing is making a shallow copy of all the indices of the array. This allows you to iterate over the copy's indices while removing the nodes from the original.
Below is a comprehensive example, but make sure your browser supports ES6 template strings.
Demo
var array = [0, 1, 2, 3, 4, 5, 6, 7];
document.write(`<p>original.forEach()</p>`);
array.forEach(function(num, index) {
document.write(`<pre>num: ${num}, index: ${index}, array: [${array}]</pre>`);
array.splice(index, 1);
});
document.write(`<pre>result: [${array}]</pre>`);
array = [0, 1, 2, 3, 4, 5, 6, 7];
var copy = array.slice();
document.write(`<p>copy.forEach()</p>`);
copy.forEach(function(num, index) {
document.write(`<pre>num: ${num}, index: ${index}, array: [${array}]</pre>`);
array.splice(array.indexOf(num), 1); // removing by reference, not by index
});
document.write(`<pre>result: [${array}]</pre>`);
body > * {
padding: 0;
margin: 0;
}

Categories

Resources