Binary search - multiple searched values - javascript

I using binary search for searching rows range that should be rendered in my app. GridRow has 3 properties: top, height and bottom and list of rows is sorted.
Example:
I passing 30px to my first call of rowBinarySearch, in next line 140 and firstIndex to speedup search. I return lastIndex + 1 for fix visibility of last row in my array:
const firstIndex = rowBinarySearch(rows, 30);
const lastIndex = rowBinarySearch(rows, 140, firstIndex);
return range.slice(firstIndex, lastIndex + 1);
Search function implementation:
function rowBinarySearch(arr: GridRow[], val: number, start = 0, end = arr.length - 1): number {
const mid = Math.floor((start + end) / 2);
if (mid < 0)
return 0;
if (val === arr[mid].top)
return mid;
if (start >= end)
return mid; // original version should return -1 if element dont exist
return val < arr[mid].top
? rowBinarySearch(arr, val, start, mid - 1)
: rowBinarySearch(arr, val, mid + 1, end);
}
Expected behavior:
1. Remove hacky return commented in commented listing above
2. Find first row that top value of row is lower then searched value
3. I would be great if shouldnt increment lastIndex by 1
Thanks for any help :)

Remove hacky return commented in commented listing above
This is not a hacky return. A recursive algorithm always needs a base case, and start >= end is exactly the base case this recursion depends on.
It is the preceding return that is not conventional. If all integer values are allowed for val, then val === arr[mid].top is a rare case, and it does not need special treatment really. Think of what should happen when val is arr[mid].top + 1. It should be the same (assuming a height of more than 1).
Also the first return for when mid < 0 is not necessary, unless you plan to call this function with negative values for either start or end. And acutally you risk to do this for end in recursive calls, but see next point on how to avoid that.
Find first row that top value of row is lower then searched value
Currently, your algorithm is not correct: when instead of 140, you pass 120 in the second call, the return value is 2 units less, while you only "climb" one row.
I would suggest defining end as the first index after the current range. This is in line with how parameters are defined for functions like .slice.
I would be great if shouldnt increment lastIndex by 1
You should not strive to do that, since it is only logical that if you use the same function for both finding the starting row and the ending row, that you want one more row selected than lastIndex - firstIndex. So just add 1 and don't worry about it.
Here is a fixed algorithm:
function rowBinarySearch(arr, val, start = 0, end = arr.length) {
if (start >= end) return end - 1; // base case
const mid = (start + end) >> 1; // Use shift, so you don't need to `floor`.
return val < arr[mid].top
? rowBinarySearch(arr, val, start, mid)
: rowBinarySearch(arr, val, mid + 1, end);
}
// Demo
const rows = Array.from({length: 7}, (_, id) => ({
id, top: id*25, bottom: id*25+25, height: 25
}));
const firstIndex = rowBinarySearch(rows, 30);
const lastIndex = rowBinarySearch(rows, 140, firstIndex);
console.log(rows.slice(firstIndex, lastIndex + 1).map(JSON.stringify).join("\n"));

Related

How does one, within a sequence of digits, count how many times a digit appears thats value is exactly one less than the previous digit's one?

code:
function OneDecremented(num) {
num = num.toString()
var count = 0
for(i = 1; i < num.length; i++) {
if(num[i - 1] - num[i] === 1){
count++
}
}
return count
}
console.log(OneDecremented(9876541110))
so I'm struggling to understand two things:
what's the difference between i and num[i]
I don't understand how the calculation is happening inside the if statement, could someone break it down?
sorry if these questions sound too silly, I'm new to JS and couldn't really get my head around the arithmetic calculations. Thank you for you time.
That code is poorly written for few reasons, but most importantly, it leaks the i reference globally so, let's start with a better version:
function OneDecremented(num) {
var str = num.toString();
var count = 0;
for(var i = 1; i < str.length; i++) {
if(str[i - 1] - str[i] === 1)
count++;
}
return count;
}
Strings, in modern JS, can be accessed like arrays, and the index returns the char at the index position:
if(str[i - 1] - str[i] === 1)
// is the same as
if ((str.charAt(i - 1) - str.charAt(i)) === 1)
Once retrieved each char, the code does an implicit "char to number" conversion, thanks to the - operator, but if it was a + instead, it would've concatenated the two chars as string instead (so, be careful).
It's always better to be explicit, but if you know how - works, it does the job for this task.
The loop starts from 1, and it checks that the char at i - 1, which is in the first iteration the char at index 0, minus the current char, is 1, meaning the current char is one less the previous.
When that's the case, the counter sums up.
Andrea and Mitya already nailed it.
The next step could be switching to a first class based approach like using a specific Array method such as reduce.
Such an approach, if implemented correctly, usually improves readability/maintainability of code and allows for better code-reuse.
For the example provided by the OP one could write two functions, the actual method, which gets the count and the above mentioned first class reducer functionality. Since reduce is a standard way of how arrays can be processed the arguments-precedence of the reducer/callback is well specified too ...
[/* ... */].reduce(function(accumulator, currentValue, currentIndex, currentlyProcessedArray) {
// implement reducer/aggregation/accumulator logic here.
// the return value serves as the
// new `accumulator` value within
// the next iteration step.
// thus, always return something! ... e.g ...
return (accumulator + currentValue);
});
function aggregatePrecursorAndDecrementedSuccessorCount(count, char, idx, arr) {
const precursorValue = Number(arr[idx - 1]);
const incrementedCurrentValue = (Number(char) + 1);
const isValidCount = (precursorValue === incrementedCurrentValue);
return (count + (isValidCount ? 1 : 0));
//return (count + Number(isValidCount)); // cast boolean value to either 1 or 0.
}
function getPrecursorAndDecrementedSuccessorCount(int) {
return String(int) // - assure/typecast always a/into string value.
.split('') // - split string value into an array of single characters.
.reduce(aggregatePrecursorAndDecrementedSuccessorCount, 0);
}
console.log(getPrecursorAndDecrementedSuccessorCount(9876541110));
.as-console-wrapper { min-height: 100%!important; top: 0; }
what's the difference between i and num[i]
i is the iteration key, i.e. 0, 1, 2 etc, for as many characters are in the stringified number. num[i] is the character at the index i in the string, i.e. num[i] where i is 0 == 9 (the character in the string at index 0).
I don't understand how the calculation is happening inside the if statement, could someone break it down?
That says: If the calculation of the number at index i-1 of the string, minus the current number being considered (at index i in the string) minus is 1, then increment count.
Step by step for the actual number used:
9 - has no previous character; calculation (undefined - 9) does not equate to 1
8 - previous char is 9; (9 - 8) == 1; increment count
7 - ditto
6 - ditto
5 - ditto
4 - ditto
1 - previous char is 4; calculation (4 - 1) does not equate to 1
1 - previous char is 1; calculation (1 - 1) does not equate to 1
1 - ditto
0 - previous char is 1; (1 - 0) == 1; increment count

How can I predefine the accumulator in Array.prototype.reduce()?

I need to add items to each other from a specific index in array.
function findEvenIndex(arr) {
let leftsum = 0;
let rightsum = 0;
arr.forEach((el, ind) => {
arr.reduce((acc, currv, i) => i > ind + 1 ? acc + currv : 0) //acc = 1 or undefined
leftsum += arr[ind + 1];
rightsum += arr[ind - 1]
})
}
I'd like the accumulator to be equal to ind+1. How should I do it?
How can I predefine the accumulator in Array.prototype.reduce()?
Just supply the second argument to reduce:
arr.reduce ((acc, currv, i) => i > ind+1 ? acc+currv : 0, ind + 1)
// -------------------------------------------------------^^^^^^^
But it makes no sense to use reduce if you're not using the return value. That reduce call is literally a no-op. You're not using the result, and there are no side-effects in the callback.
I asked what this function was meant to do, and you replied in a comment:
I need to take an array and find an index N where the sum of the integers to the left of N is equal to the sum of the integers to the right of N.
To do that, I'd start as near the middle as possible and then slowly work your way to the edges. Obviously you don't want me to post a solution, but I'd pick the midpoint index, calculate the sum each direction from that index, then for as long as the sums don't match, try moving left one place (subtracting the value at the new index from the left sum and adding it to the right) and check. Then try moving right one place (subtracting the value at the new index from the right sum and adding it to the left) and check. Keep going until you run into the edges.

Algorithm that involves rounding and multiples

So I have a problem where I have an array of some length (usually long). I have an initial start index into that array and a skip value. So, if I have this:
var initial = 5;
var skip = 10;
Then I'd iterate over my array with indexes 5, 15, 25, 35, etc.
But then I may get a new start value and I need to find the closest value to the initial plus or minus a multiple of the skip and then start my skip. So if my new value is 23 then I'd iterate 25, 35, 45, etc.
The algorithm I have for this is:
index = (round((start - initial) / skip) * skip) + initial
And then I need a check to see if index has dropped below zero:
while(index < 0) index += skip;
So my first question is if there's a name for this? A multiple with random start?
My second question is if there's a better way? I don't think what I have is terribly complicated but if I'm missing something I'd like to know about it.
If it matters I'm using javascript.
Thanks!
Edit
Instead of
while(index < 0) index += skip;
if we assume that both initial and skip are positive you can use:
if (index < 0) index = initial % skip;
To get the closest multiple of a number to a test number: See if the modulo of your test number is greater than number/2 and if so, return number - modulo:
function closestMultiple(multipleTest,number)
{
var modulo = multipleTest%number;
if(0 == modulo )
{
return multipleTest;
}
else
{
var halfNumber = number/2;
if(modulo >= halfNumber)
{
return multipleTest + (number-modulo);
}
else
{
return multipleTest - modulo;
}
}
}
To check if a number is a multiple of another then compare their modulo to 0:
function isMultiple(multipleTest,number)
{
return 0 == multipleTest%number;
}
You might want to add some validations for 0 in case you expect any inside closestMultiple.
The value of index computed as you put it
index = round((start - initial)/skip) * skip + initial
is indeed the one that minimizes the distance between the sequence with general term
aj = j * skip + initial
and start.
Therefore, index can only be negative if start lies to the left of
(a-1 + a0)/2 = initial - skip/2
in other words, if
start < initial - skip/2.
So, only in that case you have to redefine index to 0. In pseudo code:
IF (start < (initial - skip/2))
index = 0
ELSE
index = round((start - initial)/skip) * skip + initial
Alternatively, you could do
index = round((start - initial)/skip) * skip + initial
IF index < 0 THEN index = 0
which is the same.
No while loop required:
function newNum(newstart, initial, skip) {
var xLow = newstart + Math.abs(newstart - initial) % skip;
var xHi = newstart + skip;
var result = (xLow + xHi) / 2 > newstart ? xLow : xHi;
if (result < 0) result += skip;
return result;
}
Take the distance between your new starting point and your initial value, and find out what the remainder would be if you marched towards that initial value (Modulus gives us that). Then you just need to find out if the closest spot is before or after the starting point (I did this be comparing the midpoint of the low and high values to the starting point).
Test:
newNum(1, 20, 7) = 6
newNum(-1, 20, 7) = 6
newNum(180, 10, 3) = 182
(Even though you stated in your comments that the range of the new starting point is within the array bounds, notice that it doesn't really matter).

Most efficient way to find range in array

I have a sorted array of integer numbers that can be positive of negative:
[ -30, -13, -10, -4, -1, 4, 23, 55, 90, 234, 433, 500 ]
I need to find the indexes of the lowest number that is greater or equal to zero and the greatest number that is lower or equal to 400.
What is the most efficient way to do it?
(I am using JavaScript but any language or pseudo code will be fine)
O(log N)
JSFiddle: http://jsfiddle.net/vCY68/
function binaryIndexOf(data, criteria) {
'use strict';
var minIndex = 0;
var maxIndex = data.length - 1;
var currentIndex;
var currentElement;
var result = null;
while (minIndex <= maxIndex) {
currentIndex = (minIndex + maxIndex) / 2 | 0;
currentElement = data[currentIndex];
var comparison = criteria(currentElement);
if (comparison[0] == 'right') {
minIndex = currentIndex + 1;
} else {
maxIndex = currentIndex - 1;
}
if (comparison[1]) {
result = currentIndex;
}
}
return result;
}
var firstPositive = binaryIndexOf(data, function(value) {
return value < 0 ? ['right', false] : ['left', true];
});
var lastLess400 = binaryIndexOf(data, function(value) {
return value > 400 ? ['left', false] : ['right', true];
});
Comparison function here returns 2 values: 1st is where to move after this comparison. 2nd is if the current value is acceptable.
PS: to save a time on implementing binary search the code from http://oli.me.uk/2013/06/08/searching-javascript-arrays-with-a-binary-search/ was taken, with minor modifications
PPS: potentially you can reduce number of comparisons if you initialize the search range manually and parameterize the second search with firstPositive + 1 start index
Do a binary search for 0 and for 400 in your array. If you hit 0 or 400 then that is the answer, otherwise check the array element that you reach as well as the elements to the left or right to see which one is the greatest smaller than 0 or largest less than 400. Complexity is O(log n) if your array is size n.
Do a binary search for the lowest element greater than 0 and for he highest element greater than 400 in your array.
You would need to do a slight modification in your search to check everytime if the element next to your index is still decreasing or increasing. For ex : if you reach the value 3 and the previous elemnt is -2, then 3 would be your answer(i.e instead of comparing for equality you are searching for the next highest or lowest value)
You will need 2 O(logn) operations to achieve this.

Loop seamlessly over an array, forwards or backwards given an offset larger than the array

This is a similar question to what I have asked before (Select X amount forward and backwards in an array, looping to beginning and end if needed)
But I'm having trouble adapting the answers to a different problem I'm trying to solve.
given an arbitrary array and a current index
[a, b, c, d, e, f, g, h, i, j, k]
Let's say the current index is 0 for now (a)
I need to find a new index, given an offset of n (let's say 30, it could also be negative to go backwards). Such that it would loop through the array, continuing from the beginning when it got to the end (or continuing from the end if you where looping backwards) and just return the new array index.
I've managed to adapt an answer from the similar question to walk the array forwards, but it breaks when I try changing it to walk backwards.
function crawlArrayForwards(array, index, n){
var finalIndex;
for (var i = index, len = array.length; i <= index + n; i++) {
finalIndex = (i + len) % len;
}
return finalIndex;
}
Look, you don't need a for loop or anything. You just have to add your number with its sign and take the modulus.
function crawlArray(array, index, n) {
return ((index + n) % array.length + array.length) % array.length;
}
And that's it. Should work with positive or negative values of n.
It's not exactly elegant, but if you have a working method for going forwards, you can just put an 'if' statement in at the start, check it's bigger than 0. If it isn't, you can just reverse everything, multiply the offset by -1, and do it anyway! :D
Well I if you have a as your current index and b as your target index (which might be bigger or smaller than the length or zero)
than
b2 = b % (list.length - 1)
delivers a valid index in the array.
if you than subtract b2 - a = d you know if how many step to go and also if up- or downwards, depending if d is bigger or smaller than zero
for(var i = a; i !== b2; i += (d > 0) ? 1 : -1) {
}

Categories