0

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));
benvc
  • 14,448
  • 4
  • 33
  • 54
  • What output do you expect for `ranger(5, 100, 10)`? – Frank Fajardo Sep 15 '18 at 01:08
  • @FrankFajardo desired output for ranger(5, 100, 10) would be an array of 10 integers ascending from 5 to 100 where the difference between neighboring values decreases with each step - opposite of what the current code is doing where the difference in values is increasing with each step. – benvc Sep 15 '18 at 02:49
  • I'm not exactly sure what you mean by *where the difference.. decreases with each step*. Should the first step difference start with one? There is no pattern in your example output and your code. The output of your code is `[5, 9, 14, 21, 30, 41, 54, 69, 86, 105]` for `ranger(5, 100, 10)`. So I'm unsure why the first gap starts at 4 (the gap between 5 and 9). Also, your code generates `[1, 3, 6, 11, 16]` (for `ranger(1, 15, 5)`) which is different from your example output. – Frank Fajardo Sep 15 '18 at 07:55
  • @FrankFajardo you are right that there is no particular pattern in the examples since the progression would have to change for the function to handle ranges with different starts, stops and lengths. The examples are just meant to show the increasing and decreasing gaps but the answer does not have to produce the exact same output in the examples. – benvc Sep 15 '18 at 13:16

2 Answers2

1

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.

  • This is helpful as it does make the array values get progressively closer together when the array is descending, but they get farther apart when it is ascending (although that seems to be due to my question not being clear enough). This answer also generates float values for certain input sets. The primary goal of the question is to generate arrays of integers whose values get closer and closer together regardless of whether the array is ascending or descending (a bonus is to do the reverse for both ascending and descending - which the original question code is closer to achieving). – benvc Sep 15 '18 at 02:46
  • That said, your approach made me see the forest for the trees and I can probably use what you have done here to help sort out my original mess. Thanks. – benvc Sep 15 '18 at 02:53
  • Glad it helped. Let me know if you have trouble sorting the rest! – Brian Morris Sep 15 '18 at 15:09
0

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]
benvc
  • 14,448
  • 4
  • 33
  • 54