I am trying to write an efficient algorithm in JavaScript to solve this task. Please see the next examples of input data and correct results:
Array: [ [-3,-4], [1,2,-3] ] Result: (-4)*(-3) = 12
Array: [ [1,-1], [2,3], [10,-100,20] ] Result: (-1)*3*(-100) = 300
Array: [ [-3,-15], [-3,-7], [-5,1,-2,-7] ] Result: (-15)*(-7)*1 = 105
It can be any number of sub-arrays and any number of elements in each sub-array. What I already found is that I probably should leave only min and max values in the each sub-array, I did it using .map(a => [Math.min(...a), Math.max(...a)]) and sort them using .sort((a, b) => a[0] - b[0]).
And now I am stuck. Probably there is a way to calculate all possible products but I am sure that it's not an effective way to solve this task.
Please help!
The problem you post can be solved with a simple algorithm. We just need to keep tracking the maximum/minimum when iterating over each sub-array. We can keep finding the next maximum/minimum by multiplying the current maximum/minimum with the max/min value in each sub-array. We pick the maximum when the iterating is over. Its time complexity is O(n) where n is total number of elements in an array (i.e. sum of number of elements in each sub-array).
Here's the complete code. find_maximum_product function keeps tracking the minimum/maximum and returns the maximum eventually, and it also keeps tracking the multipliers and return it:
/**
* arr: array of any number of sub-arrays and
* any number of elements in each sub-array.
* e.g. [[1, -1], [2, 3], [10, -100, 20]]
*/
function find_maximum_product(arr) {
let max = 1;
let min = 1;
let max_multipliers = [];
let min_multipliers = [];
for (let i = 0; i < arr.length; i++) {
const a = Math.max(...arr[i]);
const b = Math.min(...arr[i]);
const candidates = [max * a, max * b, min * a, min * b];
max = Math.max(...candidates);
min = Math.min(...candidates);
let new_max_multipliers;
let new_min_multipliers;
switch (max) {
case candidates[0]:
new_max_multipliers = max_multipliers.concat(a);
break;
case candidates[1]:
new_max_multipliers = max_multipliers.concat(b);
break;
case candidates[2]:
new_max_multipliers = min_multipliers.concat(a);
break;
case candidates[3]:
new_max_multipliers = min_multipliers.concat(b);
break;
}
switch (min) {
case candidates[0]:
new_min_multipliers = max_multipliers.concat(a);
break;
case candidates[1]:
new_min_multipliers = max_multipliers.concat(b);
break;
case candidates[2]:
new_min_multipliers = min_multipliers.concat(a);
break;
case candidates[3]:
new_min_multipliers = min_multipliers.concat(b);
break;
}
max_multipliers = new_max_multipliers;
min_multipliers = new_min_multipliers;
}
if (max >= min) {
return [max, max_multipliers];
}
return [min, min_multipliers];
}
const arrays = [
[
[-3, -4],
[1, 2, -3],
],
[
[1, -1],
[2, 3],
[10, -100, 20],
],
[
[-3, -15],
[-3, -7],
[-5, 1, -2, -7],
],
[
[14, 2],
[0, -16],
[-12, -16],
],
[
[-20, -4, -19, -18],
[0, -15, -10],
[-13, 4],
],
[
[-2, -15, -12, -8, -16],
[-4, -15, -7],
[-10, -5],
],
];
for (let i = 0; i < arrays.length; i++) {
const [max, max_multipliers] = find_maximum_product(arrays[i]);
console.log('Array:', JSON.stringify(arrays[i]));
console.log('Result:', `${max_multipliers.join(' * ')} = ${max}`);
console.log('');
}
UPDATE
Simpler version for just getting the maximum, not getting the multipliers:
/**
* arr: array of any number of sub-arrays and
* any number of elements in each sub-array.
* e.g. [[1, -1], [2, 3], [10, -100, 20]]
*/
function get_maximum_product(arr) {
return arr
.map((a) => [Math.min(...a), Math.max(...a)])
.reduce(
(acc, current) => {
const candidates = [
acc[0] * current[0],
acc[0] * current[1],
acc[1] * current[0],
acc[1] * current[1],
];
return [Math.min(...candidates), Math.max(...candidates)];
},
[1, 1]
)[1];
}
Here is a top-down recurrence that could be adapted to bottom-up (a loop) and utilises O(n) search space.
Until I can complete it, the reader is encouraged to add a third return value in the tuple, largest_non_positive for that special case.
// Returns [highest positive, lowest negative]
// Does not address highest non-positive
function f(A, i){
const high = Math.max(...A[i]);
const low = Math.min(...A[i]);
if (i == 0){
if (low < 0 && high >= 0)
return [high, low];
if (low <= 0 && high <= 0)
return [-Infinity, low];
if (low >= 0 && high >= 0)
return [high, Infinity];
}
const [pos, neg] = f(A, i - 1);
function maybeZero(prod){
return isNaN(prod) ? 0 : prod;
}
let hp = maybeZero(high * pos);
let hn = maybeZero(high * neg);
let ln = maybeZero(low * neg);
let lp = maybeZero(low * pos);
if (low < 0 && high >= 0)
return [Math.max(hp, ln), Math.min(hn, lp)];
if (low <= 0 && high <= 0)
return [ln, lp];
if (low >= 0 && high >= 0)
return [hp, hn];
}
var As = [
[[-3,-4], [1,2,-3]],
[[1,-1], [2,3], [10,-100,20]],
[[-3,-15], [-3,-7], [-5,1,-2,-7]],
[[-11,-6], [-20,-20], [18,-4], [-20,1]],
[[-1000,1], [-1,1], [-1,1], [-1,1]],
[[14,2], [0,-16], [-12,-16]],
[[-20, -4, -19, -18], [0, -15, -10],[-13, 4]]
];
for (let A of As){
console.log(JSON.stringify(A));
console.log(f(A, A.length - 1)[0]);
console.log('');
}
Sort values in arrays by their absolute value in descending order
Check the product of first array elements if its positive its the answer
Otherwise lets call product p and we know p < 0, so if we change some positive element to some negative element or vice verse we will improve answer
we can simply check all possible elements to change, for each array a element x we can check if p / a[0] * x is better than current result if it is we update our answer
*Special case: all elements in arrays are negative and we have odd number of arrays, then we simply sort in increasing order
Complexity: O(n log n) where n is total amount of elements across all arrays
Take the product of the highest number of all the arrays that have at least one positive number.
If there's an odd number of remaining arrays (with only negatives), find the one with the highest (closest to zero) negative number, and set its absolute aside.
Take the arrays that remain after step 2, take the product of their lowest number (furthest from zero), and multiply it by the products from step 1 and (if any) step 2.
(also, avoid 0 if it would be the chosen number)
The first thing to notice is that there are only two specific cases where it's not possible to get a positive product. So I think an algorithm should first check if those specific cases are happening, then call a different subalgorithm for each of the three possible situations:
it is possible to get a positive product, so we want to find the highest positive product;
one of the arrays is full of zeroes, so all products are zero;
it is impossible to get a positive product, because no array has both positive and negative numbers, and there is an odd number of arrays with only negative numbers, so we want to find the closest to zero negative product.
The second and third cases lead to trivial algorithms.
Let's consider the first case.
For every array, the only numbers that can be useful in the highest product are the highest positive number, and the lowest negative number. If an array only has positive numbers or only has negative numbers, then there is only one useful number in that array, which can be chosen immediately.
For all the remaining arrays, you have to choose whether to use the positive or the negative number. Ideally, you want to use the one with the highest absolute value; but if you do that for every array, then the result might be negative.
This leads to a linear algorithm:
For all of the remaining arrays, initially select the number with the highest absolute value
If the resulting product is positive, you're done.
If the resulting product is negative, then a compromise has to be done in one of the arrays. For every array, compute the "cost" of this compromise (equal to the difference between the absolute values of the two interesting numbers in that array, multiplied by the product of all the other selected numbers).
Finally, choose the array whose cost is the lowest, and change the chosen number in that array.
Here is an example execution of the algorithm on the list of arrays [[18,19,20,-23], [12,-10,9,8],[-10,-3],[5,3],[-10,-5]].
Here we notice that it is possible to find a positive solution, because at least one of the arrays contains both negative and positive numbers.
For the last three arrays we have no choice between positive and negative: so we can already choose -10, 5 and -10 as the three numbers for these three arrays. For the first array, we'll have to choose between 20 and -23; and for the second array we'll have to choose between 12 and -10.
So the final product will be: (20 or -23) * (12 and -10) * (-10) * 5 * (-10).
Ideally, we would prefer 23 to 20, and 12 to 10. That would result in:
(-23) * 12 * (-10) * 5 * (-10)
Unfortunately, this is negative. So the question is: do we replace -23 with 20, or 12 with -10?
The cost of replacing -23 with 20 would be (23-20) * 11 * (10*5*10) = 33 * (10*5*10).
The cost of replacing 12 with -10 would be (12-10) * 21 * (10*5*10) = 42 * (10*5*10).
Finally, we choose to replace -23 with 20, because that is the less costly compromise.
The final product is 20 * 12 * (-10) * 5 * (-10).
Given any number between 0 and 1, such as 0.84729347293923, is there a simple way to make it into 84729347293923 without string or regex manipulation? I can think of using a loop, which probably is no worse than using a string because it is O(n) with n being the number of digits. But is there a better way?
function getRandom() {
let r = Math.random();
while (Math.floor(r) !== r) r *= 10;
return r;
}
for (let i = 0; i < 10; i++)
console.log(getRandom());
Integers mod 1 = 0, non integers mod 1 != 0.
while ((r*=10) % 1);
Ok, just want to refactor my code (i realized that was bad so this is what i discovered to correctly get the value as you requested).
NOTE: As the question says that "given any number between 0 and 1", this solution only works for values between 0 and 1:
window.onload = ()=>{
function getLen(num){
let currentNumb = num;
let integratedArray = [];
let realLen = 0;
/*While the number is not an integer, we will multiply the copy of the original
*value by ten, and when the loop detects that the number is already an integer
*the while simply breaks, in this process we are storing each transformations
*of the number in an array called integratedArray*/
while(!(Number.isInteger(currentNumb))){
currentNumb *= 10;
integratedArray.push(currentNumb);
}
/*We iterate over the array and compare each value of the array with an operation
*in which the resultant value should be exactly the same as the actual item of the
*array, in the case that both are equal we assign the var realLen to i, and
*in case that the values were not the same, we simply breaks the loop, if the
*values are not the same, this indicates that we found the "trash numbers", so
*we simply skip them.*/
for(let i = 0; i < integratedArray.length; i++){
if(Math.floor(integratedArray[i]) === Math.floor(num * Math.pow(10, i + 1))){
realLen = i;
}else{
break;
}
}
return realLen;
}
//Get the float value of a number between 0 and 1 as an integer.
function getShiftedNumber(num){
//First we need the length to get the float part of the number as an integer
const len = getLen(num);
/*Once we have the length of the number we simply multiply the number by
*(10) ^ numberLength, this eliminates the comma (,), or point (.), and
*automatically transforms the number to an integer in this case a large integer*/
return num * (Math.pow(10, len));
}
console.log(getShiftedNumber(0.84729347293923));
}
So the explanation is the next:
Because we want to convert this number without using any string, regex or any another thing, first we need to get the length of the number, this is a bit hard to do without using string conversions... so i did the function getLen for this purpose.
In the function getLen, we have 3 variables:
currentNumb: This var is a copy of the original value (the original number), this value help us to found the length of the number and we can do some transforms to this value whitout changing the original reference of the number.
We need to multiply this value any times is needed to transform the number to an integer and then multiplyng this value by ten to ten.
with the help of a while (this method makes the number a false integer).
NOTE: I saw "False integer" because when i was making the tests i realized that in the number is being adding more digits than normal... (Very very strange), so this stupid but important thing makes neccesary the filter of these "trash numbers", so later we proccess them.
integratedArray: This array stores the values of the result of the first while operations, so the last number stored in this array is an integer, but this number is one of the "fake integers", so with this array we need to iterate later to compare what of those stored values are different to the original value multiplied by (10 * i + 1), so here is the hint:
In this case the first 12 values of this array are exactly the same with the operation of Math.floor(num * Math.pow(10, i + 1))), but in the 13th value of the array these values are not the same so... yes!, there are those "trash numbers" that we were searching for.
realLen: This is the variable where we will store the real length of the number converting the float part of this number in an integer.
Some binary search approach:
Its useless if avarage length < 8;
It contains floating point issues.
But hey it is O(log n) with tons of wasted side computations - i guess if one counts them its event worse than just plain multiplication.
I prefer #chiliNUT answer. One line stamp.
function floatToIntBinarySearch(number){
const max_safe_int_length = 16;
const powers = [
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
10000000000,
100000000000,
1000000000000,
10000000000000,
100000000000000,
1000000000000000,
10000000000000000
]
let currentLength = 16
let step = 16
let _number = number * powers[currentLength]
while(_number % 1 != 0 || (_number % 10 | 0) == 0){
step /= 2
if( (_number % 10 | 0) == 0 && !(_number % 1 != 0)){
currentLength = currentLength - step;
} else {
currentLength = step + currentLength;
}
if(currentLength < 1 || currentLength > max_safe_int_length * 2) throw Error("length is weird: " + currentLength)
_number = number * powers[currentLength]
console.log(currentLength, _number)
if(Number.isNaN(_number)) throw Error("isNaN: " + ((number + "").length - 2) + " maybe greater than 16?")
}
return number * powers[currentLength]
}
let randomPower = 10 ** (Math.random() * 10 | 0)
let test = (Math.random() * randomPower | 0) / randomPower
console.log(test)
console.log(floatToIntBinarySearch(test))
Working on some Javascript challenges on Code Signal and I'm having an issue solving this:
Ratiorg got statues of different sizes as a present from CodeMaster for his birthday, each statue having an non-negative integer size. Since he likes to make things perfect, he wants to arrange them from smallest to largest so that each statue will be bigger than the previous one exactly by 1. He may need some additional statues to be able to accomplish that. Help him figure out the minimum number of additional statues needed.
Example
For statues = [6, 2, 3, 8], the output should be
makeArrayConsecutive2(statues) = 3.
Ratiorg needs statues of sizes 4, 5 and 7.
My approach:
Sort the array smallest to largest
Create counter variable to store number of missing numbers
Iterate through array
Subtract [i + 1] element from [i] element
If it equals 1, numbers are consecutive, if not the numbers are not consecutive (increment counter variable)
Return counter variable
Here is my code:
function makeArrayConsecutive2(statues) {
// Sorts array numerically smallest to largest
statues.sort((a, b) => a - b);
let counter = 0;
// If array only contains one number return 0
if(statues.length === 1) {
return 0;
}
/* Iterate through array, subtract the current element from the next element, if it
equals 1 the numbers are consecutive, if it doesn't equal one increment the counter
variable */
for(let i = 0; i <= statues.length -1; i++) {
if(statues[i] !== statues.length -1 && statues[i + 1] - statues[i] != 1) {
counter++;
}
console.log(statues[i]);
console.log('counter : ' + counter);
}
return counter;
}
When statues contains [5, 4, 6] the output is this:
4
counter : 0
5
counter : 0
6
counter : 1
I think the problem is when array is on the last element, in this case 6, it's attempting to look at statues[i + 1] when that element doesn't exist. I added statues[i] !== statues.length -1 to my if statement to address that but it doesn't appear to be working. What's wrong with my code and why is the final element incrementing the counter variable?
I'd approach it by building the target array which goes from the min+1 to the max-1 of the input by ones, excluding members of the input.....
function missingConseq(input) {
let min = Math.min.apply(null, input)
let max = Math.max.apply(null, input)
let result = []
for (i = min+1; i < max; i++) {
if (!input.includes(i)) result.push(i)
}
return result
}
let array = [6, 2, 3, 8]
console.log(missingConseq(array))
I need to create a method that finds the largest sum of consecutive entries in an array given a group size. It should take an array and the interval size as inputs and should return both the largest sum and the index of the first entry in the group.
For example, in the following Array [1,1,1,1,1,1,1,2] given a group size of 2 the result would be a maximum sum of 3 and a position of 6.
How would one Complete this?
Thanks!
Since you didn't post any code my answer will be general.
The easiest way will be to loop through the array and finding sum of current digit with n consecutive digits, up until the currentDigitIndex<=array.length-n, in this loop you should compare currentDigitSum with the currentMaxSum and store which ever is larger in the currentMaxSum (also store the maxSumPositionIndex).
While solving this problem you should account for a things like: what if array length is equal to 1 or 0, what if an array has negative integers, what if group size will be larger than array length, what if your array has 2 or 3 equally large sums...
Here is an answer I came up with. It works in MOST scenerios, not all, therefore the answer can be improved on.
Array.prototype.maxGroupOf = function (size) {
max = -Infinity;
indexSpot = 0;
len = this.length - size + 1;
add = function (x, y) {
return x + y;
};
this.forEach(function (x, y) {
if (y > len) return;
b = this.slice(y, y + size).reduce(add, 0);
if (b > max) {
max = b;
indexSpot = y;
}
}, this);
return {
sum: max,
position: indexSpot
};
};
console.log([1, 1, 1, 1, 1, 1, 1, 2].maxGroupOf(2));
---Output---
Object {sum: 3, position: 6}