Reputation: 15130
I am trying to generate 2 sequences of integers: one where the difference in neighboring values steadily increases and another where the difference steadily decreases.
Example desired output:
Ascending with increasing difference: [1, 3, 6, 10, 15]
Descending with increasing difference: [15, 13, 10, 6, 1]
Ascending with decreasing difference: [1, 6, 10, 13, 15]
Descending with decreasing difference: [15, 10, 6, 3, 1]
The code snippet below generates an array where the difference in neighboring values steadily increases (but does not yet respect the stop value input). I am stuck on the right math for creating a similar array where the differences in neighboring values steadily decrease.
const ranger = (start = 0, stop = 0, len = 1) => {
const d = (start <= stop) ? 1 : -1;
const step = Math.round(Math.abs(stop - start) / len);
const arr = Array.from(Array(len), (x, i) => i + 1)
.map((x) => (x !== 1) ? Math.round(start + d * x * step * (x / len)) : start);
return arr;
};
console.log('asc', ranger(5, 100, 10));
console.log('desc', ranger(100, 5, 10));
Upvotes: 0
Views: 923
Reputation: 15130
My original solution was more complicated than it needed to be and unable to handle zero and negative values (see edit history for details). Following is a somewhat better approach to generating integer sequence arrays with either increasing or decreasing differences between consecutive numbers (although it is still inefficient).
// requires non-negative value for len
const georange = (start, stop, len, rev = false) => {
const shift = (() => {
if (start < 1 || stop < 1) {
return start < stop ? 1 - start : 1 - stop;
}
return 0;
})();
const gstart = start + shift;
const gstop = stop + shift;
const gstep = Math.pow(gstop / gstart, 1 / (len - 1));
let range = Array(len).fill(gstart).map((x, i) => Math.round(x * Math.pow(gstep, i)));
if (rev) {
const steps = range.map((x, i, arr) => (i === 0) ? 0 : x - arr[i - 1]).reverse();
range = steps.map((_, i, arr) => arr.slice(0, i).reduce((acc, x) => acc + x, gstart));
}
if (shift) {
range = range.map((x) => x - shift);
}
return range;
};
const ascincr = georange(1, 15, 5);
console.log(ascincr);
// [1, 2, 4, 8, 15]
const ascdecr = georange(1, 15, 5, true);
console.log(ascdecr);
// [1, 8, 12, 14, 15]
const descincr = georange(15, 1, 5, true);
console.log(descincr);
// [15, 14, 12, 8, 1]
const descdecr = georange(15, 1, 5);
console.log(descdecr);
// [15, 8, 4, 2, 1]
This approach uses the basic math behind geometric sequences and twists it a bit to generate either ascending sequences with increasing differences or descending sequences with decreasing differences (regardless of whether input values are positive or negative - departing from the normal behavior of a geometric sequence). Setting the rev
flag to true
gets the differences in consecutive terms produced by an ordinary geometric sequence and reverses them to generate either ascending sequences with decreasing differences or descending sequences with increasing differences.
This revision resulted from looking at a simple range generation approach like the one below and other similar approaches from this question, then testing ways to derive a dynamic step value from a defined length (as opposed to having a defined step determine the length).
const range = (start, stop, step) => {
// requires positive step for ascending ranges and negative step for descending ranges
return Array(Math.ceil((stop - start) / step)).fill(start).map((x, i) => x + i * step);
};
const result = range(1, 15, 3);
console.log(result);
// [1, 4, 7, 10, 13]
Upvotes: 0
Reputation: 21
it looks like your method to determine your step is a little bit off.
lets first look at how to determine a proper step.
Here is our test range: [1,3,6,10,15]. The differences between the numbers is [2,3,4,5] we can represent the differences as : x + 1, x + 2, x + 3, x + 4. so we can combine these into 4x + 10. This is our total number incremented and it equals our stop - start (15 - 1) which is 14.
so our revised equation is 14 = 4x + 10 which when solved gives us x = 1;
4 here represents the number of steps, and 10 is the sum of the number of steps. we can use carl gauss's formula to determine the sum. the formula is (n / 2)(first number + last number) = sum
here n is the number of steps. and in our equation the number of steps will always be len - 1. While the last number will always be 1.
so our translated gauss' formula is (len - 1) / 2 * (len - 1 + 1)
we then plug that into our formula to determine the step: step = (Math.abs(stop - start) - (len - 1)/2 * (len - 1 + 1))/(len - 1)
SKIP TO HERE IF YOU AREN'T INTERESTED IN THE MATH TO FIND THE STEP
Alright. Now that we correctly found the step. lets see some code.
function ranger(start = 0, stop = 0, len = 1) {
let incArray = [];
let step = (Math.abs(stop - start) - (len - 1)/2*(len - 1 + 1))/(len - 1);
console.log('step is', step);
if(start < stop){
for(var i = 0, currentValue = start; currentValue < stop; i++){
//use default value on our first iteration of the loop
if(i> 0){
//we are grabbing the previous item in the array and adding
//the step plus i to it
//for ranger(1, 15, 5)) when i = 1, step = 1, incArray[1], so
// current value = 1 + 1 + 1 = 3;
currentValue = (i + step + incArray[incArray.length -1])
}
incArray.push(currentValue)
}
}
else{
for(var i = len, currentValue = start; currentValue > stop; i--){
if(i< len ){
currentValue = (-(i + step) + incArray[incArray.length -1])
}
incArray.push(currentValue)
prevValue = currentValue;
}
}
return incArray;
}
console.log('asc', ranger(1, 15, 5));
console.log('asc', ranger(1, 21, 6));
console.log('desc', ranger(15, 1, 5));
//now try a range with a step of 2:
console.log('asc', ranger(1, 19, 5));
// asc (5) [1, 3, 6, 10, 15]
// desc (5) [15, 10, 6, 3, 1]
This is just a rough draft, so you can cleanly refactor with map, and make it work more dynamically for ascending and descending.
Upvotes: 1