list.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. //
  2. // item item item item
  3. // /------\ /------\ /------\ /------\
  4. // | data | | data | | data | | data |
  5. // null <--+-prev |<---+-prev |<---+-prev |<---+-prev |
  6. // | next-+--->| next-+--->| next-+--->| next-+--> null
  7. // \------/ \------/ \------/ \------/
  8. // ^ ^
  9. // | list |
  10. // | /------\ |
  11. // \--------------+-head | |
  12. // | tail-+--------------/
  13. // \------/
  14. //
  15. function createItem(data) {
  16. return {
  17. data: data,
  18. next: null,
  19. prev: null
  20. };
  21. }
  22. var List = function(values) {
  23. this.cursor = null;
  24. this.head = null;
  25. this.tail = null;
  26. if (Array.isArray(values)) {
  27. var cursor = null;
  28. for (var i = 0; i < values.length; i++) {
  29. var item = createItem(values[i]);
  30. if (cursor !== null) {
  31. cursor.next = item;
  32. } else {
  33. this.head = item;
  34. }
  35. item.prev = cursor;
  36. cursor = item;
  37. }
  38. this.tail = cursor;
  39. }
  40. };
  41. Object.defineProperty(List.prototype, 'size', {
  42. get: function() {
  43. var size = 0;
  44. var cursor = this.head;
  45. while (cursor) {
  46. size++;
  47. cursor = cursor.next;
  48. }
  49. return size;
  50. }
  51. });
  52. List.createItem = createItem;
  53. List.prototype.createItem = createItem;
  54. List.prototype.toArray = function() {
  55. var cursor = this.head;
  56. var result = [];
  57. while (cursor) {
  58. result.push(cursor.data);
  59. cursor = cursor.next;
  60. }
  61. return result;
  62. };
  63. List.prototype.toJSON = function() {
  64. return this.toArray();
  65. };
  66. List.prototype.isEmpty = function() {
  67. return this.head === null;
  68. };
  69. List.prototype.first = function() {
  70. return this.head && this.head.data;
  71. };
  72. List.prototype.last = function() {
  73. return this.tail && this.tail.data;
  74. };
  75. List.prototype.each = function(fn, context) {
  76. var item;
  77. var cursor = {
  78. prev: null,
  79. next: this.head,
  80. cursor: this.cursor
  81. };
  82. if (context === undefined) {
  83. context = this;
  84. }
  85. // push cursor
  86. this.cursor = cursor;
  87. while (cursor.next !== null) {
  88. item = cursor.next;
  89. cursor.next = item.next;
  90. fn.call(context, item.data, item, this);
  91. }
  92. // pop cursor
  93. this.cursor = this.cursor.cursor;
  94. };
  95. List.prototype.eachRight = function(fn, context) {
  96. var item;
  97. var cursor = {
  98. prev: this.tail,
  99. next: null,
  100. cursor: this.cursor
  101. };
  102. if (context === undefined) {
  103. context = this;
  104. }
  105. // push cursor
  106. this.cursor = cursor;
  107. while (cursor.prev !== null) {
  108. item = cursor.prev;
  109. cursor.prev = item.prev;
  110. fn.call(context, item.data, item, this);
  111. }
  112. // pop cursor
  113. this.cursor = this.cursor.cursor;
  114. };
  115. List.prototype.nextUntil = function(start, fn, context) {
  116. if (start === null) {
  117. return;
  118. }
  119. var item;
  120. var cursor = {
  121. prev: null,
  122. next: start,
  123. cursor: this.cursor
  124. };
  125. if (context === undefined) {
  126. context = this;
  127. }
  128. // push cursor
  129. this.cursor = cursor;
  130. while (cursor.next !== null) {
  131. item = cursor.next;
  132. cursor.next = item.next;
  133. if (fn.call(context, item.data, item, this)) {
  134. break;
  135. }
  136. }
  137. // pop cursor
  138. this.cursor = this.cursor.cursor;
  139. };
  140. List.prototype.prevUntil = function(start, fn, context) {
  141. if (start === null) {
  142. return;
  143. }
  144. var item;
  145. var cursor = {
  146. prev: start,
  147. next: null,
  148. cursor: this.cursor
  149. };
  150. if (context === undefined) {
  151. context = this;
  152. }
  153. // push cursor
  154. this.cursor = cursor;
  155. while (cursor.prev !== null) {
  156. item = cursor.prev;
  157. cursor.prev = item.prev;
  158. if (fn.call(context, item.data, item, this)) {
  159. break;
  160. }
  161. }
  162. // pop cursor
  163. this.cursor = this.cursor.cursor;
  164. };
  165. List.prototype.some = function(fn, context) {
  166. var cursor = this.head;
  167. if (context === undefined) {
  168. context = this;
  169. }
  170. while (cursor !== null) {
  171. if (fn.call(context, cursor.data, cursor, this)) {
  172. return true;
  173. }
  174. cursor = cursor.next;
  175. }
  176. return false;
  177. };
  178. List.prototype.map = function(fn, context) {
  179. var result = [];
  180. var cursor = this.head;
  181. if (context === undefined) {
  182. context = this;
  183. }
  184. while (cursor !== null) {
  185. result.push(fn.call(context, cursor.data, cursor, this));
  186. cursor = cursor.next;
  187. }
  188. return result;
  189. };
  190. List.prototype.copy = function() {
  191. var result = new List();
  192. var cursor = this.head;
  193. while (cursor !== null) {
  194. result.insert(createItem(cursor.data));
  195. cursor = cursor.next;
  196. }
  197. return result;
  198. };
  199. List.prototype.updateCursors = function(prevOld, prevNew, nextOld, nextNew) {
  200. var cursor = this.cursor;
  201. while (cursor !== null) {
  202. if (prevNew === true || cursor.prev === prevOld) {
  203. cursor.prev = prevNew;
  204. }
  205. if (nextNew === true || cursor.next === nextOld) {
  206. cursor.next = nextNew;
  207. }
  208. cursor = cursor.cursor;
  209. }
  210. };
  211. List.prototype.insert = function(item, before) {
  212. if (before !== undefined && before !== null) {
  213. // prev before
  214. // ^
  215. // item
  216. this.updateCursors(before.prev, item, before, item);
  217. if (before.prev === null) {
  218. // insert to the beginning of list
  219. if (this.head !== before) {
  220. throw new Error('before doesn\'t below to list');
  221. }
  222. // since head points to before therefore list doesn't empty
  223. // no need to check tail
  224. this.head = item;
  225. before.prev = item;
  226. item.next = before;
  227. this.updateCursors(null, item);
  228. } else {
  229. // insert between two items
  230. before.prev.next = item;
  231. item.prev = before.prev;
  232. before.prev = item;
  233. item.next = before;
  234. }
  235. } else {
  236. // tail
  237. // ^
  238. // item
  239. this.updateCursors(this.tail, item, null, item);
  240. // insert to end of the list
  241. if (this.tail !== null) {
  242. // if list has a tail, then it also has a head, but head doesn't change
  243. // last item -> new item
  244. this.tail.next = item;
  245. // last item <- new item
  246. item.prev = this.tail;
  247. } else {
  248. // if list has no a tail, then it also has no a head
  249. // in this case points head to new item
  250. this.head = item;
  251. }
  252. // tail always start point to new item
  253. this.tail = item;
  254. }
  255. };
  256. List.prototype.remove = function(item) {
  257. // item
  258. // ^
  259. // prev next
  260. this.updateCursors(item, item.prev, item, item.next);
  261. if (item.prev !== null) {
  262. item.prev.next = item.next;
  263. } else {
  264. if (this.head !== item) {
  265. throw new Error('item doesn\'t below to list');
  266. }
  267. this.head = item.next;
  268. }
  269. if (item.next !== null) {
  270. item.next.prev = item.prev;
  271. } else {
  272. if (this.tail !== item) {
  273. throw new Error('item doesn\'t below to list');
  274. }
  275. this.tail = item.prev;
  276. }
  277. item.prev = null;
  278. item.next = null;
  279. return item;
  280. };
  281. List.prototype.appendList = function(list) {
  282. // ignore empty lists
  283. if (list.head === null) {
  284. return;
  285. }
  286. this.updateCursors(this.tail, list.tail, null, list.head);
  287. // insert to end of the list
  288. if (this.tail !== null) {
  289. // if destination list has a tail, then it also has a head,
  290. // but head doesn't change
  291. // dest tail -> source head
  292. this.tail.next = list.head;
  293. // dest tail <- source head
  294. list.head.prev = this.tail;
  295. } else {
  296. // if list has no a tail, then it also has no a head
  297. // in this case points head to new item
  298. this.head = list.head;
  299. }
  300. // tail always start point to new item
  301. this.tail = list.tail;
  302. list.head = null;
  303. list.tail = null;
  304. };
  305. module.exports = List;