'How to get numbers to cycle around specific range in JavaScript?

I have this which isn't working, for cycling like 1 2 3 4 5 6 7 8 9 1 2 3 4 ..., cycling between 1 and 9.

const cycle = integers => {
  let i = 0
  let x = integers[i++]
  while (i < integers.length) {
    let y = integers[i++]
    const s = x + y
    const remainder = (s % 9) + 1
    x = remainder
  }
  return (x - 1) % 9
}

logCycle([ 9, 6 ])
logCycle([ 9, 9 ])
logCycle([ 9, 9, 9, 9, 9 ])
logCycle([ 9, 129, 993, 91, 9 ])
logCycle([ 9, 10 ])

function logCycle(i) {
  log('cycle', i, cycle(i))
}

function log(t, i, o) {
  console.log(`${t}(${JSON.stringify(i)}) => ${o}`)
}

Notice how at cycle([9, 9]) it is returning 0, and other places it breaks down too. The cycle function takes an array of integers, of arbitrary length. So the array may have 1000000 items, or just 2.

I expect:

  • cycle([9, 9]) => 9
  • cycle([9, 10]) => 1

How do I get this to work properly? And how do I get it to work for any pair of min/max numbers, like if I wanted to cycle between (and including) 5 and 13, or 79 and 5337, how would I make it generic like that?

The cycle function is summing and doing modulus on the result, to have it "cycle" ideally within that range. It isn't iterating through the cycle, it is "summing through the cycle", like a clock.

Also looking to keep the solution non-recursive, as the number of elements in array might be large.



Solution 1:[1]

Apparently I misunderstood some parts of the question at first. After having read even further comments it seems to me that

  • the numbers to step through can be any previously defined sequence, like, from 1 to 9, from 5 to 13 or from 79 to 5337
  • the total number of steps for going (repeatedly) through the above sequence is given by the sum of all the arguments to the cycle function.

For these requirements the following should work:

function defCycle(m,n){
    const base=n-m+1,off=m-1;  // base: sequence-length,  off: offset of sequence
    return arr=>(arr.reduce((a,c)=>a+c)%base||base)+off;
};

// run the function on a collection of test data:
const ntest=1000000;
[[1,9],[5,13],[79,5337]]
.forEach(([m,n],cycle)=>{
  console.log(`Sequence from ${m} to ${n}:`);
  cycle=defCycle(m,n);
  [[ 9, 6 ],[ 9, 9 ],[ 9, 9, 9, 9, 9 ],[ 9, 129, 993, 91, 9 ],[ 9, 10 ],[1, 811, 29317, 391, 21],[ntest*(ntest+1)/2]]
  .forEach(arr=>console.log(`cycle(${JSON.stringify(arr)}) => ${cycle(arr)}`));
  console.log(`cycle([1,2,3,...,${ntest}]) => ${cycle([...Array(ntest)].map((_,i)=>i+1))}`);
});

The last two of the test cases should produce the exact same result: going through the sum of a sequence array from 1 to 1000000 ( = (1000000*1000001)/2) should result in 1 (for the sequence: "1...9").

The action within the defCycle() function happens in these two lines:

  1. base=n-m+1,off=m-1;
    base: the number of sequence elements
    off: the offset of the sequence
    example (sequence from 5 to 11): m=5,n=11 => base=7,off=4
  2. return arr=>(arr.reduce((a,c)=>a+c)%base||base)+off;
    this returns a singe-argument-function based on the previously calculated base and off. The calculation consists of the two expressions:
    • [shift] = (arr.reduce((a,c)=>a+c) sums up all the elements of the arr passed as argument - and
    • [shift]%base||base calculates the actual result: the modulo(base) of the total [shift]. Whenever the result ==0 it is replaced with the base value.

Solution 2:[2]

If you only need the result (no need to perform other operations while cycling), then you can calculate it fairly easily using base conversion.

function getResult(base, num) {
  const str = (parseInt((num-1).toString(base)) + 1).toString()
  return str.charAt(str.length - 1)
}

console.log( getResult(9, 6) ) // 6

console.log( getResult(9, 9) ) // 9

console.log( getResult(9, 10) ) // 1

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 GrafiCode