Grouping Numbers JS algorithm - javascript

I was trying to solve a problem:
Problem:
Given an array of Positive repetitive numbers. Output
should give an array with odds sorted on the right and evens on the
left (no particular order)
Input : [4,1,2,3,4]
Output: [4,2,3,1]
Solve it In-place and without using extra space and O(N) runtime.
Code:
/*
* Algorithm is simple, have two pointers one on the left and another on the right.
* Note: We are sorting all evens on the left and odds on the right
* If you see even on left, move on else swap.
*/
function groupNumbers(intArr) {
if(intArr.length == 0 || intArr.length == 1){
return intArr;
}
for(let i=0, j =intArr.length-1; i<intArr.length; i++){
if(j>=i){ //elements should not overlap
let start = intArr[i];
let end = intArr[j];
if(start%2 == 0){ //Even
i++;
} else {
[start, end] = [end, start]; //swap
}
if(end%2 == 1){
j--;
} else {
[start, end] = [end, start]; //swap
}
} //if-ends
}//for-ends
return intArr;
}
I'm not sure where I'm going wrong. I'm missing something. I'm getting the same sorted array as output.
Condition: **SOLVE it INPLACE and without using extra space ** (Preferably in ONE iteration)

I'm not sure where I'm going wrong. I'm missing something. I'm getting the same sorted array as output.
several things:
let start = intArr[i];
let end = intArr[j];
...
[start, end] = [end, start];
this does indeed swap the values in the variables start and end, but not the indices in the Array.
Then you have two i++ in the same loop that increment the left pointer.
if(start%2 == 0){ //Even
i++;
} else {
[start, end] = [end, start]; //swap
}
here you swap the items when the left pointer points to an odd value, but there's no check that the right pointer also points to an even value. you might as well just swap two odd values here. Same for the right pointer.
const isEven = v => (v&1) === 0;
const isOdd = v => (v&1) === 1;
function groupNumbers(arr){
var left = 0, right = arr.length-1;
while(left < right){
//move the left pointer to find the next odd value on the left
while(left < right && isEven(arr[left])) ++left;
//move the right pointer to find the next even value on the right
while(left < right && isOdd(arr[right])) --right;
//checking that the two pointer didn't pass each other
if(left < right) {
console.log("swapping %i and %i", arr[left], arr[right]);
//at this point I know for sure that I have an odd value at the left pointer
//and an even value at the right pointer
//swap the items
var tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
}
return arr;
}
[
[1,2,3,4],
[1,2,3,4,5,6,7,8,9,0],
[1,3,5,7],
[2,4,1,3],
[5,4,3,2,1],
].forEach(sequence => {
console.log("\ninput: " + sequence);
console.log("output: " + groupNumbers(sequence));
});
.as-console-wrapper{top:0;max-height:100%!important}
as suggested by #JaredSmith, the same thing just using a sort-function :)
function sortEvenLeftOddRight(a,b){
return (a&1) - (b&1);
//return (a&1) - (b&1) || a-b; //to additionally sort by value
}
[
[1,2,3,4],
[1,2,3,4,5,6,7,8,9,0],
[1,3,5,7],
[2,4,1,3],
[5,4,3,2,1],
].forEach(sequence => {
console.log("\ninput: " + sequence);
sequence.sort(sortEvenLeftOddRight);
console.log("output: " + sequence);
});
.as-console-wrapper{top:0;max-height:100%!important}

A very concise method to solve this would be to use reduce:
const out = arr.reduce((p, c) => {
// if the value is divisible by 2 add it
// to the start of the array, otherwise push it to the end
c % 2 === 0 ? p.unshift(c) : p.push(c)
return p;
}, []);
OUT
[4,2,4,1,3]
DEMO

Related

Next Greatest Alphabet in an Array [Using Binary Search Algorithm]

I am trying to solve a question on Leetcode
Find Smallest Letter Greater Than Target
Given a characters array letters that is sorted in non-decreasing order and a character target, return the smallest character in the array that is larger than target.
Note that the letters wrap around.
For example, if target == 'z' and letters == ['a', 'b'], the answer is 'a'.
Example 1:
Input: letters = ["c","f","j"], target = "a"
Output: "c"
Example 2:
Input: letters = ["c","f","j"], target = "c"
Output: "f"
Example 3:
Input: letters = ["c","f","j"], target = "d"
Output: "f"
Constraints:
2 <= letters.length <= 104
letters[i] is a lowercase English letter.
letters is sorted in non-decreasing order.
letters contains at least two different characters.
target is a lowercase English letter.
My solution until now looks something like this:
function nextGreatestAlphabet(letters, target){
let left = 0;
let right = letters.length - 1;
let res = -1;
while(left <= right) {
let mid = Math.floor(left + (right - left) / 2);
if (letters[mid] == target ) { console.log(1)
res = letters[mid];
left = mid + 1;
}
if(letters[mid] > target){ console.log(2)
right = mid -1;
res = letters[mid];
}
if(letters[mid] < target ){ console.log(3)
left = mid + 1;
}
}
return res;
}
console.log(nextGreatestAlphabet(["c","f","j"],"c")); //c
But the correct result should be "f" as c is already present here and next greatest is f
You should avoid setting res during the search, and certainly not set it when letters[mid] == target, as it is sure that is not the correct answer. Instead you should get the result after the loop has exited.
This then also means you don't need a separate case for letters[mid] == target, as the rest of the action is exactly what is done for letters[mid] < target. So you can make this a quite simple if (letters[mid] > target) ... else ... structure.
After the loop has exited, the left index points to the desired character. Then you need to deal with the boundary case where that index points beyond the array and map it to 0. This you can do with the remainder operator:
NB: I use here the name of the function as required by the LeetCode challenge:
function nextGreatestLetter(letters, target) {
let left = 0;
let right = letters.length - 1;
while(left <= right) {
let mid = Math.floor(left + (right - left) / 2);
if(letters[mid] > target) {
right = mid -1;
} else {
left = mid + 1;
}
}
return letters[left % letters.length];
}
Personally, I prefer working with right being the index just after the range that is under consideration, and for deriving mid you can use the >> operator (Array sizes are limited in this challenge, so that is a safe operation):
function nextGreatestLetter(letters, target) {
let left = 0;
let right = letters.length;
while (left < right) {
let mid = (left + right) >> 1;
if (letters[mid] > target) {
right = mid;
} else {
left = mid + 1;
}
}
return letters[left % letters.length];
}
I think that your
if (letters[mid] == target ) { console.log(1)
res = letters[mid];
left = mid + 1;
}
sets the result to the target but you actually want to have the next one, so it should be sth. like
res=letter[mid+1].
if
mid==letters.length - 1
holds then it is already the right end. then you can just put
res = letter[0];

Connect 4 diagonal logic via matrix

I'm wanting to add a horizontal check for a winning move in my connect 4 game - However I'm completely flumoxed with how to proceed.
There are already working checks for horizontal and vertical.
You can see that we are checking with a regex test against a stringified version of the array matrix.
A winning vertical array looks like this:
Array and joined array passed to the test function:
A winning horiontal array looks like this:
Array and joined array passed to the horizontal test function:
A winning diagonal array looks like this:
const testWin = (arr: number[]): boolean => /1{4}|2{4}/.test(arr.join(""));
const usePlayPiece = () => {
const [board, setBoard] = useState(boardState);
const [player, setPlayerTurn] = useState(playerState);
const [gameOver, setGameOver] = useState(gameOverState);
return (col: number) => {
// Play piece (non mutating)
const newBoard = board.map((column, i) => (i === col) ? [...column, player] : column);
const row = newBoard[col].length - 1;
if (
testWin(newBoard[col]) // Did win vertically
|| testWin(newBoard.map((col) => col[row] || 0)) // Did win horizontally
// || testWin() // Did win diagonally
) {
setGameOver(true);
} else {
// Keep playing
}
setBoard(newBoard);
};
};
You could temporarily pad your columns with zeroes so they are equally sized (typically to length 6). Then turn that 2D array to a string, where the columns are separated with an extra character, and then use a one-for-all regular expression on it:
const testWin = (arr: number[][]): boolean => =>
/([12])(\1{3}|(.{5}\1){3}|(.{6}\1){3}|(.{7}\1){3})/.test(
arr.map(col => col.join("").padEnd(6, "0")).join(",")
);
And just have one call where you pass the complete board as argument:
if (testWin(newBoard)) {
// game over...
}
Keeping in with the string version of your array, you need to test the full matrix through so you will need to pad out numbers after the last entry in all columns. This will allow you to test for a consistent pattern.
Using a 6 * 7 array you would end up with 6 numbers between each number you are testing for. The regex then will look like
1\d{6}1\d{6}1\d{6}1|2\d{6}2\d{6}2\d{6}2
For the opposite direction you will swap the {6} for {4}.
This can be simplified to 1(\d{6}1){3}|2(\d{6}2){3} and 1(\d{4}1){3}|2(\d{4}2){3}
Apart from operating on strings (which I think might be faster for very large matrices), the problem can be generalized on m x n matrices:
function genMtrx (n,m, probEmp = 0.8){
return Array.from({length:n}).map(d =>
Array.from({length:m}).map(d =>
Math.random() < probEmp ? 0 : Math.random() < 0.5 ? 1 : 2
)
)
}
Normally there are 8 directions you can go (l,r,t,b,tr,tl,bl,br), but since you are testing every cell until you hit a winning condition, you would need 4 of the directions like the others posted here:
const dirs = {
right: (i,j) => [i+1, j],
top: (i,j) => [i, j+1],
topleft: (i,j) => [i-1,j+1],
topright: (i,j) => [i+1,j+1]
}
Then based on these dirs, you can generalize a winning condition:
function isWinning(mtrx, nr = 1){
let streak = 0,
nCol = mtrx.length,
nRow = mtrx[0].length,
dirKeys = Object.keys(dirs),
dirKeysL = dirKeys.length;
for (let i = 0; i < nCol; ++i){
for (let j = 0; j < nRow; ++j) {
for (let k = 0; k < dirKeysL; ++k) {
let dir = dirs[dirKeys[k]],
[nI, nJ] = dir(i,j),
cell = mtrx[nI]?.[nJ];
while (cell && cell === nr) {
if(++streak === 4) {
return true;
}
[nI, nJ] = dir(nI, nJ);
cell = mtrx[nI]?.[nJ];
}
streak = 0;
}
}
}
return false;
}
The function default tests for opponent1, but if you pass 2, it will test that one instead. Then you can do (for opponent 2):
isWinning(genMtrx(100,100,0.9),2)
I am sure the algorithm can be optimized much further. I also think if the matrix is large enough, it is better to pick random cells or do a 2D binary search. In that case you will have to put back the omitted 4 directions into the dirs object.

Not sure what I am doing wrong in binary search

I have a binary search and I tried to paper trace it, in my account it should return the value as true in the second iteration of the while loop, only the base case is running in console.log , I have no idea what I am doing wrong here, would love some pointers
const binarySearch = (sortedArray,value ) =>{
let left =0
let right = sortedArray.length-1
let middle =left+Math.floor((right-left ) /2)
// console.log(`
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// left ==${left}
// right ==${right}
// middle==${middle}
// is value === middle??? ${value === sortedArray[middle]}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// `)
while(left<right){
if(sortedArray[middle] === value){
return middle
}
if(sortedArray[middle] < value){
left = middle+1
}
if(sortedArray[middle] > value){
right = middle-1
}
}
return -1
}
binarySearch([1,2,3,4,5], 4)
edit:I found the bug! I thought for some strange reason that the while loop would use the updated middle value, but there is no reason for it to, as javascript just keeps going on the call stack unless specifically I modify some value. Then I had another issue where I was not accounting if left or right could be the value, it seems to work now, would love some tips for improvement or advice, thanks.
const binarySearch = (sortedArray,value ) =>{
let left =0
let right = sortedArray.length-1
while(left<=right){
let middle =left+Math.floor((right-left ) /2)
if(sortedArray[middle] === value){
return middle
}
if(sortedArray[middle] < value){
left = middle+1
}
if(sortedArray[middle] > value){
right = middle-1
}
}
return -1
}
binarySearch([1,2,3,4,5], 1)
So the main issue I see over here is that you're not updating your middle pointer every time you're updating the left and right pointers... You have to figure out what's the new middle pointer of either of the sub arrays:
const binarySearch = (sortedArray, value) => {
let left = 0;
let right = sortedArray.length - 1;
let middle = left + Math.floor((right - left) / 2);
while (left <= right) {
// The part you're missing
middle = left + Math.floor((right - left) / 2);
if (sortedArray[middle] === value) {
return middle;
}
if (sortedArray[middle] < value) {
left = middle + 1;
}
if (sortedArray[middle] > value) {
right = middle - 1;
}
}
return -1;
};
console.log(binarySearch([1, 2, 3, 4, 5], 4));

javascript hackerranks sherlock and array performance issue

Watson gives Sherlock an array A of length N. Then he asks him to
determine if there exists an element in the array such that the sum of
the elements on its left is equal to the sum of the elements on its
right. If there are no elements to the left/right, then the sum is
considered to be zero. Formally, find an i, such that,
Input Format
The first line contains T, the number of test cases. For each test
case, the first line contains N, the number of elements in the array
A. The second line for each test case contains N space-separated
integers, denoting the array A.
Constraints
1<=T<=10
1<=N<=10^5
1<=Ai<=2*10^4
1<=i<=N
Output Format
For each test case print YES if there exists an element in the array,
such that the sum of the elements on its left is equal to the sum of
the elements on its right; otherwise print NO.
Sample Input
2
3
1 2 3
4
1 2 3 3
Sample Output
NO
YES
Explanation
For the first test case, no such index exists. For the second test
case,
therefore index 3 satisfies the given conditions.
I'm having timeout issues on 3 of the test cases
function check(input) {
var result = "NO";
var sum=0;
input.map(function(data){
sum=sum+(+data);
})
sumLeft=0;
sumRight=sum-(+input[0]);
for(var i=1;i<input.length;i++){
sumLeft=sumLeft+(+input[i-1]);
sumRight=sumRight-(+input[i])
if(sumLeft==sumRight)
{
console.log("YES");
return;
}
}
console.log("NO");
}
function processData(input) {
//Enter your code here
var lines = input.split("\r\n");
for (var m = 2; m < lines.length; m = m + 2) {
check(lines[m].split(" "));
}
}
process.stdin.resume();
process.stdin.setEncoding("ascii");
_input = "";
process.stdin.on("data", function(input) {
_input += input;
});
process.stdin.on("end", function() {
processData(_input);
});
Loop over the array once to find the sum. Declare two variables: sumLeft and sumRight. sumLeft should have an initial value of 0 and sumRight should be totalSum-arr[0].
Iterate over the array again and increment sumLeft by the (n-1) element and decrement sumRight by the nth element. Keep comparing the two variables to check if they equal each other. You cut your time complexity down to O(n)
The below code passed the test on https://www.hackerrank.com/challenges/sherlock-and-array . The tricky part was setting up default responses for when the array length was 1. I will admit that #trincot 's answer was more efficient (n as opposed to 2n) for arrays containing only positive integers.
function check(input) {
var result = "NO";
var sum=0;
if(input.length == 1){
console.log("YES");
return;
}
input.map(function(data){
sum=sum+(+data);
})
sumLeft=0;
sumRight=sum-(+input[0]);
for(var i=1;i<input.length-1;i++){
sumLeft=sumLeft+(+input[i-1]);
sumRight=sumRight-(+input[i])
if(sumLeft==sumRight)
{
console.log("YES");
return;
}else if (sumLeft>sumRight) { ///worked both with and without this optimization
console.log("NO");
return;
}
}
console.log("NO");
}
function processData(input) {
//var lines = input.split("\r\n");
var lines = input.split(/\r|\n/)
for (var m = 2; m < lines.length; m = m + 2) {
check(lines[m].split(" "));
}
}
process.stdin.resume();
process.stdin.setEncoding("ascii");
_input = "";
process.stdin.on("data", function(input) {
_input += input;
});
process.stdin.on("end", function() {
processData(_input);
});
You could go through the array from both ends in inwards direction using two pointers (indices). Keep a balance, starting with 0, as follows:
When the balance is negative move the left pointer one step to the right while increasing the balance with the value you leave behind. When the balance is positive, move the right pointer one step to the left while decreasing the balance with the value you leave behind.
When the two pointers meet each other, check the balance. If it is zero, you have success.
Here is the algorithm in ES6 code, together with a text area where you can adapt the input according to the required input format:
function hasMiddle(a) {
var balance = 0, i = 0, j = a.length-1;
while (i < j) balance += balance > 0 ? -a[j--] : a[i++];
return !balance;
}
// I/O: event handling, parsing input, formatting output
var input = document.querySelector('textarea');
var output = document.querySelector('pre');
input.oninput = function() {
var lines = this.value.trim().split(/[\r\n]+/).filter(s => s.trim().length);
// Strip the case count and array element counts:
lines = lines.slice(1).filter( (s, i) => i % 2 );
// Call function for each test case, returning array of booleans:
var results = lines.map( line => hasMiddle(line.match(/\d+/g).map(Number)) );
// Output results
output.textContent = results.map( pos => pos ? 'YES' : 'NO' ).join('\n');
}
// Evaluate input immediately
input.oninput();
Input:<br>
<textarea style="width:100%; height:120px">2
3
1 2 3
4
1 2 3 3
</textarea>
<pre></pre>
This algorithm requires your input array to consist of non-negative numbers.
If you need to support negative numbers in your array, then the algorithm needs to go through the array first to calculate the sum, and then go through the array again to find the point where the balance reaches 0:
function hasMiddle(a) {
var balance = a.reduce( (sum, v) => sum + v );
return !a.every ( (v, i) => balance -= v + (i ? a[i-1] : 0) );
}
// I/O for snippet
var input = document.querySelector('textarea');
var output = document.querySelector('pre');
input.oninput = function() {
var lines = this.value.trim().split(/[\r\n]+/).filter(s => s.trim().length);
// Strip the case count and array element counts:
lines = lines.slice(1).filter( (s, i) => i % 2 );
// Call function for each test case, returning array of booleans:
var results = lines.map( line => hasMiddle(line.match(/[\d-]+/g).map(Number)));
// Output results
output.textContent = results.map( pos => pos ? 'YES' : 'NO' ).join('\n');
}
// Evaluate input immediately
input.oninput();
Input:<br>
<textarea style="width:100%; height:120px">2
3
1 2 3
4
1 2 3 3
</textarea>
<pre></pre>
Given that we have a proper array one might do as follows
var arr = [...Array(35)].map(_ => ~~(Math.random()*10)+1),
sum = arr.reduce((p,c) => p+c),
half = Math.floor(sum/2),
ix;
console.log(JSON.stringify(arr));
midix = arr.reduce((p,c,i,a) => { (p+=c) < half ? p : !ix && (ix = i);
return i < a.length - 1 ? p : ix;
},0);
console.log("best possible item in the middle # index", midix,": with value:",arr[midix]);
console.log("sums around midix:",
arr.slice(0,midix)
.reduce((p,c) => p+c),
":",
arr.slice(midix+1)
.reduce((p,c) => p+c));
Of course for randomly populated arrays as above, we can not always get a perfect middle index.

Using recursion to search all combinations of elements in an array of integers

I am working on this problem from Coderbyte using JS:
Have the function ArrayAdditionI(arr) take the array of numbers stored in arr and return the string true if any combination of numbers in the array can be added up to equal the largest number in the array, otherwise return the string false. For example: if arr contains [4, 6, 23, 10, 1, 3] the output should return true because 4 + 6 + 10 + 3 = 23. The array will not be empty, will not contain all the same elements, and may contain negative numbers.
The problem seems ripe for a recursive solution, given the branching nature of the need to explore the various combinations of array elements. I would also like to use recursion to get some extra practice.. I'm still very new to coding.
Here is the solution I came up with, and I was very happy about it until I started testing and discovered that it is not at all a solution:
function ArrayAdditionI(arr) {
// sorting the array to easily remove the biggest value
var sArr = arr.sort(function (a, b) {
return a-b;});
// removing the biggest value
var biggest = sArr.pop();
// count will be iterated to stop the recursion after the final element of the array has been processed
var count = 0;
function recursion (start, i) {
if (sArr[i] + start === biggest) {
return true;
}
else if (start + sArr[i] < biggest) {
return recursion(start + sArr[i], i+1);
}
else if (start + sArr[i] > biggest && count < sArr.length) {
count++;
return recursion(0, count);
}
else {
return false;
}
}
return recursion(0,0);
}
This code works only if the array elements which can be summed to fulfill the base case are adjacent to each other; calling ArrayAdditionI([1,2,3,4]), for example, will not work because the two elements which must be summed to reach the target value("1" in position 0, and "3" in position 2) are not adjacent.
My flow will return 1, then 1+2, then 1+2+3, then 2, then 2+3, then 3, and finally return false since the target (4) was not reached.
I can visualize how a proper solution needs to flow through a given array, but I don't know how to make this happen through recursion. For the given array [1,2,3,4], the flow should check results in this pattern: (position0, pos0+pos1, pos0+pos2, pos0+pos1+pos2, pos2, pos2+pos3, pos3). Can anyone give me a nudge? I'm going to think on this more before I sleep in an hour and give it a fresh go in the morning.. maybe my brain just needs a recharge.
Again, I'm really new to this, if I've made some very silly mistakes please let me know, I'll learn from them. Thanks!
Your algorithm has a flaw. Your function looks only one step forward.
You need to add loop inside to look over all rest values.
function ArrayAdditionI(arr) {
// sorting the array to easily remove the biggest value
// slice added to prevent original array modification
var sArr = arr.slice().sort(function (a, b) {
return a - b;
});
// removing the biggest value
var biggest = sArr.pop();
function recursion(start, i) {
var result = false;
// looking over all rest values of array
for (var j = i; j < sArr.length && !result; j++) {
if (sArr[j] + start === biggest) {
result = true;
}
else if (start + sArr[j] < biggest) {
result = recursion(start + sArr[j], j + 1);
}
}
return result;
}
return recursion(0, 0);
}
function ArrayAdditionI (arr) {
var sArr = arr.sort(function (a,b) {
return a-b;
});
var biggest = sArr.pop();
function recursion (start, indx) {
if (start + sArr[indx] === biggest) {
return true;
}
else if (sArr.length < indx) {
return false;
}
else if (start + sArr[indx] < biggest) {
return recursion(start + sArr[indx], indx + 1) || recursion(start, indx+1)
}
else {
return false;
}
}
return recursion(0,0);
}

Categories