Separate The Wheat From The Chaff - CodeWars Challenge - javascript

Introduction
The problem is explained on Link to challenge for best instructions
As far as I understand, the idea is to swap elements on the left side of an array with elements from the right side of an array if the element on the left side is greater than 0.
I.e. [2, -4, 6, -6] => [-6, -4, 6, 2].
Positives on the left side have to get swapped with their corresponding "out of place" opposite on the right side. So, unfortunately we can't use
array = array.sort((a, b) => a - b);
In the end, there should be only negatives numbers on the left side and positives on the right side. If the array has an odd length, then one "side" will be one (or more) elements larger, depending on whether or not there are more positives or negatives in the original array.
I.e. [8, 10, -6, -7, 9, 5] => [-7, -6, 10, 8, 9, 5]
Approach
To solve this, I created a pretty lengthy algorithm that splits the array into halfs, and keeps track of "out of place" (positive) elements on the left side and "out of place" (negative) elements on the right side, to use for swapping:
function wheatFromChaff (values) {
//IF ORIGINAL VALUES LENGTH IS EVEN
if (values.length % 2 === 0) {
let left = values.slice(0, values.length / 2);
let right = values.slice(values.length / 2);
let outOfPlaceLeft = left.filter((element) => element > 0);
let outOfPlaceRight = right.filter((element) => element < 0);
//replace positive "out of place" left elements with negative "out of place" right elements
for (let i = 0; i < left.length; i++) {
if (left[i] > 0) {
left.splice(i, 1, outOfPlaceRight.pop());
}
}
//push remaining "out of place" negative right elements to the left
while (outOfPlaceRight.length) {
let first = outOfPlaceRight.shift();
left.push(first);
}
//filter out any negatives on the right
right = right.filter((element) => element > 0).concat(outOfPlaceLeft);
//concat the remaining positives and return
return left.concat(right);
}
//IF ORIGINAL VALUES LENGTH IS ODD
if (values.length % 2 !== 0) {
let left2 = values.slice(0, Math.floor(values.length / 2));
let right2 = values.slice(Math.ceil(values.length / 2));
let middle = values[Math.floor(values.length/2)];
let outOfPlaceLeft2 = left2.filter((element) => element > 0);
let outOfPlaceRight2 = right2.filter((element) => element < 0);
//replace "out of place", positive left elements
for (let j = 0; j < left2.length; j++) {
//if out of there are out of place elements on the right
if (outOfPlaceRight2.length) {
if (left2[j] > 0) {
left2.splice(j, 1, outOfPlaceRight2.pop());
}
}
//if out of place elements on the right are empty
if (!outOfPlaceRight2.length && middle < 0) {
if (left2[j] > 0) {
left2.splice(j, 1, middle);
}
}
}
//filter out negatives on the right
right2 = right2.filter((element) => element > 0);
//unshift remaining "out of place" positive left elements to the right
while (outOfPlaceLeft2.length) {
let first = outOfPlaceLeft2.shift();
right2.unshift(first);
}
if (middle > 0) {
right2.unshift(middle);
}
if (middle < 0) {
left2.push(middle);
}
return left2.concat(right2);
}
}
console.log(wheatFromChaff([2, -6, -4, 1, -8, -2]));
Sometimes the previous approach works, but it gives me an error on Codewars:
AssertionError [ERR_ASSERTION]: [ -10, 7 ] deepEqual [ -10, -3, 7 ]
Do you see any flaws with my logic? Any suggestions for a better strategy?

I have solved this using two "pointers" one starting on the head of the array, and the other starting at the tail of the array. The idea is, on every iteration, to increment the head or decrement the tail iteratively until you found a positive number on the head pointer and a negative number at the tail pointer. When this condition is satisfied, then you have to do a swap on the positions of the elements and continue. Finally, you have to stop the loop when the head pointer is greater than the tail pointer.
function wheatFromChaff(values)
{
let res = [];
let head = 0, tail = values.length - 1;
while (head <= tail)
{
if (values[head] < 0)
{
res[head] = values[head++];
}
else if (values[tail] > 0)
{
res[tail] = values[tail--];
}
else
{
res[tail] = values[head];
res[head++] = values[tail--];
}
}
return res;
}
console.log(wheatFromChaff([2, -4, 6, -6]));
console.log(wheatFromChaff([8, 10, -6, -7, 9]));
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
This approach passed all test mentioned on that link:
Time: 894ms Passed: 5 Failed: 0
But maybe there is a better solution.

Another way, using indexOf() and lastIndexOf() helper functions:
function lastIndexOf(arr, pred, start) {
start = start || arr.length - 1;
for (let i = start; i >= 0; i--) {
if (pred(arr[i])) return i;
}
return -1;
}
function indexOf(arr, pred, start = 0) {
for (let length = arr.length, i = start; i < length; i++) {
if (pred(arr[i])) return i;
}
return Infinity;
}
function wheatFromChaff(values) {
let firstPos = indexOf(values, x => x > 0);
let lastNeg = lastIndexOf(values, x => x < 0);
while ( firstPos < lastNeg ) {
let a = values[firstPos];
let b = values[lastNeg];
values[firstPos] = b;
values[lastNeg] = a;
firstPos = indexOf(values, x => x > 0, firstPos);
lastNeg = lastIndexOf(values, x => x < 0, lastNeg);
}
return values;
}
console.log(wheatFromChaff([2, -6, -4, 1, -8, -2]));

Related

Get 5 closest elements to an element in array including that element

I am trying to get 5 closest elements to given element, including that element, in array. For example, if we have:
const arr = [1, 2, 3, 4, 7, 11, 12, 13, 15, 17]
and I want to get 5 closest elements to 11, it should return [4, 7, 11, 12, 13]. If i pass 1 it should return [1, 2, 3, 4, 7]. If I pass 15 it should return [11, 12, 13, 15, 17].
I'm not sure what you meant;
You might've meant a code to find the element and return the five nearest elements to it by place in the array;
Or you might've meant a code to find the 5 numbers closest to a number you say.
IF you meant the first case
There are two ways to do so,
A value as a parameter
Use this code:
function closestNByValue(arr, value, n) {
let ind = arr.indexOf(value);
let finalArr = [];
if (n > arr.length) {
finalArr = Array.from(arr);
} else if (ind == -1) {
finalArr = [];
} else if (ind <= n/2 - 0.5) {
finalArr = arr.slice(0, n);
} else if (ind >= (arr.length - n/2) - 0.5) {
finalArr = arr.slice(-n);
} else if (n%2 == 0) {
finalArr = arr.slice(ind-(n/2), ind+(n/2));
} else {
finalArr = arr.slice(ind-(n/2 - 0.5), ind+(n/2 + 0.5));
}
return finalArr;
}
console.log(closestNByValue([1, 2, 3, 4, 7, 11, 12, 13, 15, 17], 11, 5))
How does it do the job?
Okay first we need to find the index of the value and save it in ind (short form of 'index') and we check multiple different situations for what the ind is so we'd be able to output the best answer as finalArr.
There are two exceptions; what if there was no such value in our array? then ind = -1 and we'd return an empty array; or what if the number of elements nearby that we seek is larger than or equal to the arr.length? then we'd return all of the arr.
But if there were no exceptions, there are three different situations for the ind; first, ind is a number that makes us have all of the finalArr values from the first of arr, second, ind be a number that makes us have all of the finalArr values from the last of arr, and third, ind being a number that we have to select half from the indexes smaller than ind and half, larger.
If it is the third way, the way we select we'd be different depending on the oddity of the numbers we want to select.
And we'll have a conditional statement for each situation and return the finalArr.
An index as a parameter
function closestNByIndex(arr, ind, n) {
let finalArr = [];
if (n > arr.length) {
finalArr = Array.from(arr);
} else if (ind == -1) {
finalArr = [];
} else if (ind <= n/2 - 0.5) {
finalArr = arr.slice(0, n);
} else if (ind >= (arr.length - n/2) - 0.5) {
finalArr = arr.slice(-n);
} else if (n%2 == 0) {
finalArr = arr.slice(ind-(n/2), ind+(n/2));
} else {
finalArr = arr.slice(ind-(n/2 - 0.5), ind+(n/2 + 0.5));
}
return finalArr;
}
console.log(closestNByIndex([1, 2, 3, 4, 7, 11, 12, 13, 15, 17], 5, 5))
Similar to the first code it works, though we have the index and we don't search for it.
The point is, if you use the function with value, it'd do the nearest 5 elements of the first value that equals the entry but such confusion is not being tolerated in the second code.
IF you meant the second case
This is a code I coded:
const arr = [1, 2, 3, 4, 7, 11, 12, 13, 15, 17];
function allDiff(arr, num1, num2) {
const finalArr = [];
const x = Math.abs(num2 - num1);
for (let y = 0; y < arr.length; y++) {
if (Math.abs(arr[y] - num1) == x) {
finalArr.push(arr[y]);
}
}
return finalArr;
}
function deleteArr(arr, delet) {
for (let x = 0; x < arr.length; x++) {
if (delet.includes(arr[x])) {
delete arr[x];
}
}
return arr;
}
function closest(arr, num) {
const map = new Map()
arr2 = Array.from(arr);
let key, value;
for (let x = 0; x < arr2.length; x++) {
key = Math.abs(arr2[x] - num);
value = allDiff(arr2, num, arr2[x]);
arr2 = deleteArr(arr2, value);
map.set(key, value);
}
return map;
}
function closestN(arr, num, n) {
const map = closest(arr, num);
const mapKeys = Array.from(map.keys());
const mapKeysSorted = mapKeys.sort(function(a, b) {
return a - b
});
let finalArr = [];
let y;
for (let i = 0; i < mapKeysSorted.length; i++) {
if (n <= 0) {
break;
}
y = map.get(mapKeysSorted[i]);
if (n < y.length) {
finalArr = finalArr.concat(y.slice(0, n + 1));
break;
}
finalArr = finalArr.concat(y);
n -= y.length;
}
return finalArr;
}
console.log(closestN(arr, 11, 5));
It might be a little too long, but I have programmed it as you can give it any array (arr) with integer values, an integer (num) that you'd like it to be the base and another integer (n) for the number of the size of the output array, 5 in this case.
Explaining the code
The function closest would return a map of (the difference between the numbers, a list of the numbers in the arr that differs the number equal to their key).
The main function, closestN, calls the closest function and saves the map in the map variable.
Then it sorts the keys of the map in mapKeysSorted.
Now, a for loop loops through the mapKeySorted array and pushes new elements to the finalArr until the size of the finalArr reaches the number of elements we seek.
The main function is the closestN.
Here's a way to get to your goal:
To start, first thing to do is finding the index of the wanted number in the array. Example index of 1 in your array arr is 0. The index will help in extracting the numbers later on. The method findIndex will help us in finding the index.
Then, we need to find the position at which will start extaracting the closest numbers (in terms of position not value). As seen from the desired output you have provided, usually you want the returned array to be in the following structure:
output: [
2 nearest numbers (from N left),
the wanted number,
2 nearest numbers (from N right)
]
This can get tricky so we should make sure to deal with some edge case like when the wanted element is sitting at position 0.
Extract the numbers and return them as an array as described by your desired output. The use of slice method will come in handy here which allow us to extract the numbers just as we need.
Here's a live demo demonstrating solution:
const arr = [1, 2, 3, 4, 7, 11, 12, 13, 15, 17],
/** a function that returns an array containing the "5" (depending on "arr" length that could be less) nearest numbers (in terms of position) in "arr" array to the supplied number "n" */
findClosestNumbers = n => {
/** make sure we don't exceed the array length */
const toTake = 5 > arr.length ? arr.length : 5,
/** find the index of the wanted nulber "n", if "-1" is returned then "n" cannot be found ion the array "arr" */
idx = arr.findIndex(el => n == el),
/**
* from where we should start returning the nearest numbers (the position of the first number to extract from "arr"
* the below condition help deal with some edge cases like when "n" is the last element in "arr"
*/
startIdx = idx + toTake / 2 > arr.length ?
arr.length - 5 :
(idx - 2 >= 0 ?
idx - 2 :
0);
/** return the nearest numbers or return an empty array "[]" if the number "n" is not found on the array "arr" */
return idx == -1 ? [] : arr.slice(startIdx, startIdx + 5);
};
/** run for various scenarios */
console.log('For 1 =>', findClosestNumbers(1));
console.log('For 11 =>', findClosestNumbers(11));
console.log('For 15 =>', findClosestNumbers(15));
console.log('For 17 =>', findClosestNumbers(17));
.as-console-wrapper {
max-height: 100%!important;
}
The demo above is meant to help you understand how things could work and it is not the only way to get to your goal. Also, because I kept it as simple as possible, the above demo is wide open for improvements.

Binary search exiting early?

I'm trying to find the duplicates of two arrays and one of the arrays is significantly larger so I'm iterating through the smaller array while doing a binary search on the larger array for the number. However, my solution isn't running.
function bSearch(arr, num) {
let start = 0
let end = arr1.length - 1
while (start <= end) {
let middle = Math.round(start + end / 2)
if (arr[middle] === num) {
return arr[middle]
} else if (arr[middle] < num) {
start = middle
} else {
end = middle
}
}
return false
}
function dup(arr1, arr2) {
let output = []
let shorterArray = arr1.length > arr2.length ? arr2 : arr1
for (let i = 0; i < shorterArray.length; i++) {
if (bSearch(arr1, shorterArray[i])) {
output.push(shorterArray[i])
}
}
return output
}
let arr1 = [1, 2, 3, 5, 6, 7], arr2 = [3, 6, 7, 8, 20]
dup(arr1, arr2)
// should return [3, 5, 7]
// currently only returns [3]
A number of small issues here.
bSearch(arr1, shorterArray[i]) - in case of arr1 being short you search only it.
In your binary search you use the length of arr1, not arr for intial end variable declarion.
let middle = Math.round(start + end / 2) - .round() rounds different ways, use .floor().
Math.round((start + end) / 2 - start and end addition should be in brackets
Binary logic should increase or decrease middle, otherwise you end up in infinite loop i.e (6 + 6)/2 === 6
Thus:
if (arr[middle] === num) {
return arr[middle]
} else if (arr[middle] < num) {
start = middle + 1
} else {
end = middle - 1
}
JsBin: https://jsbin.com/ciyusodisi/edit?js,console

Confusion about start end index in binary search

Following is the binary search from this post. s means start index, e means end index. I think I understand them.
function bs(arr, tar) {
let s = 0;
let e = arr.length - 1;
while(s <= e) {
let m = Math.floor((s + e) / 2);
if(tar === arr[m]) {
return m;
}
if(tar > arr[m]) {
s = m + 1; // follow index
}
if(tar < arr[m]) {
e = m - 1; // follow index
}
}
return -1;
}
let arr = [1, 2, 3, 4, 5, 6, 7];
let tar = 5;
let out = bs(arr, tar);
console.log(out);
Sometimes I also see this version:
function bs(arr, tar) {
let s = 0;
let e = arr.length; // full len
while(s < e) {
let m = Math.floor((s + e) / 2);
if(tar === arr[m]) {
return m;
}
if(tar > arr[m]) {
s = m + 1; // because s=0;
}
// end, exact index
if(tar < arr[m]) {
e = m; // not m-1, because arr.length???
}
}
return -1;
}
let arr = [1, 2, 3, 4, 5, 6, 7];
let tar = 5;
let out = bs(arr, tar);
console.log(out);
I think after a while, I get what the indexes mean.
Binary search need an ordered list to work, for example a list of numbers. The idea is to search an element in the right half and this is possible because this algorithm converge to one. Let's say that you have a list of 10(1, 10, 54, 70, 100, 110, 120, 130, 150, 200)numbers and you are looking for 120. You divide list into two halves and look for the middle element (100) is 120 greater than 100? Yes so the element is in the second half, now start point is middle position and end position is the end of the list, you go on until you have an element, the one you are looking for. Hope this was helpful

Break array into multiple arrays based on threshold

I have the following graph, which is a representation of an array [2,8,12,5,3,...]. X axis is in seconds. I want to break this array into multiple parts when y values stays 0 for longer than 2 seconds. So the array in this example would break into 3 parts: x = 0 to 8, x = 8 to 13 and x = 13 to 20 because y stays = 0 for more than 2 seconds from 8 to 13. In practice this array could be huge. What would be fastest method to do this in pure javascript (or if needed lodash/underscore)? Currently I am looping through this array to mark 2 second stop times. Is there a better way of doing this?
You could use an iterative approach with one loop while checking the expected zero value and decide if the threshold is reached or not. of not, then delete the last interval and append the length to the array before.
This proposal yields
with threshold = 2:
[
[ 1, 7],
[ 8, 13],
[14, 20]
]
with threshold = 7:
[
[ 1, 20]
]
var y = [2, 8, 12, 5, 3, 2, 0, 0, 3, 4, 8, 10, 8, 10],
x = [1, 2, 4, 5, 6, 7, 8, 13, 14, 15, 16, 18, 19, 20],
threshold = 2,
isZero = false;
result = [];
y.forEach(function (a, i) {
var last = result[result.length - 1];
if ((a === 0) !== isZero) {
if (last) {
last[1] = x[i];
}
return;
}
isZero = !isZero;
if (last && isZero && x[i] - last[0] < threshold) {
result.pop();
if (result[result.length - 1]) {
result[result.length - 1][1] = x[i];
}
return;
}
result.push([x[i]]);
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You'll always need to look at the values of the array, so you won't be able to get further than an O(n) solution. The most efficient would probably be to run through the array with a variable containing the amount of zeros you've passed through at a certain point.
The function below is a hastily made implementation of this. I've also used a variable to store the previous index. This could also be calculated from the split array, but that would be rather inefficient if you're really talking about huge arrays.
function splitArray(array, treshold) {
var zeros = 0,
previousIdx = 0,
splitArrays = [];
array.forEach(function(point, idx) {
if (point === 0) {
zeros++;
if (zeros == treshold && previousIdx != idx - treshold + 1) {
splitArrays.push(array.slice(previousIdx, idx - treshold + 1));
previousIdx = idx - treshold + 1;
}
} else if (zeros >= treshold) {
splitArrays.push(array.slice(previousIdx, idx));
previousIdx = idx;
zeros = 0;
}
});
if (previousIdx != array.length -1) {
splitArrays.push(array.slice(previousIdx));
}
return splitArrays;
}
I've created a JSFiddle that shows this function in action with some test data: https://jsfiddle.net/Glodenox/La8m3du4/2/
I don't doubt this code can still be improved though.
If you just want to get the indices of the sections instead of an array with all data in separate arrays, you can replace the three array.slice(a, b) statements with [a, b-1].

Highest Product you can get from k of the integers

I am working on the following problem:
Given an arrayOfInts, find the highestProduct you can get from k of the integers.
This is the solution I have come up with so far based on a generalization of getting the highestProduct from 3 of the integers.
var getHighestProductOfk = function (arrayOfInts, k) {
if (arrayOfInts.length < k) {
throw Error('Array should be higher than k');
}
highestProductArray = [arrayOfInts[0]];
lowestProductArray = [arrayOfInts[0]];
for (let i=1; i<k; i++) {
highestProductArray[i] = highestProductArray[i-1]*arrayOfInts[i];
lowestProductArray[i] = lowestProductArray[i-1]*arrayOfInts[i];
}
for(let i=1; i<arrayOfInts; i++) {
let currentInt = arrayOfInts[i];
for(let j=k-1; j>=0; j--) {
highestProductArray[j] = Math.max(
highestProductArray[j],
highestProductArray[j-1]*currentInt,
lowestProductArray[j-1]*currentInt
);
lowestProductArray[j] = Math.min(
lowestProductArray[j],
lowestProductArray[j-1]*currentInt,
highestProductArray[j-1]*currentInt
);
}
// highest number
highestProductArray[0] = Math.max(highestProductArray[0], currentInt)
// lowest number
lowestProductArray[0] = Math.max(lowestProductArray[0], currentInt)
}
return highestProductArray[k-1];
}
Any idea what I do wrong?
for the following example [1, 10, -5, 1, -100], the result is -50 instead of 5000.
lowest number is 1 and the highest is 1 instead of -100 and 10
The solution for the highestProduct of three of the integers:
var getHighestProductOfThree = function (arrayOfInts) {
if (arrayOfInts.length < 3) {
throw Error('Array should be higher than 3');
}
let highestProductOfThree = arrayOfInts[0]*arrayOfInts[1]*arrayOfInts[2];
let highestProductOfTwo = arrayOfInts[0]*arrayOfInts[1];
let lowestProductOfTwo = arrayOfInts[0]*arrayOfInts[1];
let highest = arrayOfInts[0];
let lowest = arrayOfInts[0];
for (let i=1; i<arrayOfInts.length; i++) {
let currentInt = arrayOfInts[i];
highestProductOfThree = Math.max(
highestProductOfThree,
highestProductOfTwo*currentInt,
lowestProductOfTwo*currentInt
);
highestProductOfTwo = Math.max(
highestProductOfTwo,
currentInt*highest,
currentInt*lowest
);
lowestProductOfTwo = Math.min(
lowestProductOfTwo,
currentInt*lowest,
currentInt*highest
);
highest = Math.max(
highest,
currentInt
);
lowest = Math.min(
lowest,
currentInt
);
}
return highestProductOfThree;
}
Here's an idea. Sort the numbers. Next, pick from the largest positive numbers as many as you can, up to k of them. Now pick the largest even group from the smallest negative numbers that form a larger product than the smallest positive numbers, which we will replace with them. (There are some edge cases, such as only one negative and k - 1 positives).
Pick 3 from [1, 10, -5, 1, -100]
Sort => [-100,-5,1,1,10]
Pick largest positives => 10 * 1 * 1
Pick largest even number of smallest negatives we can,
whose product is greater than the one replaced
=> (-100) * (-5) > 1 * 1
Answer => 10 * (-100) * (-5)
Based on my preliminary thoughts, I suggest to sort the values ascending, take the highest value, if the count is odd and use the rest with pairs.
This keeps a positive product with a loop until all needed factors are used.
In a while loop with a check for count, the pairs are chosen, if the product is greate than of the beginning of the array. This includes negative numbers, but works for only positive or negative numbers as well.
function getHighestProductOfK(a, k) {
var p = 1;
a.sort(function (a, b) { return a - b; });
if (k > a.length || k & 2 && a[a.length - 1] < 0) {
return;
}
if (k % 2) {
p = a.pop();
k--;
}
while (k) {
p *= a[0] * a[1] > a[a.length - 2] * a[a.length - 1] ? a.shift() * a.shift() : a.pop() * a.pop();
k -= 2;
}
return p;
}
console.log(getHighestProductOfK([1, 10, -5, 1, -100], 3));
console.log(getHighestProductOfK([3, 4, 5, 6, 7], 3));
console.log(getHighestProductOfK([-3, -4, -5, -6, -7], 3));
console.log(getHighestProductOfK([3, 4, -5, -6, -7], 3));
Needs some testing to make sure it always gives good answers..
function largestProduct(k, arr) {
if (k > arr.length) throw new RangeError('Not enough numbers');
let pos = [],
neg = [];
arr.forEach(e => {
if (e >= 0) pos.push(e);
else neg.push(e);
});
pos.sort((a, b) => a < b); // 9, 8, 7, ...
neg.sort((a, b) => a > b); // -9, -8, -7, ...
if (pos.length === 0 && k % 2) // k requires odd number of negatives
return neg.slice(-k); // give the smallest number TODO: same return
let big = [];
while (k > 1) grow();
if (k === 1) { // we've reached the end of doubles but still need more
if (pos.length) big.push(pos[0]);
else { // ran out of positives, backtrack
big = big.slice(0, -1);
big.push(neg[0], neg[1]);
}
}
return {
factors: big,
product: big.reduce((a, b) => a * b, 1)
};
function grow() { // choose the next best number
let doublepos = pos[0] * pos[1],
doubleneg = neg[0] * neg[1];
if (doublepos > doubleneg || doubleneg !== doubleneg) {
big.push(pos[0]);
pos = pos.slice(1);
k -= 1;
} else {
big.push(neg[0], neg[1]);
neg = neg.slice(2);
k -= 2;
}
}
}

Categories