I have some question and Maybe One of you can help me,
I have a float array and I won't find the closest float value to the target number(float)
I use a simple binary Search With javascript And from here I got stuck.
function binarySearch(arr, val, start = 0, end = arr.length - 1) {
const mid = Math.floor((start + end) / 2);
if(Math.abs(val - arr[mid]) < Math.abs(val - arr[mid + 1])){
return mid;
}
if (start >= end) {
return -1;
}
return val < arr[mid]
? binarySearch(arr, val, start, mid - 1)
: binarySearch(arr, val, mid + 1, end);
}
let array = [0,0.03336667683886884,0.06673335367773768,0.10010003051660653,0.13346670735547536,0.1668333841943442,0.20020006103321306]
let result = binarySearch(array,'0.166833') //=> This value needs to be returned 0.1668333841943442
Your condition Math.abs(val - arr[mid]) < Math.abs(val - arr[mid + 1]) is wrong, you are always checking the left value is closer than the right value, so even for value 1 in array [10, 20, 30, 40], when you check number 20, 1 is closer to 20 than to 30 - incorrect result is then returned.
Also, you need to check the edge cases, the index mid + 1 may not be available. Also the value may be smaller than the minimum or larger than the maximum, so an infinite loop may occur.
Let me offer you this solution instead:
function binarySearch(arr, val, start, end) {
// edge case: value of smaller than min or larger than max
if(array[0] >= val) return 0;
if(array[array.length - 1] <= val) return array.length - 1;
while (start <= end) {
let mid = Math.floor((end + start) / 2);
// value is in interval from previous to current element
if(val >= arr[mid - 1] && val <= arr[mid]) {
return Math.abs(val - arr[mid - 1]) < Math.abs(val - arr[mid]) ? mid - 1 : mid;
}
else {
if(arr[mid] < val) {
start = mid + 1;
}
else {
end = mid - 1;
}
}
}
return -1;
}
Your stop condition is broken. In your example, if you feed the following array:
[0.3, 0.5, 0.8, 2.0]
You have at initiation:
arr[mid] = 0.5
arr[mid + 1] = 0.8
So if you feed val = 0.1 to your algorithm, you will have:
Math.abs(val - arr[mid]) = 0.4
Math.abs(val - arr[mid + 1]) = 0.7
Therefore, you will return the index of 0.5 although you were expecting the index of 0.3.
A pseudo idea to solve your problem would be:
if array contains 1 element => return element
else if array contains 2 elements => return element closest to val.
else if val < array[mid] => recurse on first half
else if val > array[mid] => recurse on second half
It looks you have a typo. You should not return mid index, but item of array from array[mid]:
function binarySearch(arr, val, start = 0, end = arr.length - 1) {
const mid = Math.floor((start + end) / 2);
if(Math.abs(val - arr[mid]) < Math.abs(val - arr[mid + 1])){
return arr[mid];
}
if (start >= end) {
return -1;
}
return val < arr[mid]
? binarySearch(arr, val, start, mid - 1)
: binarySearch(arr, val, mid + 1, end);
}
let array = [0, 0.03336667683886884, 0.06673335367773768, 0.10010003051660653,
0.13346670735547536, 0.1668333841943442, 0.20020006103321306];
console.log(binarySearch(array,'0.166833')) // 0.1668333841943442
I found this solution but I need to return the index of The right value
function binarySearch(arr, target, lo = 0, hi = arr.length - 1) {
if (target < arr[lo]) {return arr[0]}
if (target > arr[hi]) {return arr[hi]}
const mid = Math.floor((hi + lo) / 2);
return hi - lo < 2
? (target - arr[lo]) < (arr[hi] - target) ? arr[lo] : arr[hi]
: target < arr[mid]
? binarySearch(arr, target, lo, mid)
: target > arr[mid]
? binarySearch(arr, target, mid, hi)
: arr[mid]
}
let array = [0, 0.03336667683886884, 0.06673335367773768, 0.10010003051660653,0.13346670735547536, 0.1668333841943442, 0.20020006103321306,30.01];
console.log(binarySearch(array,0.03336))
Related
I have an Array of Log items, already sorted by their timestamp (number of milliseconds since 1970). Now I want to filter them by a specific time range, so I think of Binary Search, however this variant is different than all variants I knew before as I need to find a range within a range. Note that there may be none or multiple items at the value edges.
I came up with this to reduce one range requirement but still don't know how to get to the first/last edge items:
filterByTime(min: number, max: number): LogItem[] {
const items = this.items;
const len = items.length;
if (min === undefined && max === undefined) {
return items.slice(0, len - 1);
}
min = min || Number.MIN_VALUE;
max = max || Number.MAX_VALUE;
const fn = (a: LogItem) => {
if (a.time < min) {
return -1;
} else if (a.time > max) {
return 1;
} else {
return 0;
}
};
const lowerBound = this.binarySearchBound(fn, 0, len, true);
if (lowerBound == -1) { return []; }
const upperBound = this.binarySearchBound(fn, 0, len, false);
return items.slice(lowerBound, upperBound + 1);
}
binarySearchBound(compareFn: (a: LogItem) => number, left: number, right: number, isLowerBound: boolean): number {
const items = this.items;
if (items.length == 0) {
return -1;
}
if (isLowerBound && compareFn(items[0]) == 0) {
return 0;
} else if (!isLowerBound && compareFn(items[items.length - 1]) == 0) {
return items.length - 1;
}
while (left <= right) {
const mid = (left + right) / 2;
const item = this.items[mid];
const compare = compareFn(item);
if (compare < 0) {
left = mid + 1;
} else if (compare > 0) {
right = mid - 1;
} else {
// What to do now?
}
}
return -1;
}
Worst case scenario, I can just do a linear search from the edge since I can assume there are not that much items at the edge but surely there is a better way I didn't think of but then I may have to iterate through the whole result set if mid falls in the middle of the result set.
EDIT for adding a note: It's possible for min or max is undefined (and could be both, in which case I can just set an if and return the copy of the whole array). Is it better to just substitute it with MIN_VALUE and MAX_VALUE if they are undefined, or is there a better way to handle that case?
I would suggest the following:
Write two binary search functions, as the execution time is then not hampered by passing and checking the isLowerBound boolean.
Make the returned upperBound to mean the next index after the potential last index that belongs to the range. This corresponds with how arguments work with native functions like slice.
Don't use -1 as a special index. If coded well, an empty range will come out of the two binary searches any way and give an empty array as result
Make the compare function to work with 2 parameters, so you can actually search for either the min or the max value.
Yes, I would use MIN_VALUE and MAX_VALUE as defaults and not test for boundary cases. If boundary cases happen often, it might be worth to include those checks, but in general be aware that these checks will then be executed for every filter, which may bring down the average execution time.
Here is the suggested implementation with integer data (instead of objects) to keep it simple. In order to have it run in a snippet I also removed the type references:
function filterByTime(min=Number.MIN_VALUE, max=Number.MAX_VALUE) {
const fn = (a, b) => a - b; // simplified (should be a.time - b.time)
const lowerBound = this.binarySearchLowerBound(fn, 0, this.items.length, min);
const upperBound = this.binarySearchUpperBound(fn, lowerBound, this.items.length, max);
return this.items.slice(lowerBound, upperBound);
}
function binarySearchLowerBound(compareFn, left, right, target) {
while (left < right) {
const mid = (left + right) >> 1;
if (compareFn(this.items[mid], target) < 0) {
left = mid + 1;
} else { // Also when equal...
right = mid;
}
}
return left;
}
function binarySearchUpperBound(compareFn, left, right, target) {
while (left < right) {
const mid = (left + right) >> 1;
if (compareFn(this.items[mid], target) <= 0) { // Also when equal...
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
// Demo with simplified data (instead of objects with time property)
this.items = [1, 2, 2, 2, 3, 4, 4, 5, 5, 5, 6, 7, 8, 8];
console.log(this.filterByTime(2, 4));
console.log(this.filterByTime(4, 5));
Combined the variants on this article, I merged first and last code into a single function:
filterByTime(items: LogItem[], min: number, max: number): LogItem[] {
const len = items.length;
if (len == 0) {
return [];
}
if (min === undefined && max === undefined) {
return items.slice(0, len - 1);
}
min = min || Number.MIN_VALUE;
max = max || Number.MAX_VALUE;
const fn = (a: LogItem) => {
if (a.time < min) {
return -1;
} else if (a.time > max) {
return 1;
} else {
return 0;
}
};
const lowerBound = this.binarySearchBound(fn, 0, len, true);
if (lowerBound == -1) { return []; }
const upperBound = this.binarySearchBound(fn, 0, len, false);
return items.slice(lowerBound, upperBound + 1);
}
binarySearchBound(compareFn: (a: LogItem) => number, left: number, right: number, isLowerBound: boolean): number {
const items = this.items;
if (items.length == 0) {
return -1;
}
if (isLowerBound && compareFn(items[0]) == 0) {
return 0;
} else if (!isLowerBound && compareFn(items[items.length - 1]) == 0) {
return items.length - 1;
}
let result = -1;
while (left <= right) {
const mid = (left + right) / 2;
const item = this.items[mid];
const compare = compareFn(item);
if (compare < 0) {
left = mid + 1;
} else if (compare > 0) {
right = mid - 1;
} else {
result = mid;
if (isLowerBound) {
right = mid - 1;
} else {
left = mid + 1;
}
}
}
return result;
}
I want to be able to generate the following array (or something like it) in javascript:
[
52, // random number 0-100
53, // random +1 or -1
54, // random +1 or -1
53, // random +1 or -1
52, // random +1 or -1
53, // random +1 or -1
52, // random +1 or -1
51, // random +1 or -1
50, // random +1 or -1
51, // random +1 or -1
// etc., etc., etc.
]
How can I do that?
I've tried this, but I always get a random number followed by 1's and -1's only:
Array(50).fill(0).map((v, i, a) => i !== 0 ? (Math.round(Math.random()) ? a[i-1] + 1 : a[i-1] - 1) : Math.floor(Math.random() * 101))
This might help:
function randomGenerator(size) {
let result = [];
let firstValue = Math.round(Math.random() * 100);
result.push(firstValue);
for (let i=0;i<size-1;i++){
firstValue += Math.random() > 0.5 ? 1:-1;
result.push(firstValue)
}
return result;
}
console.log(randomGenerator(10));
console.log(randomGenerator(13));
or if you prefer to go functional you can take advantage of default values
and comma operator:
const randGenerator = (size = 13,init = Math.round(Math.random() *100)) =>
Array(size).fill(0).map(e => (init += Math.random() > 0.5 ? 1:-1,init))
console.log(randGenerator(10))
console.log(randGenerator(13))
Simple solution:
function rand(min, max) {
return min + Math.floor((max + 1 - min) * Math.random())
}
function generate(size) {
let last = rand(0, 100)
const array = []
for (let i = 0; i < size; i++) {
array.push(last)
last += rand(0, 1) ? 1 : - 1
}
return array
}
console.dir(generate(50))
Here is how you can do it with a recursive function, you can set the initial min and max for the random number and also specify the final array length:
const arr = [];
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min);
}
function updateArr(min, max, arrLength, arr) {
if (arrLength) {
let resultNum = getRandomInt(min, max);
if (resultNum !== arr[arr.length - 1]) {
arr.push(resultNum);
updateArr(resultNum - 1, resultNum + 1, arrLength - 1, arr);
} else updateArr(min, max, arrLength, arr);
}
}
updateArr(0, 100, 10, arr);
console.log(arr);
arr = [Math.random()*100]
for (let i = 0; i < 49; i++){
arr.push(arr[i] + (Math.random() > 0.5? 1 : -1))
}
Is this working for you?
Could you please tell me how to find the index. I have a problem
Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.
You may assume no duplicates in the array.
Example 1:
Input: [1,3,5,6], 5
Output: 2
Example 2:
Input: [1,3,5,6], 2
Output: 1
Example 3:
Input: [1,3,5,6], 7
Output: 4
I tried like this
var searchInsert = function(nums, target) {
if (target > nums[nums.length - 1]) return nums.length;
if (target < nums[0]) return 0;
let start = 0,
end = nums.length - 1,
mid = Math.floor((start + end) / 2);
while (start < end) {
if(nums[mid] == target) return mid
if(nums[mid] > target) end = mid -1;
if(nums[mid] < target) start = mid +1;
mid = Math.floor((start + end) / 2);
}
return nums.length %2 == 0 ? mid +1 : mid
};
my test case fails
Input
[1,3]
2
Output
2
Expected
1
my test case only fail when an element is not found I want to insert in perfect index
updated Answer
var searchInsert = function (nums, target) {
if (target > nums[nums.length - 1]) return nums.length;
if (target === nums[nums.length - 1]) return nums.length-1;
if (target < nums[0]) return 0;
if (target ===nums[0]) return 0;
let start = 0,
end = nums.length - 1,
mid = Math.floor((start + end) / 2);
while (start <= end) {
if(nums[mid] == target) return mid
if(nums[mid] > target) end = mid -1;
if(nums[mid] < target) start = mid +1;
mid = Math.floor((start + end) / 2);
}
return nums[mid] > target ? mid : mid + 1;
};
Thanks for the help this code works perfectly all test case
Problem is with your last statement
return nums.length %2 == 0 ? mid +1 : mid
So in the last case your mid = 1 as since the length of array is two you're adding mid + 1
One approach is to return the value from while itself in case your target isn't found in array, on condition when start === end
const searchInsert = function(nums, target) {
if (target > nums[nums.length - 1]) return nums.length;
if (target <= nums[0] || nums.length === 0) return 0;
let start = 0,
end = nums.length - 1,
mid = Math.floor((start + end) / 2);
while (start < end) {
if (nums[mid] == target) return mid
if (nums[mid] > target) end = mid - 1;
if (nums[mid] < target) start = mid + 1;
if(start >= end){
return nums[mid] > target ? mid : mid + 1
}
mid = Math.floor((start + end) / 2);
}
};
console.log(searchInsert( [1,3,5,6], 5)) // 2
console.log(searchInsert([1,3,5,6], 2)) // 1
console.log(searchInsert([1,3,5,6], 7)) // 4
console.log(searchInsert([1,3], 2)) // 1
console.log(searchInsert([1,2,3,5], 4)) // 3
console.log(searchInsert([1,2,3,4,5,7,9,10,15], 8)) //6
console.log(searchInsert([0, 1, 3, 4], 2)) // 2
console.log(searchInsert([1],1)) // 0
console.log(searchInsert([0],1)) // 1
console.log(searchInsert([],1)) // 0
What you can try is using the <Array>.indexOf(n) and <Array>.sort() like so:
let arr = [1,3,5,6]
const needle = 0
function searchInsert (arr, needle) {
let index = arr.indexOf(needle)
if (index === -1) {
arr.push(needle)
arr = arr.sort((x, y) => x - y)
index = arr.indexOf(needle)
}
return index
}
I'm doing an exercise which i'm no being able to solve. I need to get the maximum accumulated profit by buying and selling bitcoins. I have a function(A,Y) which receive an A = array of different prices during time and a Y = fee
Restrictions:
Note: If a bitcoin was bought at 0 and sold at 1, we would have ad a loss of A[1] - A[0] =7050 -7200 - Y = -200. So, that movement was not made.
Note2: You can only have 1 bitcoin at the time. To sell, you have to have bought first. To buy, you need to have nothing or sold before.
Note3: Movements need to be time consequents. You cannot buy at A[5] and sell at A[4]
Note4: If no profit cannot be made, it should return 0
complexity is O(N)
A = [7200,7050,7300,7500,7440,7200,7300,7280,7400] //expected result 550
Y = 50
A[3] - A[1] - Y = 7500 - 7050 - 50 = 400
A[8] - A[5] - Y = 7400 - 7200 - 50 = 150
result = 550 //maximum accumulated profit
This is what i have
function solution(A, Y) {
if(A.length < 2) {
return 0;
}
var minIndex = (A[0] > A[1]) ? 1 : 0;
var minPrice = A[minIndex];
var acum = 0;
var i = minIndex + 1
for (i; i< A.length-1; i++) {
if( (A[i] - minPrice - Y) > (A[i+1] - minPrice - Y )) {
acum += A[i] - minPrice - Y;
i = i+1
} else {
acum += A[i + 1] - minPrice - Y;
i = i+2
}
minPrice = (A[i] > A[i+1]) ? A[i+1] : A[i];
}
return acum > 0 ? acum : 0;
}
Actually i'm getting 450 but it should be 550
It looks more complicated as it seems to be, because you need to check every single buying price with all possible selling price.
The result is a tree with this brute force approach.
This solution returns only the maximum profit with all buy/sell prices.
function maxima(array, fee) {
function iter(prices, index, count) {
var i = 0, profit = 0;
if (index >= array.length) {
if (!prices.length || prices.length % 2) {
return;
}
if (prices.some((v, i, a) => i && (i % 2 ? a[i - 1] >= v : a[i - 1] < v))) {
return;
}
while (i < prices.length) {
profit += prices[i + 1] - prices[i] - fee;
i += 2;
}
if (!result.length || result[0].profit < profit) {
result = [{ profit, prices }];
} else if (result[0].profit === profit) {
result.push({ profit, prices });
}
return;
}
iter(prices.concat(array[index]), index + 1); // buy/sell
iter(prices, index + 1); // no action
}
var result = [];
iter([], 0, 0);
return result;
}
console.log(maxima([7200, 7050, 7300, 7500, 7440, 7200, 7300, 7280, 7400], 50));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I know you already have an answer, but I would like to propose a possible O(n) solution. The idea is that you monitor direction of price movement along with local min and max. You define a change in direction anytime the price changes direction by more than Y from the local min or max. You buy and sell on direction changes.
var A = [6000, 7200, 7050, 7040, 7045, 7041, 7039, 7300, 7500, 7490, 7480, 7501, 7440, 7200, 7300, 7280, 7400];
var A = [7200, 7050, 7300, 7500, 7440, 7200, 7300, 7280, 7400];
let Y = 50
function buysell(A, Y) {
let direction = -1
let min = A[0]
let max = 0
let total = 0
for (let i = 1; i < A.length; i++) {
if (direction == -1) {
if (A[i] < min) min = A[i]
if (A[i] - min > Y) { // only change direction if change is greater than Y
direction = 1;
max = A[i]
console.log('buy at', min)
}
} else { // price is going up
if (A[i] > max) max = A[i]
if (max - A[i] > Y) {
total += max - min - Y
console.log('sell at ', max)
min = A[i]
direction = -1
}
}
}
// buy at end if price was going up
if (direction == 1) {
console.log('sell at ', max)
total += max - min - Y
}
return total
}
console.log("total: ", buysell(A, Y))
// Test with some edge cases:
var A = [6000, 7200,7050, 7040, 7045, 7041, 7039,7040, 7300,7500, 7490, 7480,7501, 7440,7200,7300,7280,7400];
console.log("total: ", buysell(A, Y))
var A = [ 7172, 2477, 4755, 2297, 2893, 8863 ]
console.log("total: ", buysell(A, Y))
(I believe Mark_M's answer is the best here but I'll leave mine just for completeness.)
For each selling price we'd like to know the best buying price before it so we can pair that sale with the maximum accumulated before that. We can have an O(n^2) algorithm since we have to traverse back anyway.
function f(A, Y){
let m = [0].concat(new Array(A.length - 1));
for (let i=1; i<A.length; i++){
let smallest = A[i-1];
m[i] = m[i - 1];
for (let j=i-1; j>0; j--){
smallest = Math.min(smallest, A[j]);
if (smallest < A[i] + Y)
m[i] = Math.max(m[i], A[i] - smallest - Y + (m[j - 1] || 0));
}
}
return m[m.length - 1];
}
var a = [7200,7050,7300,7500,7440,7200,7300,7280,7400];
console.log(f(a, 50));
Lets say you have input Array=[1,2,3,5,7,9,10,11,12,15]
The output should be 1-3,5,7,9-12,15
Im looking for feedback on my attempt and other possible solutions.
Heres my attempt in javascript:
var min = 0;
var max = -1;
function summarize(array) {
var sumString = "";
var prevVal = -1;
array.forEach(function(currVal, index) {
if (index > 0) {
prevVal = array[index - 1];
}
if (index === 0) {
min = currVal;
max = currVal;
} else if (currVal - prevVal === 1) {
max = currVal;
} else if (min !== max && max !== -1) {
sumString += min + "-" + max + (index < array.length - 1 ? "," : "");
min = currVal;
max = -1;
} else {
sumString += min + (index < array.length - 1 ? "," : "");
}
if (index === array.length - 1) {
if (max === -1) {
sumString += "," + min;
} else {
sumString += min + "-" + max;
}
}
});
return sumString;
}
Here is a slightly shorter implementation:
var i = 0, prev, arr = [1,2,3,5,7,9,10,11,12,15], out = [];
for(i=0; i<arr.length; prev = arr[i], i++) {
// if the current number is not prev+1, append it to out
// Note that we are adding it as a string, to ensure that
// subsequent calls to `split()` (see else part) works
if(prev !== arr[i] - 1) out.push(String(arr[i]));
// if the current number is prev+1, modify the last value
// in out to reflect it in the RHS of - (hyphen)
else out[out.length - 1] = [out[out.length - 1].split('-')[0], String(arr[i])].join('-');
}
// out => ["1-3", "5", "7", "9-12", "15"]
Another possible solution for positive numbers in ascending order. It features Array.prototype.reduce.
var array = [1, 2, 3, 5, 7, 9, 10, 11, 12, 15, 23, 24],
result = [];
array.reduce(function (r, a) {
result.push(r + 1 - a ? String(a) : result.pop().split('-')[0] + '-' + String(a));
return a;
}, array[0]);
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
Another possible solution :
var points = [1,2,3,5,6,31,7,9,10,11,12,15];
points.sort(function(a, b){return a-b}); //sort array in asc
var resultArr=[];
var max; var min;
for(i=0;i<points.length;i++) //loop
{
if(i==0)
{
min=points[i]; //lowest number in arr
max=points[i]+1; //assign next value
}
else
{
if(max==points[i]) //if value matches continue
max=points[i]+1;
else //next value is not an incremental one so push it to result arr
{
max=max-1;
resultArr.push(min+(min!=max? "-"+ max :""));
min=points[i];
max=points[i]+1;
}
if(i==points.length-1) //last element of the arr so push it to result arr
{
max=max-1;
resultArr.push(min+(min!=max? "-"+ max :""));
}
}
}
alert(resultArr);
First step uses dashes to separate sequential numbers and commas if they aren't. Second step replaces -#- with -.
var X = [1,2,3,5,7,9,10,11,12,15];
var S = '' + X[0];
for (var i = 1; i < X.length; i++) {
S += (X[i] == X[i - 1] + 1)? '-': ',';
S += X[i];
}
while (/-[0-9]+-/.test(S))
S = S.replace(/-[0-9]+-/g, '-');
alert(S);
For a sequence like 1,2,5,6 will output 1-2,5-6 which might not be what you're looking for, so an optional third step would be to replace #-#+1 with #,#+1, i.e. restore the comma:
for (var i = 1; i < X.length; i++)
S = S.replace(X[i - 1] + '-' + X[i], X[i - 1] + ',' + X[i]);
I ran into this problem recently, after some reflection, I noticed 3 different transformations: (1) Group consecutive numbers; (2) Transform groups into strings representing the ranges; (3) Join range strings on comma.
function summarizeRange(items) {
const sorted = items.slice(0).sort((a, b) => a - b);
return sorted
.slice(1)
.reduce((range, item) => {
const rangedIndex = range.reduce((ranged, rangedCollection, index) =>
rangedCollection.indexOf(item - 1) > -1 ? index : ranged,
-1
);
if (rangedIndex > -1) {
range[rangedIndex] = range[rangedIndex].concat(item);
return range;
}
return range.concat([
[item]
]);
}, [
[sorted[0]]
])
.map(range => range.length > 1 ?
'' + range[0] + '-' + range[range.length - 1] :
'' + range[0]
)
.join(',');
}
console.log(summarizeRange([0,3,2,6,19,20,22,21,1]));