Find index by breakpoints instead of multiple else if statements - javascript

I am trying to generate sizes based on a persons height. For the moment my program works but I would like to find a shorter way to get the size instead of using all these else if statements. I would like to loop through "breakpoints" to find the corresponding index.
This is my original code + what i had in mind.
const sizes = ['xxs', 'xxs or xs', 'xs', 'xs or s'] // Goes on for all sizes...
function generateSize(height) {
let size;
if (height < 142) {
size = sizes[0];
} else if (height >= 142 && height < 148) {
size = sizes[1];
} else if (height >= 148 && height < 154) {
size = sizes[2];
} else if (height >= 154 && height < 160) {
size = sizes[3]; // Goes on for all sizes...
} else {
size = 'larger...';
}
return size;
}
// Example of what I had in mind.
const heightBreakpoints = [142, 148, 154, 160];
function getByBreakpoints(breakpoints, height){ // Part where I am stuck.
let index;
// Loop through breakpoints...
return index;
}
const sizeIndex = (getByBreakpoints(heightBreakpoints, 158));
const s = sizes[sizeIndex];

I think you could simplify this greatly just by tweaking your starting data structure. What if we had an array of objects that tie together size and its breakpoint:
const sizeMap = [
{ maxHeight: 142, size: 'xxs' },
{ maxHeight: 148, size: 'xxs or xs' },
{ maxHeight: 154, size: 'xs' },
{ maxHeight: 160, size: 'xs or s' },
]
const getSize = height => sizeMap.find(item => height < item.maxHeight).size
console.log(getSize(143))
Array function find returns the first value that satifsies your condition. The precondition for this approach to work is to have your array object's heights in ascending order.

const sizes = ['xxs', 'xxs or xs', 'xs', 'xs or s', "even another one here"]
// Goes on for all sizes...
function generateSize(height){
let size;
let left = 142;
let right = 142;
// Initialize the variables for comparison
// Left and right comparison based on position of "&"
// This is just user-defined
for(var i = 0; i < sizes.length; i++){
if(height < right){
size = sizes[i];
return size;
}
else {
// add counter from here
// This takes us to the next item in the array
i += 1;
// add right comparison with intervals of 6
right += 6;
if(height >= left && height < right){
size = sizes[i];
return size;
}
else {
// add left comparison with intervals of 6
left += 6;
// revert the counter to its initial value
i -= 1;
}
}
}
}
console.log("First: " + generateSize(141))
console.log("Second: " + generateSize(147))
console.log("Third: " + generateSize(153))
console.log("Fourth: " + generateSize(159))
console.log("Last: " + generateSize(161));
// Note this 161, which will return the new last value in the array
This assumes your sizes are at intervals of 6, (which they are) and returns respective values corresponding to the array

if(height<160){
height-=142;
if(height<0){size=sizes[0]}
else{
size=sizes[(hieght)%6]
}
}
else{
size='larger...'
}
check if this works in all cases I am sleepy

Related

Optimise Round Up/Down Function

So one of our clients (an auctioneer) has a set of weird increments (also know as London increments), where essentially they don't conform to any divisible number, so using something like: Math.round(number / increment) * increment will not work.
The increments
From: 100, To: 299, Increment: 10
From: 300, To: 319, Increment: 20
From: 320, To: 379, Increment: 30
From: 380, To: 419, Increment: 20
And this kind of thing goes on.
So taking a number like: 311 should round up to 320. Now I have this code and it works fine, it also rounds up/down 321 => 350 and 363 => 380 as expected.
My concern is that it is not fast and/or sustainable and with large numbers that need to be rounded it will get slower. This function needs to be as fast as the Math.round() obviously knowing that it won't but as fast as possible. Now as much as I got it working, the way I have done it is essentially looping X amount of times (x being any number, so I have set it to 9999999, and I am hoping someone knows a better way of doing this.
// Get increment amount
window.getIncrement = (num) => {
var num = parseInt(num);
for (var i = 0; i < window.increments.length; i++) {
if (num >= parseInt(window.increments[i].from) && num <= parseInt(window.increments[i].to)) {
return parseInt(window.increments[i].increment);
}
}
}
// Get increment start value
window.getIncrementStartValue = (num) => {
var num = parseInt(num);
for (var i = 0; i < window.increments.length; i++) {
if (num >= parseInt(window.increments[i].from) && num <= parseInt(window.increments[i].to)) {
return parseInt(window.increments[i].from);
}
}
};
// Custom round up function
const roundToNearestIncrement = (increment, number, roundDown) => {
var incrementStart = parseInt(window.getIncrementStartValue(number));
var increment = parseInt(increment), number = parseInt(number);
console.log(incrementStart, increment, number);
// So now we have a start value, check the direction of flow
var lastBelow = false, firstAbove = false;
for (var i = 0; i < 9999999; i++) {
var incrementRounder = incrementStart + (increment * i);
if (incrementRounder === number) { return number; }
if (incrementRounder < number) { lastBelow = incrementRounder; }
if (incrementRounder > number) { firstAbove = incrementRounder; }
if (lastBelow !== false && firstAbove !== false) { break; }
console.log('Loop #' + i + ', Below: ' + lastBelow + ', Above: ' + firstAbove);
}
return !roundDown ? firstAbove : lastBelow;
}
Then you use it like so:
// Example usage
var num = 329;
var inc = getIncrement(num);
console.log('Rounded: ' + roundToNearestIncrement(inc, num) + ', Expected: 350');
Now as I said it works great, but my concern is that it will slow down a Node process if the number uses something large like 1,234,567, or just the highest number of that increment set, because the code will loop until it finds the above and below number, so if anyone has a better idea on how to do this that it will work but not loop?
See screenshot of the one I did before:
You can see it had to loop 1865 times before it found the above and below amounts.
Anyway, any ideas you have would be appreciated.
There are a couple of ways of making this faster
1.You can store a very big hash will all the possible values and the rounding result. This will use a lot of scape, but will be the fastest. This means that you'll a hash similar to this
rounded = []; rounded[0]=0 ... rounded[100] = rounded[101] = ... = rounded[109] = 110 ... and so on.
Of course this solution depends on the size of the table.
2.Build a binary search tree, based on the breakout points and search that tree. If the tree is balanced it will take O(log(n)) for a search.
If I understand the problem correctly:
Pre-build the array of all the thresholds, in ascending order. I imagine it'll look something like [0, 1, 2,..., 320, 350, 380, 400, 420,...];
Then the lookup will be simple:
const findNearestThreshold = (number) => thresholdsArray
.find(threshold => (threshold >= number));
A solution basing just on the increments array.
const steps = [
{ from: 100, increment: 10}, // I don't need 'to' property here
{ from: 300, increment: 20},
{ from: 320, increment: 30},
{ from: 380, increment: 20},
]
const roundUp = x => {
const tooLargeIndex = steps.findIndex(({from}) => from > x);
const { from, increment } = steps[tooLargeIndex - 1];
const difference = x - from;
return from + Math.ceil(difference / increment) * increment;
}
console.log(300, roundUp(300));
console.log(311, roundUp(311));
console.log(321, roundUp(321));

Find the closest index to given value [duplicate]

This question already has answers here:
Get the closest number out of an array
(21 answers)
Closed 7 years ago.
I have an ordered array:
btnDrag.pos = [0, 65, 131, 196, 259, 323, 388, 453, 517];
And a function that fires when drag stops:
btnDrag.draggable({
axis: 'x',
containment: 'parent',
stop: function() {
var index = (function(){
var new_x = btnDrag.position().left;
// now, how to find the closest index in btnDrag.pos relative to new_x ?
// return index;
})();
btnDrag.animate({
'left': (btnDrag.pos[index] + 'px')
});
}
});
The array values are points which btnDrag is allowed to stay (in axis 'x').
So, the function must return the closest index with the value to btnDrag go.
Thanks in advance.
Since your array is sorted, the fastest way is to use a modified version of the binary search algorithm:
function closest (arr, x) {
/* lb is the lower bound and ub the upper bound defining a subarray or arr. */
var lb = 0,
ub = arr.length - 1;
/* We loop as long as x is in inside our subarray and the length of our subarray is
greater than 0 (lb < ub). */
while (ub - lb > 1) {
var m = parseInt((ub - lb + 1) / 2); // The middle value
/* Depending on the middle value of our subarray, we update the bound. */
if (arr[lb + m] > x) {
ub = lb + m;
}
else if (arr[lb + m] < x) {
lb = lb + m;
}
else {
ub = lb + m;
lb = lb + m;
}
}
/* After the loop, we know that the closest value is either the one at the lower or
upper bound (may be the same if x is in arr). */
var clst = lb;
if (abs(arr[lb] - x) > abs(arr[ub] - x)) {
clst = ub;
}
return clst; // If you want the value instead of the index, return arr[clst]
}
Here is a fiddle where you can test it: http://jsfiddle.net/Lpzndcbm/4/
Unlike all the solution proposed here this solution runs in O(log(n)) and not in O(n). If you are not familiar with complexity, it means that this algorithm will find the closest value in an array of size N in at most O(log(N)) loop while the others will find it in at most N loop (with N = 10000, it makes a big difference since log(10000) ~ 14 (binary log)).
Note that if you have really small array, this may be slower than the naive algorithm.
There you go :
function closest(list, x) {
var min,
chosen = 0;
for (var i in list) {
min = Math.abs(list[chosen] - x);
if (Math.abs(list[i] - x) < min) {
chosen = i;
}
}
return chosen;
}
Each time, the minimum distance is computed and the chosen value is updated based on the minimum. (http://jsbin.com/dehifefuca/edit?js,console)
Something like this?
var closest = btnDrag.pos.reduce(function (prev, curr) {
return (Math.abs(curr - new_x) < Math.abs(prev - new_x) ? curr : prev);
});
Simple for loop will do it:
var btnDrag = {};
btnDrag['pos'] = [0, 65, 131, 196, 259, 323, 388, 453, 517];
new_x = 425;
var index = -1;
for (var i = 0; i < btnDrag.pos.length; i++)
{
if (i < btnDrag.pos.length-1) //loop till i is at 2 positions from the end.
{
//value has to be less then the selected value + 1
if (new_x < btnDrag.pos[i+1])
{
//calculate the half between the values and add it with the first value
// test if new_x is larger then that value.
if ((btnDrag.pos[i+1] - btnDrag.pos[i])/2 + btnDrag.pos[i] > new_x)
{
index = i;
break;
}
else
{
index = i+1;
break;
}
}
}
else
{
//edge cases.
if (new_x < 0)
{
index = 0;
}
else
{
index = btnDrag.pos.length-1;
}
}
}
document.body.innerHTML = btnDrag['pos'][index] + " (" + index + ")";

Dispersing numbers in a javascript array

I have an array of 10+ numbers. They represent coordinates on a circle - in degrees, i.e. each number is in between 0 and 359.999999...
The problem I am trying to solve is that when I draw my items on the circle (via html5 canvas api), sometimes they are clustered together and that results in items being drawn onto each other.
So I would like to create an algorithm which disperses items evenly around their initial cluster position. Let's say (and I'd like this to be a configurable option) the minimal distance between two items is 5 degrees.
So if the initial array is [5, 41, 97, 101, 103, 158, 201, 214, 216, 217, 320] then I would like the algorithm come up with something like [5, 41, 95, 100, 105, 158, 201, 211, 216, 221, 320]
(with bolded items being dispersed around their initial "gravity center" regardless whether those are 2 or more items).
Also what would be neccessary is that the algorithm recognizes 0 and 359 being just 1 unit (degree) apart and also spread such items evenly around.
Has anyone ever created such algorithm or have a good idea how it could be achieved? Even some general thoughts are welcome.
I'm sure I could achieve that with plenty of trial and error, but I'd like to hear some educated guesses, if you will, first.
var val = [5, 41, 96, 101, 103, 158, 201, 214, 216, 217, 320, 1201, 1213, 1214, 1216, 1217, 1320],
delta = Array.apply(null, { length: val.length }).map(function () { return 0 }),
result,
threshold = 5,
converged = false;
document.write('val: ' + val + '<br>');
while (!converged) {
converged = true;
delta = delta.map(function (d, i) {
if (i < delta.length - 1 && delta.length > 1) {
if (val[i + 1] + delta[i + 1] - val[i] - d < threshold) {
converged = false;
delta[i + 1] += 1;
return d - 1;
}
}
return d;
});
document.write('delta: ' + delta + '<br>');
}
result = val.map(function (v, i) {
return v + delta[i];
});
document.write('result: ' + result + '<br>');
// try to minimise difference
converged = false;
while (!converged) {
converged = true;
delta = delta.map(function (d, i) {
if (i < delta.length - 2) {
var space = val[i + 1] + delta[i + 1] - val[i] - d;
if (d < 0 && space > threshold) {
converged = false;
return d + space - threshold;
}
}
return d;
});
document.write('delta min: ' + delta + '<br>');
}
result = val.map(function (v, i) {
return v + delta[i];
});
document.write('result: ' + result + '<br>');
the code pushes two too close couples appart with one on each side. this is symetrically and results in sometimes to far pushed values, which can be corrected.
[not implemented!]
if the space of your values is not sufficient, [0..360[ or by more then 72 elements with a difference of 5 the the while loop may not come to an end.
edit: the minimise block should iterate until all values are correted.
To get that "evenly random" distribution, say you have N numbers - split the circle into N segments, then randomly place each number into its segment.
That way you won't even need to care about 0 and 359 being only 1 unit apart.
Here's an idea:
var numbers = 5;
var segment = 360/numbers;
var result = [];
for(var i = 0; i < numbers; i++) {
result.push(Math.round((Math.random() + i) * segment));
}
alert(result.join(','));
Here's a more graphical idea (imagine folding the rectangle into a cylinder):
var numbers = 5;
var segment = 360 / numbers;
var minimum = 15;
var div = document.getElementById('r');
function build() {
var result = [];
div.innerHTML = '';
for (var i = 0; i < numbers; i++) {
var current = 0;
while(current < minimum + (result[result.length - 1] || 0)) {
current = Math.round((Math.random() + i) * segment);
}
result.push(current);
var d = document.createElement('div');
d.className = 'segment';
d.style.left = (result[result.length - 2] || 0) + 'px';
d.style.width = (current - (result[result.length - 2] || 0) - 1) + 'px';
d.textContent = current;
div.appendChild(d);
}
console.log(result.join(','));
}
build();
.segment {
height: 100%;
position: absolute;
font-size: 12px;
text-align: right;
border-right: 1px solid black;
}
#r {
height: 100%;
width: 360px;
background: #eee;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
button {
position: absolute;
right: 4px;
margin: 0;
}
<button onclick="build()">Rebuild</button>
<div id="r"></div>
Algorithms that pretty-print graphs use a system of springs to move vertices away from each other when they overlap. Here, you are dealing with just one dimension and may get away with iteratively adjusting nearby angles until all nodes are at least 5 degrees apart.
You can deal with the cyclic values by creating an auxiliary working array, such that the elements are reordered after the largest gap. This allows you to treat the array as linear values without having to care about wrapping:
[2, 7, 320, 359] -> [-40, -1, 2, 7]
The code below does this. The nodes are moved in a rather crude fashion, though: The code only looks at pairs of nodes that are too close. The code can probably be improved by loking at clusters of two or more nodes that are too close to each other:
function adjust(arr, dist)
{
var offset = 0;
var max = 360.0 + arr[0] - arr[arr.length - 1];
var min = max;
var mix = 0; // index of first elment after largest gap
// exit early if array can't be adjusted
if (dist * arr.length > 240.0) return arr;
// find largest gap
for (var i = 1; i < arr.length; i++) {
var d = arr[i] - arr[i - 1];
if (d > max) {
max = d;
mix = i;
}
if (d < min) min = d;
}
var x = []; // working array
var adj = []; // final, adjusted array
// create working array on greatest gap
for (var i = 0; i < arr.length; i++) {
if (i + mix < arr.length) {
x.push(arr[i + mix] - 360);
} else {
x.push(arr[i + mix - arr.length]);
}
}
// iteratively adjust angles
while (min < dist) {
min = dist;
for (var i = 1; i < x.length; i++) {
var d = x[i] - x[i - 1];
if (d < dist) {
if (d < min) min = d;
x[i - 1] -= (dist - d) / 2;
x[i] += (dist - d) / 2;
}
}
}
// create final array
for (var i = 0; i < x.length; i++) {
if (i - mix < 0) {
adj.push(x[i - mix + x.length]);
} else {
adj.push(x[i - mix] + 360);
}
}
return adj;
}

random percentage that fluctuates javascript/jquery

So I'm trying to pick a random number based on a percentage. 0-5
0 - 25% (25/100)
1 - 25% (25/100)
2 - 20% (20/100)
3 - 15% (15/100)
4 - 10% (10/100)
5 - 5% (5/100)
But, sometimes one or more of those values will need to be omitted from the picking. So you could encounter something like this
0 - 25% (25/65)
2 - 20% (20/65)
3 - 15% (15/65)
5 - 5% (5/65)
Which means that the percentage has to scale with the other numbers there.
I thought I could use a random function to do something like this
var1 = Math.floor((Math.random() * 100) + 1);
//0 - 25% random(1-25)
//1 - 25% random(26-50)
//2 - 20% random(51-70)
//3 - 15% random(71-85)
//4 - 10% random(86-95)
//5 - 5% random(96-100)
if(var1 <= 25){
var2 = 0;
}else if(var1 <= 50){
var2 = 1;
}else if(var1 <= 70){
var2 = 2;
}else if(var1 <= 85){
var2 = 3;
}else if(var1 <= 95){
var2 = 4;
}else if(var1 <= 100){
var2 = 5;
}else{
// error
}
But I ran into a problem when one or more of the variables (0-5) is omitted. It is possible to setup a bunch of if/else statements but it's not a very practical solution.
Does anybody know a better way I could possibly do this?
Also, if you're unsure of what my question is, please say so. I will try to be more specific and clear on my intentions.
I think, it is exactly what you want. Example
function RangeArray(diffs) {
this.diffs = diffs;
}
// Omit range by index
RangeArray.prototype.omitAt = function(index) {
var arr = this.diffs;
// move value to the next item in array
arr[index + 1] += arr[index];
arr[index] = 0;
}
// Find index of range for value
RangeArray.prototype.indexOf = function(value) {
var arr = this.diffs;
var sum = 0;
for (var index = 0; index < arr.length; ++index) {
sum += arr[index];
if (value < sum) {
return index;
}
}
return -1;
}
// ------- tests ----------
// array of ranges created using your percentage steps
var ranges = new RangeArray([25,25,20,15,10,5]);
// your random values
var values = [1, 26, 51, 70, 86, 99];
// test resutls: indexes of ranges for values
var indexes;
console.log('ranges: 1-25, 26-50, 51-70, 71-85, 86-95, 96-100');
console.log('random values: ' + values.join(', '));
// for your random values indexOf should return 0, 1, 2, 3, 4, 5 accordingly
indexes = values.map(function(x) { return ranges.indexOf(x); });
console.log('test 1 results: ' + indexes.join(', '));
// omit range at index 1 and 4
ranges.omitAt(1);
ranges.omitAt(4);
// for your random values indexOf should return 0, 2, 2, 3, 5, 5 accordingly
indexes = values.map(function(x) { return ranges.indexOf(x); });
console.log('test 2 results: ' + indexes.join(', '));
You store your ranges in an array with a the percentage of each block then you can check them. Be sure that your array sums up to 100.
You can use a simple loop.
var1 = Math.floor((Math.random() * 100) + 1);
var2 =0;
results = [25,25,20,15,10,5];
total = 0;
for (i = 0;i <results.length; i++ ) {
total += results[i];
if (var1 < total) {
var2 = i;
break;
}
}
console.log(var1)
console.log(var2)
example fiddle: http://jsfiddle.net/auo6nc7p/
So in the array above the first item has 25% probablility, the second 25% and so on. You can then remove the element from the array if you do not want them to be picked up.
Is that answering your question ?

Better way for al lot of if else

I have a function to change the background color depending on the value of a slider
There are 35 different colors and I now use this code for it (of course it is longer)
if (value < 25) {
color = '#FFFFFF';
} else if (value > 25 && value < 50) {
color = '#F8F8F8';
} else if (value > 50 && value < 75) {
color = '#F0F0F0 ';
}
Is there a way to shorten this up?
If you're incrementing by 25, then make an Array of colors:
var colors = ['#FFFFFF', '#F8F8F8', '#F0F0F0 ', ... ]
And then do a little math to see which index to use.
color = colors[(value - (value % 25)) / 25];
Or if you prefer:
color = colors[Math.floor(value / 25)];
You could make it a two line statement, without arrays, by doing something similar to this:
var rgbvalue = 255-Math.floor(value/25);
var color = 'rgb('+rgbvalue+','+rgbvalue+','+rgbvalue+');';
Of course you would have to limit the value, so that the rgbvalue doesn't get smaller than 0, but I guess you can easily do that, if you know the possible values.
And if you want it to get dark faster, you can multiply the result of the Math.floor operation, like this:
var rgbvalue = 255-(Math.floor(value/25)*5);
And you have the advantage that you don't have to write a huge array of shades of gray.
More bullet-proof version (not fully -proof though)
var colors = ['#FFFFFF','#F8F8F8','#F0F0F0'];
/* this is not that necessary */
var value = input_value || default_input_value;
var color = colors[ Math.floor(value/25) ];
colors = {'#FFFFFF','#F8F8F8','#F0F0F0 '}
color=colors[(int)value/25];
You may need to adjust this depending on the range of value.
Ditch the && and cascade instead
if(values > 75){
//anything above 75 falls here
}
else if(value > 50){
//anything <= 75 but > 50 falls here
}
else if(values > 25){
//anything <= 50 but > 25 falls here
}
else {
//anything <= 25 falls here
}
You could use an array of objects that describe the color and the min and max of the range and then use a function to iterate through the array to find the color between the range.
function getColor(value) {
var colorRanges = [
{ color : '#FFFFFF', min : 0, max : 25 },
{ color : '#F8F8F8', min : 25, max : 50 },
{ color : '#F0F0F0', min : 50, max : 75 }
],
length = colorRanges.length;
while(length--) {
var colorRange = colorRanges[length];
if (value >= colorRange.min && value < colorRange.max) {
return colorRange.color;
}
}
// default color
return colorRanges[0].color;
}
With a little additional effort, you could expose a way to add new colors and ranges, have a default for the range interval, etc. If your colors and range interval are fixed however, this is probably overkill.

Categories