lepu-test-platform-web/node_modules/csso/lib/utils/list.js

390 lines
8.3 KiB
JavaScript
Raw Permalink Normal View History

//
// 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;