web-dev-qa-db-fra.com

Tampon circulaire en JavaScript

Quelqu'un a-t-il déjà implémenté un tampon circulaire en JavaScript? Comment feriez-vous cela sans avoir de pointeurs?

34
user191800

Étrange coïncidence, je viens d’en écrire un plus tôt aujourd’hui! Je ne sais pas quelles sont exactement vos exigences, mais cela pourrait être utile.

Il présente une interface comme un tableau de longueur illimitée, mais "oublie" d’anciens éléments:

// Circular buffer storage. Externally-apparent 'length' increases indefinitely
// while any items with indexes below length-n will be forgotten (undefined
// will be returned if you try to get them, trying to set is an exception).
// n represents the initial length of the array, not a maximum
function CircularBuffer(n) {
    this._array= new Array(n);
    this.length= 0;
}
CircularBuffer.prototype.toString= function() {
    return '[object CircularBuffer('+this._array.length+') length '+this.length+']';
};
CircularBuffer.prototype.get= function(i) {
    if (i<0 || i<this.length-this._array.length)
        return undefined;
    return this._array[i%this._array.length];
};
CircularBuffer.prototype.set= function(i, v) {
    if (i<0 || i<this.length-this._array.length)
        throw CircularBuffer.IndexError;
    while (i>this.length) {
        this._array[this.length%this._array.length]= undefined;
        this.length++;
    }
    this._array[i%this._array.length]= v;
    if (i==this.length)
        this.length++;
};
CircularBuffer.IndexError= {};
30
bobince
var createRingBuffer = function(length){

  var pointer = 0, buffer = []; 

  return {
    get  : function(key){return buffer[key];},
    Push : function(item){
      buffer[pointer] = item;
      pointer = (length + pointer +1) % length;
    }
  };
};

Mise à jour: si vous remplissez le tampon avec des chiffres uniquement, voici quelques plugins pour un liner:

min  : function(){return Math.min.apply(Math, buffer);},
sum  : function(){return buffer.reduce(function(a, b){ return a + b; }, 0);},
19
Torsten Becker

Comme beaucoup d'autres, j'ai aimé la solution de noiv , mais je voulais une API un peu plus agréable:

var createRingBuffer = function(length){
  /* https://stackoverflow.com/a/4774081 */
  var pointer = 0, buffer = []; 

  return {
    get  : function(key){
        if (key < 0){
            return buffer[pointer+key];
        } else if (key === false){
            return buffer[pointer - 1];
        } else{
            return buffer[key];
        }
    },
    Push : function(item){
      buffer[pointer] = item;
      pointer = (pointer + 1) % length;
      return item;
    },
    prev : function(){
        var tmp_pointer = (pointer - 1) % length;
        if (buffer[tmp_pointer]){
            pointer = tmp_pointer;
            return buffer[pointer];
        }
    },
    next : function(){
        if (buffer[pointer]){
            pointer = (pointer + 1) % length;
            return buffer[pointer];
        }
    }
  };
};

Améliorations par rapport à l'original:

  • get supporte l'argument par défaut (retourne le dernier élément déposé dans le tampon)
  • get supporte l'indexation négative (compte de droite)
  • prev déplace le tampon en arrière et retourne ce qu'il y a (comme popping sans delete)
  • next undoes prev (déplace le tampon et le renvoie)

J'ai utilisé cela pour stocker un historique de commandes que je pouvais parcourir dans une application à l'aide de ses méthodes prev et next, qui retournent joliment non définies lorsqu'elles n'ont nulle part où aller.

6
Two-Bit Alchemist

Ceci est une maquette rapide du code que vous pourriez utiliser (il ne fonctionne probablement pas et contient des bogues, mais il montre la façon dont cela pourrait être fait):

var CircularQueueItem = function(value, next, back) {
    this.next = next;
    this.value = value;
    this.back = back;
    return this;
};

var CircularQueue = function(queueLength){
    /// <summary>Creates a circular queue of specified length</summary>
    /// <param name="queueLength" type="int">Length of the circular queue</type>
    this._current = new CircularQueueItem(undefined, undefined, undefined);
    var item = this._current;
    for(var i = 0; i < queueLength - 1; i++)
    {
        item.next = new CircularQueueItem(undefined, undefined, item);
        item = item.next;
    }
    item.next = this._current;
    this._current.back = item;
}

CircularQueue.prototype.Push = function(value){
    /// <summary>Pushes a value/object into the circular queue</summary>
    /// <param name="value">Any value/object that should be stored into the queue</param>
    this._current.value = value;
    this._current = this._current.next;
};

CircularQueue.prototype.pop = function(){
    /// <summary>Gets the last pushed value/object from the circular queue</summary>
    /// <returns>Returns the last pushed value/object from the circular queue</returns>
    this._current = this._current.back;
    return this._current.value;
};

utiliser cet objet serait comme:

var queue = new CircularQueue(10); // a circular queue with 10 items
queue.Push(10);
queue.Push(20);
alert(queue.pop());
alert(queue.pop());

Vous pouvez bien sûr l'implémenter en utilisant aussi tableau avec une classe qui utiliserait en interne un tableau et conserverait une valeur de l'index de l'élément actuel et le déplacerait.

4
Robert Koritnik

J'utilise personnellement l'implémentation de Trevor Norris que vous pouvez trouver ici: https://github.com/trevnorris/cbuffer

et j'en suis assez content :-)

3
laurentIsRunning

Court et doux:

// IMPLEMENTATION
function CircularArray(maxLength) {
  this.maxLength = maxLength;
}

CircularArray.prototype = Object.create(Array.prototype);

CircularArray.prototype.Push = function(element) {
  Array.prototype.Push.call(this, element);
  while (this.length > this.maxLength) {
    this.shift();
  }
}

// USAGE
var ca = new CircularArray(2);
var i;

for (i = 0; i < 100; i++) {
  ca.Push(i);
}

console.log(ca[0]);
console.log(ca[1]);
console.log("Length: " + ca.length);

Sortie:

98
99
Length: 2
2
Oliver Heard

Je ne pouvais pas faire fonctionner le code de Robert Koritnik, je l'ai donc modifié comme suit:

    var CircularQueueItem = function (value, next, back) {
        this.next = next;
        this.value = value;
        this.back = back;
        return this;
    };

    var CircularQueue = function (queueLength) {
        /// <summary>Creates a circular queue of specified length</summary>
        /// <param name="queueLength" type="int">Length of the circular queue</type>
        this._current = new CircularQueueItem(undefined, undefined, undefined);
        var item = this._current;
        for (var i = 0; i < queueLength - 1; i++) {
            item.next = new CircularQueueItem(undefined, undefined, item);
            item = item.next;
        }
        item.next = this._current;
        this._current.back = item;

        this.Push = function (value) {
            /// <summary>Pushes a value/object into the circular queue</summary>
            /// <param name="value">Any value/object that should be stored into the queue</param>
            this._current.value = value;
            this._current = this._current.next;
        };
        this.pop = function () {
            /// <summary>Gets the last pushed value/object from the circular queue</summary>
            /// <returns>Returns the last pushed value/object from the circular queue</returns>
            this._current = this._current.back;
            return this._current.value;
        };
        return this;
    }

Utiliser:

    var queue = new CircularQueue(3); // a circular queue with 3 items
    queue.Push("a");
    queue.Push("b");
    queue.Push("c");
    queue.Push("d");
    alert(queue.pop()); // d
    alert(queue.pop()); // c
    alert(queue.pop()); // b
    alert(queue.pop()); // d
    alert(queue.pop()); // c
1
Mr. Flibble

Au lieu d'implémenter une file d'attente circulaire avec JavaScript, nous pouvons utiliser certaines fonctions intégrées de array pour réaliser l'implémentation d'une file d'attente circulaire.

exemple: Supposons que nous ayons besoin d'implémenter la file d'attente circulaire de longueur 4.

var circular = new Array();
var maxLength = 4;
var addElementToQueue = function(element){
    if(circular.length == maxLength){
        circular.pop();
    }
    circular.unshift(element);
};
addElementToQueue(1);
addElementToQueue(2);
addElementToQueue(3);
addElementToQueue(4);

Sortie:

circulaire [4, 3, 2, 1]

Si vous essayez d'ajouter un autre élément à ce tableau, par exemple: 

addElementToQueue(5);

Sortie:

circulaire [5, 4, 3, 2]

1
Puneet

J'aime vraiment comment noiv11 a résolu ce problème et pour mon besoin, j'ai ajouté une propriété supplémentaire 'buffer' qui renvoie le tampon:

var createRingBuffer = function(length){

  var pointer = 0, buffer = []; 

  return {
    get  : function(key){return buffer[key];},
    Push : function(item){
      buffer[pointer] = item;
      pointer = (length + pointer +1) % length;
    },
    buffer : buffer
  };
};

// sample use
var rbuffer = createRingBuffer(3);
rbuffer.Push('a');
rbuffer.Push('b');
rbuffer.Push('c');
alert(rbuffer.buffer.toString());
rbuffer.Push('d');
alert(rbuffer.buffer.toString());
var el = rbuffer.get(0);
alert(el);
1
soderlind

Prise sans scrupule:

Si vous recherchez un tampon node.js en rotation, j’en ai écrit un qui peut être trouvé ici: http://npmjs.org/packages/pivot-buffer

La documentation fait actuellement défaut, mais RotatingBuffer#Push vous permet d'ajouter un tampon au tampon actuel, en faisant pivoter les données précédentes si la nouvelle longueur est supérieure à la longueur spécifiée dans le constructeur.

0
Yotam Ofek

Je pense que vous devriez pouvoir le faire en utilisant simplement des objets. Quelque chose comme ça:

var link = function(next, value) {
    this.next = next;
    this.value = value;
};

var last = new link();
var second = link(last);
var first = link(second);
last.next = first;

Maintenant, vous venez de stocker la valeur dans la propriété value de chaque lien.

0
Jani Hartikainen

C'est très facile si vous maintenant ce que Array.prototype.length est:

function CircularBuffer(size) {
  Array.call(this,size); //superclass
  this.length = 0; //current index
  this.size = size; //buffer size
};

CircularBuffer.prototype = Object.create(Array.prototype);
CircularBuffer.prototype.constructor = CircularBuffer;
CircularBuffer.prototype.constructor.name = "CircularBuffer";

CircularBuffer.prototype.Push = function Push(elem){
  Array.prototype.Push.call(this,elem);
  if (this.length >= this.size) this.length = 0;
  return this.length;
}

CircularBuffer.prototype.pop = function pop(){
  var r = this[this.length];
  if (this.length <= 0) this.length = this.size;  
  this.length--;
  return r;
}
0
Uncle Leo

Merci noiv pour votre solution simple et efficace solution . Je devais aussi pouvoir accéder au tampon comme PerS l'a fait , mais je voulais obtenir les éléments dans l'ordre dans lequel ils ont été ajoutés. Alors voici ce que j'ai fini avec:

function buffer(capacity) {
    if (!(capacity > 0)) {
        throw new Error();
    }

    var pointer = 0, buffer = [];

    var publicObj = {
        get: function (key) {
            if (key === undefined) {
                // return all items in the order they were added
                if (pointer == 0 || buffer.length < capacity) {
                    // the buffer as it is now is in order
                    return buffer;
                }
                // split and join the two parts so the result is in order
                return buffer.slice(pointer).concat(buffer.slice(0, pointer));
            }
            return buffer[key];
        },
        Push: function (item) {
            buffer[pointer] = item;
            pointer = (capacity + pointer + 1) % capacity;
            // update public length field
            publicObj.length = buffer.length;
        },
        capacity: capacity,
        length: 0
    };

    return publicObj;
}

Voici la suite de tests:

QUnit.module("buffer");

QUnit.test("constructor", function () {
    QUnit.expect(4);

    QUnit.equal(buffer(1).capacity, 1, "minimum length of 1 is allowed");
    QUnit.equal(buffer(10).capacity, 10);

    QUnit.throws(
        function () {
            buffer(-1);
        },
        Error,
        "must throuw exception on negative length"
    );

    QUnit.throws(
        function () {
            buffer(0);
        },
        Error,
        "must throw exception on zero length"
    );
});

QUnit.test("Push", function () {
    QUnit.expect(5);

    var b = buffer(5);
    QUnit.equal(b.length, 0);
    b.Push("1");
    QUnit.equal(b.length, 1);
    b.Push("2");
    b.Push("3");
    b.Push("4");
    b.Push("5");
    QUnit.equal(b.length, 5);
    b.Push("6");
    QUnit.equal(b.length, 5);
    b.Push("7");
    b.Push("8");
    QUnit.equal(b.length, 5);
});

QUnit.test("get(key)", function () {
    QUnit.expect(8);

    var b = buffer(3);
    QUnit.equal(b.get(0), undefined);
    b.Push("a");
    QUnit.equal(b.get(0), "a");
    b.Push("b");
    QUnit.equal(b.get(1), "b");
    b.Push("c");
    QUnit.equal(b.get(2), "c");
    b.Push("d");
    QUnit.equal(b.get(0), "d");

    b = buffer(1);
    b.Push("1");
    QUnit.equal(b.get(0), "1");
    b.Push("2");
    QUnit.equal(b.get(0), "2");
    QUnit.equal(b.length, 1);
});

QUnit.test("get()", function () {
    QUnit.expect(7);

    var b = buffer(3);
    QUnit.deepEqual(b.get(), []);
    b.Push("a");
    QUnit.deepEqual(b.get(), ["a"]);
    b.Push("b");
    QUnit.deepEqual(b.get(), ["a", "b"]);
    b.Push("c");
    QUnit.deepEqual(b.get(), ["a", "b", "c"]);
    b.Push("d");
    QUnit.deepEqual(b.get(), ["b", "c", "d"]);
    b.Push("e");
    QUnit.deepEqual(b.get(), ["c", "d", "e"]);
    b.Push("f");
    QUnit.deepEqual(b.get(), ["d", "e", "f"]);
});
0
Muxa

Une approche consisterait à utiliser une liste chaînée, comme d'autres l'ont suggéré. Une autre technique consisterait à utiliser un tableau simple en tant que tampon et à garder une trace des positions de lecture et d'écriture via des index dans ce tableau.

0
user191817