How to access a circular array?

Asked

Viewed 620 times

23

Whereas I have a array:

const a = ['A', 'B', 'C'];

I would like to create a function that returns an item and, with each call, returns the subsequent one, and when it comes to the end, returns the first one again. That is to say:

  • Call 1 returns A;
  • Call 2 returns B;
  • Call 3 returns C;
  • Call 4 returns A;
  • Call 5 returns B;

And so on and so forth.

7 answers

26


WARNING

Rereading this answer two years later, NAY It seems to me a good use case for an iterator... It has no need, it is only syntactic firula and probably has an impact on performance. I must have written this as a way of studying Terators at the time, and to put one more method of doing this among the answers. If I were to answer today, I’d probably suggest something like what Augusto Vasques suggested or the hkotsubo.

Then I reinforce the warning: you probably don’t need an iterator for this!!!

Seems like a good use case for a iterator (created here with a Generator Function):

function* indiceArray(tamanho) {
    let indice = 0;
    while(true) {
        yield indice;
        indice = (indice + 1) % tamanho;
    }
    return indice;
}

const a = ['A', 'B', 'C']
const indice = indiceArray(a.length);

console.log(a[indice.next().value])
console.log(a[indice.next().value])
console.log(a[indice.next().value])
console.log(a[indice.next().value])

  • 2

    I made a benchmark out of curiosity and really the difference in performance is drastic... See here. Anyway, I still think it’s a super interesting solution. A pity that the performance of iterators is still bad (and in view of what they do should be excellent). I hope that one day this will improve.

14

An alternative is to use closures:

function makeCircular(arr) {
  var current = 0;
  return function() {
    return arr[current++ % arr.length];
  }
}

const a = ['A', 'B', 'C'];
let next = makeCircular(a);

// imprime A, B, C, A, B, C...
for(i = 0; i < 20; i++) {
    console.log(next());
}

makeCircular(a) receives an array and returns another function, which in turn returns, at each call, the next circular array value.

You can even create several closures different for the same array, and each maintains its individual state:

function makeCircular(arr) {
  var current = 0;
  return function() {
    return arr[current++ % arr.length];
  }
}

const a = ['A', 'B', 'C'];
let next = makeCircular(a);
let next2 = makeCircular(a);

console.log(next()); // A
console.log(next()); // B
console.log(next2()); // A
console.log(next()); // C


Only one detail: the internal counter (current) may give problem if it reaches the value of Number.MAX_VALUE:

function makeCircular(arr) {
  var current = Number.MAX_VALUE; // começando em MAX_VALUE só pra mostrar o problema
  return function() {
    return arr[current++ % arr.length];
  }
}

const a = ['A', 'B', 'C'];
let next = makeCircular(a);

// imprime C 20 vezes
for(i = 0; i < 20; i++) {
    console.log(next());
}

In this case, you printed "C" 20 times, indicating that when you reach MAX_VALUE, it can no longer increase the value (tested in Chrome and Firefox and the behavior was the same).

All right that according to documentation, the value of MAX_VALUE is about 21024, which should be more than sufficient for most applications.

But if you want to avoid this - rare - situation, you can keep the value of current always smaller than the array size:

function makeCircular(arr) {
  var current = -1;
  return function() {
    current = (current + 1) % arr.length;
    return arr[current];
  }
}

const a = ['A', 'B', 'C'];
let next = makeCircular(a);

// imprime A, B, C, A, B, C...
for(i = 0; i < 20; i++) {
    console.log(next());
}

11

The following function returns the item subsequent to each call

var arr = ["A", "B", "C"];

function getItem(arr) {
    arr.push(arr.shift());
    return arr[arr.length - 1];
}

console.log(getItem(arr));
console.log(getItem(arr));
console.log(getItem(arr));
console.log(getItem(arr));
console.log(getItem(arr));

The operation is based on pile. The first element of the array is always removed and placed at the last position, that is, at the top. At each function call, the array structure is updated through the arr.push() responsible for stacking the first element removed by the function arr.shift().

Reference

  • And that alert, the slice and the join are for what?

  • 1

    @Sorack removed :)

6

If you don’t want to waste time:

To make an array use circular indexes at the end just use the formula:

indíce normalizado = resto(indice estrapolado, comprimento da array)

Remembering that rest in Javascript is %.

const arr = ['A', 'B', 'C'];

const len = arr.length;

for (i = 0; i < 10; i++) {
  console.log("arr[" + i + "]= " + arr[i % len]);
}

6

An alternative would also be to have the option of setting a limit to its function, that is to say, repeating the values of the array up to a certain amount. For this, you could define a generator and control the amount of previously returned items:

function* repeat(array, n = Infinity) {
    loop: while (true) {
        for (const item of array) {
            if (!n--) {
                break loop;
            }

            yield item;
        }
    }
}

Thus, making repeat([1, 2, 3]) will repeat the three values infinitely, but if you assign a limit in n, repeat([1, 2, 3], 5), will repeat until generate five values, [1, 2, 3, 1, 2].

function* repeat(array, n = Infinity) {
  loop: while (true) {
    for (const item of array) {
      if (!n--) {
        break loop;
      }

      yield item;
    }
  }
}

const items = repeat([1, 2, 3], 5);

for (const item of items) {
  console.log(item);
}

5

Good I will enter the game also and suggest adding a non-interable property to your array:

function adicionaNext(obj) {
  Object.defineProperty(obj, 'next', {
    value: (i => function() { return this[i++ % this.length] })(0)
  })
}

const arr = ['A', 'B', 'C']
adicionaNext(arr)

console.log(arr.next())
console.log(arr.next())
console.log(arr.next())
console.log(arr.next())
console.log(arr.next())

2

Using closures, it is possible to generate a function foo which, whenever invoked, returns the next element of the array, although it is modified during the code.

function getElement(list) {
    if (!Array.isArray(list)) {
        throw new Error('list deve ser array.');
    }

    let index = list.length - 1;

    function next() {
        index = (index + 1)%list.length;
        return list[index];
    }

    return next;
}

const a = ['A', 'B', 'C'];
const foo = getElement(a);

// testando
console.log('a = ', a);
for (var i = 0; i < 10; i++) {
    console.log(foo());
}
console.log('inserindo D');
a.push('D');
console.log('a = ', a);
for (var i = 0; i < 10; i++) {
    console.log(foo());
}
console.log('deletando B, C');
a.splice(1, 2);
console.log('a = ', a);
for (var i = 0; i < 10; i++) {
    console.log('->', foo());
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.