390 lines
8.3 KiB
JavaScript
390 lines
8.3 KiB
JavaScript
//
|
|
// item item item item
|
|
// /------\ /------\ /------\ /------\
|
|
// | data | | data | | data | | data |
|
|
// null <--+-prev |<---+-prev |<---+-prev |<---+-prev |
|
|
// | next-+--->| next-+--->| next-+--->| next-+--> null
|
|
// \------/ \------/ \------/ \------/
|
|
// ^ ^
|
|
// | list |
|
|
// | /------\ |
|
|
// \--------------+-head | |
|
|
// | tail-+--------------/
|
|
// \------/
|
|
//
|
|
|
|
function createItem(data) {
|
|
return {
|
|
data: data,
|
|
next: null,
|
|
prev: null
|
|
};
|
|
}
|
|
|
|
var List = function(values) {
|
|
this.cursor = null;
|
|
this.head = null;
|
|
this.tail = null;
|
|
|
|
if (Array.isArray(values)) {
|
|
var cursor = null;
|
|
|
|
for (var i = 0; i < values.length; i++) {
|
|
var item = createItem(values[i]);
|
|
|
|
if (cursor !== null) {
|
|
cursor.next = item;
|
|
} else {
|
|
this.head = item;
|
|
}
|
|
|
|
item.prev = cursor;
|
|
cursor = item;
|
|
}
|
|
|
|
this.tail = cursor;
|
|
}
|
|
};
|
|
|
|
Object.defineProperty(List.prototype, 'size', {
|
|
get: function() {
|
|
var size = 0;
|
|
var cursor = this.head;
|
|
|
|
while (cursor) {
|
|
size++;
|
|
cursor = cursor.next;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
});
|
|
|
|
List.createItem = createItem;
|
|
List.prototype.createItem = createItem;
|
|
|
|
List.prototype.toArray = function() {
|
|
var cursor = this.head;
|
|
var result = [];
|
|
|
|
while (cursor) {
|
|
result.push(cursor.data);
|
|
cursor = cursor.next;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
List.prototype.toJSON = function() {
|
|
return this.toArray();
|
|
};
|
|
|
|
List.prototype.isEmpty = function() {
|
|
return this.head === null;
|
|
};
|
|
|
|
List.prototype.first = function() {
|
|
return this.head && this.head.data;
|
|
};
|
|
|
|
List.prototype.last = function() {
|
|
return this.tail && this.tail.data;
|
|
};
|
|
|
|
List.prototype.each = function(fn, context) {
|
|
var item;
|
|
var cursor = {
|
|
prev: null,
|
|
next: this.head,
|
|
cursor: this.cursor
|
|
};
|
|
|
|
if (context === undefined) {
|
|
context = this;
|
|
}
|
|
|
|
// push cursor
|
|
this.cursor = cursor;
|
|
|
|
while (cursor.next !== null) {
|
|
item = cursor.next;
|
|
cursor.next = item.next;
|
|
|
|
fn.call(context, item.data, item, this);
|
|
}
|
|
|
|
// pop cursor
|
|
this.cursor = this.cursor.cursor;
|
|
};
|
|
|
|
List.prototype.eachRight = function(fn, context) {
|
|
var item;
|
|
var cursor = {
|
|
prev: this.tail,
|
|
next: null,
|
|
cursor: this.cursor
|
|
};
|
|
|
|
if (context === undefined) {
|
|
context = this;
|
|
}
|
|
|
|
// push cursor
|
|
this.cursor = cursor;
|
|
|
|
while (cursor.prev !== null) {
|
|
item = cursor.prev;
|
|
cursor.prev = item.prev;
|
|
|
|
fn.call(context, item.data, item, this);
|
|
}
|
|
|
|
// pop cursor
|
|
this.cursor = this.cursor.cursor;
|
|
};
|
|
|
|
List.prototype.nextUntil = function(start, fn, context) {
|
|
if (start === null) {
|
|
return;
|
|
}
|
|
|
|
var item;
|
|
var cursor = {
|
|
prev: null,
|
|
next: start,
|
|
cursor: this.cursor
|
|
};
|
|
|
|
if (context === undefined) {
|
|
context = this;
|
|
}
|
|
|
|
// push cursor
|
|
this.cursor = cursor;
|
|
|
|
while (cursor.next !== null) {
|
|
item = cursor.next;
|
|
cursor.next = item.next;
|
|
|
|
if (fn.call(context, item.data, item, this)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// pop cursor
|
|
this.cursor = this.cursor.cursor;
|
|
};
|
|
|
|
List.prototype.prevUntil = function(start, fn, context) {
|
|
if (start === null) {
|
|
return;
|
|
}
|
|
|
|
var item;
|
|
var cursor = {
|
|
prev: start,
|
|
next: null,
|
|
cursor: this.cursor
|
|
};
|
|
|
|
if (context === undefined) {
|
|
context = this;
|
|
}
|
|
|
|
// push cursor
|
|
this.cursor = cursor;
|
|
|
|
while (cursor.prev !== null) {
|
|
item = cursor.prev;
|
|
cursor.prev = item.prev;
|
|
|
|
if (fn.call(context, item.data, item, this)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// pop cursor
|
|
this.cursor = this.cursor.cursor;
|
|
};
|
|
|
|
List.prototype.some = function(fn, context) {
|
|
var cursor = this.head;
|
|
|
|
if (context === undefined) {
|
|
context = this;
|
|
}
|
|
|
|
while (cursor !== null) {
|
|
if (fn.call(context, cursor.data, cursor, this)) {
|
|
return true;
|
|
}
|
|
|
|
cursor = cursor.next;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
List.prototype.map = function(fn, context) {
|
|
var result = [];
|
|
var cursor = this.head;
|
|
|
|
if (context === undefined) {
|
|
context = this;
|
|
}
|
|
|
|
while (cursor !== null) {
|
|
result.push(fn.call(context, cursor.data, cursor, this));
|
|
cursor = cursor.next;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
List.prototype.copy = function() {
|
|
var result = new List();
|
|
var cursor = this.head;
|
|
|
|
while (cursor !== null) {
|
|
result.insert(createItem(cursor.data));
|
|
cursor = cursor.next;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
List.prototype.updateCursors = function(prevOld, prevNew, nextOld, nextNew) {
|
|
var cursor = this.cursor;
|
|
|
|
while (cursor !== null) {
|
|
if (prevNew === true || cursor.prev === prevOld) {
|
|
cursor.prev = prevNew;
|
|
}
|
|
|
|
if (nextNew === true || cursor.next === nextOld) {
|
|
cursor.next = nextNew;
|
|
}
|
|
|
|
cursor = cursor.cursor;
|
|
}
|
|
};
|
|
|
|
List.prototype.insert = function(item, before) {
|
|
if (before !== undefined && before !== null) {
|
|
// prev before
|
|
// ^
|
|
// item
|
|
this.updateCursors(before.prev, item, before, item);
|
|
|
|
if (before.prev === null) {
|
|
// insert to the beginning of list
|
|
if (this.head !== before) {
|
|
throw new Error('before doesn\'t below to list');
|
|
}
|
|
|
|
// since head points to before therefore list doesn't empty
|
|
// no need to check tail
|
|
this.head = item;
|
|
before.prev = item;
|
|
item.next = before;
|
|
|
|
this.updateCursors(null, item);
|
|
} else {
|
|
|
|
// insert between two items
|
|
before.prev.next = item;
|
|
item.prev = before.prev;
|
|
|
|
before.prev = item;
|
|
item.next = before;
|
|
}
|
|
} else {
|
|
// tail
|
|
// ^
|
|
// item
|
|
this.updateCursors(this.tail, item, null, item);
|
|
|
|
// insert to end of the list
|
|
if (this.tail !== null) {
|
|
// if list has a tail, then it also has a head, but head doesn't change
|
|
|
|
// last item -> new item
|
|
this.tail.next = item;
|
|
|
|
// last item <- new item
|
|
item.prev = this.tail;
|
|
} else {
|
|
// if list has no a tail, then it also has no a head
|
|
// in this case points head to new item
|
|
this.head = item;
|
|
}
|
|
|
|
// tail always start point to new item
|
|
this.tail = item;
|
|
}
|
|
};
|
|
|
|
List.prototype.remove = function(item) {
|
|
// item
|
|
// ^
|
|
// prev next
|
|
this.updateCursors(item, item.prev, item, item.next);
|
|
|
|
if (item.prev !== null) {
|
|
item.prev.next = item.next;
|
|
} else {
|
|
if (this.head !== item) {
|
|
throw new Error('item doesn\'t below to list');
|
|
}
|
|
|
|
this.head = item.next;
|
|
}
|
|
|
|
if (item.next !== null) {
|
|
item.next.prev = item.prev;
|
|
} else {
|
|
if (this.tail !== item) {
|
|
throw new Error('item doesn\'t below to list');
|
|
}
|
|
|
|
this.tail = item.prev;
|
|
}
|
|
|
|
item.prev = null;
|
|
item.next = null;
|
|
|
|
return item;
|
|
};
|
|
|
|
List.prototype.appendList = function(list) {
|
|
// ignore empty lists
|
|
if (list.head === null) {
|
|
return;
|
|
}
|
|
|
|
this.updateCursors(this.tail, list.tail, null, list.head);
|
|
|
|
// insert to end of the list
|
|
if (this.tail !== null) {
|
|
// if destination list has a tail, then it also has a head,
|
|
// but head doesn't change
|
|
|
|
// dest tail -> source head
|
|
this.tail.next = list.head;
|
|
|
|
// dest tail <- source head
|
|
list.head.prev = this.tail;
|
|
} else {
|
|
// if list has no a tail, then it also has no a head
|
|
// in this case points head to new item
|
|
this.head = list.head;
|
|
}
|
|
|
|
// tail always start point to new item
|
|
this.tail = list.tail;
|
|
|
|
list.head = null;
|
|
list.tail = null;
|
|
};
|
|
|
|
module.exports = List;
|