Quelqu'un a-t-il déjà implémenté un tampon circulaire en JavaScript? Comment feriez-vous cela sans avoir de pointeurs?
É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= {};
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);},
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.
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.
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 :-)
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
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
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);
circulaire [4, 3, 2, 1]
Si vous essayez d'ajouter un autre élément à ce tableau, par exemple:
addElementToQueue(5);
circulaire [5, 4, 3, 2]
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);
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.
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.
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;
}
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"]);
});
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.