有没有人在JavaScript中实现了循环缓冲区?如果没有指针,你会怎么做?
答案 0 :(得分:30)
奇怪的共同发病率,我今天刚刚写了一篇!我不知道你的要求究竟是什么,但这可能有用。
它呈现的界面就像一个无限长度的数组,但“忘记”旧项目:
// 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= {};
答案 1 :(得分:19)
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;
}
};
};
更新:如果你只用数字填充缓冲区,这里有一些衬里插件:
min : function(){return Math.min.apply(Math, buffer);},
sum : function(){return buffer.reduce(function(a, b){ return a + b; }, 0);},
答案 2 :(得分:6)
和其他许多人一样,我喜欢noiv's solution,但我想要一个更好的API:
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];
}
}
};
};
原始改进:
get
支持默认参数(将最后一项推送到缓冲区)get
支持否定索引(从右计算)prev
将缓冲区移回一个并返回其中的内容(如弹出而不删除)next
撤消prev(向前移动缓冲区并将其返回)我用它来存储一个命令历史记录,然后我可以使用它的prev
和next
方法在应用程序中翻转这些方法,当它们无处可去时,很好地返回undefined。
答案 3 :(得分:4)
这是你可以使用的代码的快速模型(它可能不起作用并且有bug,但它显示了它可以完成的方式):
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;
};
使用这个对象就像:
var queue = new CircularQueue(10); // a circular queue with 10 items
queue.push(10);
queue.push(20);
alert(queue.pop());
alert(queue.pop());
你当然可以使用数组实现它,并使用一个内部使用数组的类,并保留当前项索引的值并移动它。
答案 4 :(得分:3)
我个人使用Trevor Norris的实现,你可以在这里找到: https://github.com/trevnorris/cbuffer
我很满意: - )
答案 5 :(得分:2)
短而甜蜜:
// 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);
输出:
98
99
Length: 2
答案 6 :(得分:1)
我真的很喜欢noiv11 solved this,为了我的需要,我添加了一个额外的属性'buffer'来返回缓冲区:
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);
答案 7 :(得分:1)
我无法让Robert Koritnik的代码工作,因此我将其编辑为以下似乎有用的代码:
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;
}
使用:
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
答案 8 :(得分:1)
我们可以使用一些内置的数组函数来实现循环队列实现,而不是用JavaScript实现循环队列。
例如: 假设我们需要实现长度为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);
圆形 [4,3,2,1]
如果您尝试将另一个元素添加到此数组,例如:
addElementToQueue(5);
圆形 [5,4,3,2]
答案 9 :(得分:0)
感谢你的简单而有效solution。我还需要能够像PerS did那样访问缓冲区,但我希望按照添加顺序获取项目。所以这就是我最终的结果:
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;
}
以下是测试套件:
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"]);
});
答案 10 :(得分:0)
一种方法是使用其他人建议的链表。另一种技术是使用一个简单的数组作为缓冲区,并通过索引进入该数组来跟踪读写位置。
答案 11 :(得分:0)
我认为你应该能够通过使用对象来做到这一点。像这样:
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;
现在,您只需将值存储在每个链接的value属性中。
答案 12 :(得分:0)
无耻的自我插件:
如果你正在寻找一个旋转的node.js缓冲区,我写了一个可以在这里找到的:http://npmjs.org/packages/pivot-buffer
目前缺少文档,但RotatingBuffer#push
允许您将缓冲区附加到当前缓冲区,如果新长度大于构造函数中指定的长度,则旋转先前的数据。
答案 13 :(得分:0)
如果您现在Array.prototype.length是什么,这将非常简单:
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;
}
答案 14 :(得分:0)
近10年后,使用JavaScript ES6给出了答案:
class CircularBuffer {
constructor(bufferLength) {
this.buffer = [];
this.pointer = 0;
this.bufferLength = bufferLength;
}
push(element) {
if(this.buffer.length === this.bufferLength) {
this.buffer[this.pointer] = element;
} else {
this.buffer.push(element);
}
this.pointer = (this.pointer + 1) % this.bufferLength;
}
get(i) {
return this.buffer[i];
}
//Gets the ith element before last one
getLast(i) {
return this.buffer[this.pointer+this.bufferLength-1-i];
}
}
//To use it:
let circularBuffer = new CircularBuffer(3);
circularBuffer.push('a');
circularBuffer.push('b');
circularBuffer.push('c');
// should print a,b,c
console.log(`0 element: ${circularBuffer.get(0)}; 1 element: ${circularBuffer.get(1)}; 2 element: ${circularBuffer.get(2)};`);
console.log('Last element: '+circularBuffer.getLast(0)); // should print 'c'
circularBuffer.push('d');
// should print d,b,c
console.log(`0 element: ${circularBuffer.get(0)}; 1 element: ${circularBuffer.get(1)}; 2 element: ${circularBuffer.get(2)};`);
答案 15 :(得分:0)
我更喜欢简单的方法。这应该是三班轮的IMO。
类似
const makeRing = sz => ({ sz, buf: new Array(size) }),
at = ({sz, buf}, pos) => buf[pos % sz],
set = ({sz, buf}, pos, to) => buf[pos % sz] = to;
那你就可以
const ring = makeRing(10);
ring.buf.fill(1);
set(ring, 35, 'TWO!');
console.log(ring.buf);
console.log(at(ring, 65));
答案 16 :(得分:0)
很多答案,但没有看到类似以下功能简单的方法……(ES6):
const circularAdd = maxSize => (queue, newItem) =>
queue.concat([newItem]).slice(Math.max(0, queue.length + 1 - maxSize));
可用作还原剂。例如。在scan
的可见流中。
// Suppose newItem$ is a simple new value emitter
const itemBuffer$ = newItem$.pipe(scan(circularAdd(100), []));
// itemBuffer$ will now emit arrays with max 100 items, where the new item is last
我现在看不到这个特定问题的答案,因为它没有提供阅读位置...:)
答案 17 :(得分:0)
我没有进行任何性能检查,但是据我了解,顺序数组访问应该比链接列表更快。我还注意到,多个实现都遭受了基于ES3(至少)的原型过时(至少)的困扰,这使我大吃一惊。而且它们都不支持动态大小增加,即“增长”。因此,这就是我如何看待此实现。随时根据您的需求进行扩展:
export class Dequeue<T> {
private buffer: T[];
private head: number;
private tail: number;
private size: number;
constructor(initialCapacity: number) {
this.buffer = [];
this.buffer.length = initialCapacity;
this.head = this.tail = this.size = 0;
}
public enqueue(value: T): T {
let next = this.head + 1;
let buffer = this.buffer;
let length = buffer.length;
if (length <= next) {
next -= length;
}
if (buffer.length <= this.size) {
buffer.length += length;
for (let index = next; index < length; index++) {
buffer[index + length] = buffer[index];
}
}
this.size++;
buffer[this.head] = value;
this.head = next;
return value;
}
public dequeue(): T | undefined {
if (this.size > 0) {
this.size--;
let buffer = this.buffer;
let length = buffer.length;
let value = buffer[this.tail];
let next = this.tail + 1;
if (length <= next) {
next -= length;
}
this.tail = next;
return value;
} else {
return undefined;
}
}
public get length() {
return this.size;
}
}
为防止undefined
在界面中传播,我建议使用以下版本:
export function Throw(message: string): never {
throw new Error(message);
}
export class Dequeue<T> {
private buffer: T[];
private head: number;
private tail: number;
private size: number;
constructor(initialCapacity: number) {
this.buffer = [];
this.buffer.length = initialCapacity;
this.head = this.tail = this.size = 0;
}
public enqueue(value: T): T {
let next = this.head + 1;
let buffer = this.buffer;
let length = buffer.length;
if (length <= next) {
next -= length;
}
if (buffer.length <= this.size) {
buffer.length += length;
for (let index = next; index < length; index++) {
buffer[index + length] = buffer[index];
}
}
this.size++;
buffer[this.head] = value;
this.head = next;
return value;
}
public dequeue(defaultValueFactory: () => T = () => Throw('No elements to dequeue')): T {
if (this.size > 0) {
this.size--;
let buffer = this.buffer;
let length = buffer.length;
let value = buffer[this.tail];
let next = this.tail + 1;
if (length <= next) {
next -= length;
}
this.tail = next;
return value;
} else {
return defaultValueFactory();
}
}
public get length() {
return this.size;
}
}
答案 18 :(得分:0)
这是我的看法。具体来说,这是圆形/环形滑动缓冲区的非常简单的对象实现。
一点便笺。尽管人们称呼它类似的名字,例如“圆形”,“环”,“队列”,但值得一说,因为它们含义不同。
环形/圆形队列。您可以将元素添加到头部,然后从末尾进行裁剪。最小大小为0,最大大小为基础数组的大小。队列环绕基础数组。
同一件事,一个队列,FIFO,先进先出,但最大大小可变(不确定),并使用标准push()和unshift()数组方法实现。要添加元素,只需将它推入()到数组上,然后使用unshift()来消耗元素,就这样,它是非常标准的函数,不需要发明任何东西。
一个固定大小的滑动缓冲区,其中新元素添加到头部(右侧),该缓冲区向后滑动(左侧),最左边的多余元素自动丢失。从概念上说,它是一个滑动缓冲区,只是碰巧可以最有效地实现为圆形/环形。
这是(3)种的实现。这可以用作数据可视化窗口小部件的后端,并且主要用于此目的。实时监测的滑动线图。
对象:
function make_CRS_Buffer(size) {
return {
A: [],
Ai: 0,
Asz: size,
add: function(value) {
this.A[ this.Ai ] = value;
this.Ai = (this.Ai + 1) % this.Asz;
},
forall: function(callback) {
var mAi = this.A.length < this.Asz ? 0 : this.Ai;
for (var i = 0; i < this.A.length; i++) {
callback(this.A[ (mAi + i) % this.Asz ]);
}
}
};
}
用法:
var buff1 = make_CRS_Buffer(5);
buff1.add(cnt);
buff1.forall(value => {
b1.innerHTML += value + " ";
});
还有一个完整的功能示例,其中两个缓冲区并行运行:
var b1 = document.getElementById("b1");
var b2 = document.getElementById("b2");
var cnt = 0;
var buff1 = make_CRS_Buffer(5);
var buff2 = make_CRS_Buffer(12);
function add() {
buff1.add(cnt);
buff2.add(cnt);
cnt ++;
b1.innerHTML = "";
buff1.forall(value => {
b1.innerHTML += value + " ";
});
b2.innerHTML = "";
buff2.forall(value => {
b2.innerHTML += value + " ";
});
}
function make_CRS_Buffer(size) {
return {
A: [],
Ai: 0,
Asz: size,
add: function(value) {
this.A[ this.Ai ] = value;
this.Ai = (this.Ai + 1) % this.Asz;
},
forall: function(callback) {
var mAi = this.A.length < this.Asz ? 0 : this.Ai;
for (var i = 0; i < this.A.length; i++) {
callback(this.A[ (mAi + i) % this.Asz ]);
}
}
};
}
<!DOCTYPE html>
<html>
<body>
<h1>Circular/Ring Sliding Buffer</h1>
<p><i>(c) 2020, Leonid Titov</i>
<div id="b1" style="
background-color: hsl(0,0%,80%);
padding: 5px;
">empty</div>
<div id="b2" style="
background-color: hsl(0,0%,80%);
padding: 5px;
">empty</div>
<br>
<button onclick="add()">Add one more</button>
</body>
</html>
希望它会有用。
答案 19 :(得分:0)
我建议使用 TypeScript 循环数组实现:https://gist.github.com/jerome-benoit/c251bdf872473d1f86ea3a8b90063c90。 精简,API 与标准数组对象相同。