List.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. //
  2. // list
  3. // ┌──────┐
  4. // ┌──────────────┼─head │
  5. // │ │ tail─┼──────────────┐
  6. // │ └──────┘ │
  7. // ▼ ▼
  8. // item item item item
  9. // ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
  10. // null ◀──┼─prev │◀───┼─prev │◀───┼─prev │◀───┼─prev │
  11. // │ next─┼───▶│ next─┼───▶│ next─┼───▶│ next─┼──▶ null
  12. // ├──────┤ ├──────┤ ├──────┤ ├──────┤
  13. // │ data │ │ data │ │ data │ │ data │
  14. // └──────┘ └──────┘ └──────┘ └──────┘
  15. //
  16. function createItem(data) {
  17. return {
  18. prev: null,
  19. next: null,
  20. data: data
  21. };
  22. }
  23. function allocateCursor(node, prev, next) {
  24. var cursor;
  25. if (cursors !== null) {
  26. cursor = cursors;
  27. cursors = cursors.cursor;
  28. cursor.prev = prev;
  29. cursor.next = next;
  30. cursor.cursor = node.cursor;
  31. } else {
  32. cursor = {
  33. prev: prev,
  34. next: next,
  35. cursor: node.cursor
  36. };
  37. }
  38. node.cursor = cursor;
  39. return cursor;
  40. }
  41. function releaseCursor(node) {
  42. var cursor = node.cursor;
  43. node.cursor = cursor.cursor;
  44. cursor.prev = null;
  45. cursor.next = null;
  46. cursor.cursor = cursors;
  47. cursors = cursor;
  48. }
  49. var cursors = null;
  50. var List = function() {
  51. this.cursor = null;
  52. this.head = null;
  53. this.tail = null;
  54. };
  55. List.createItem = createItem;
  56. List.prototype.createItem = createItem;
  57. List.prototype.updateCursors = function(prevOld, prevNew, nextOld, nextNew) {
  58. var cursor = this.cursor;
  59. while (cursor !== null) {
  60. if (cursor.prev === prevOld) {
  61. cursor.prev = prevNew;
  62. }
  63. if (cursor.next === nextOld) {
  64. cursor.next = nextNew;
  65. }
  66. cursor = cursor.cursor;
  67. }
  68. };
  69. List.prototype.getSize = function() {
  70. var size = 0;
  71. var cursor = this.head;
  72. while (cursor) {
  73. size++;
  74. cursor = cursor.next;
  75. }
  76. return size;
  77. };
  78. List.prototype.fromArray = function(array) {
  79. var cursor = null;
  80. this.head = null;
  81. for (var i = 0; i < array.length; i++) {
  82. var item = createItem(array[i]);
  83. if (cursor !== null) {
  84. cursor.next = item;
  85. } else {
  86. this.head = item;
  87. }
  88. item.prev = cursor;
  89. cursor = item;
  90. }
  91. this.tail = cursor;
  92. return this;
  93. };
  94. List.prototype.toArray = function() {
  95. var cursor = this.head;
  96. var result = [];
  97. while (cursor) {
  98. result.push(cursor.data);
  99. cursor = cursor.next;
  100. }
  101. return result;
  102. };
  103. List.prototype.toJSON = List.prototype.toArray;
  104. List.prototype.isEmpty = function() {
  105. return this.head === null;
  106. };
  107. List.prototype.first = function() {
  108. return this.head && this.head.data;
  109. };
  110. List.prototype.last = function() {
  111. return this.tail && this.tail.data;
  112. };
  113. List.prototype.each = function(fn, context) {
  114. var item;
  115. if (context === undefined) {
  116. context = this;
  117. }
  118. // push cursor
  119. var cursor = allocateCursor(this, null, this.head);
  120. while (cursor.next !== null) {
  121. item = cursor.next;
  122. cursor.next = item.next;
  123. fn.call(context, item.data, item, this);
  124. }
  125. // pop cursor
  126. releaseCursor(this);
  127. };
  128. List.prototype.forEach = List.prototype.each;
  129. List.prototype.eachRight = function(fn, context) {
  130. var item;
  131. if (context === undefined) {
  132. context = this;
  133. }
  134. // push cursor
  135. var cursor = allocateCursor(this, this.tail, null);
  136. while (cursor.prev !== null) {
  137. item = cursor.prev;
  138. cursor.prev = item.prev;
  139. fn.call(context, item.data, item, this);
  140. }
  141. // pop cursor
  142. releaseCursor(this);
  143. };
  144. List.prototype.forEachRight = List.prototype.eachRight;
  145. List.prototype.reduce = function(fn, initialValue, context) {
  146. var item;
  147. if (context === undefined) {
  148. context = this;
  149. }
  150. // push cursor
  151. var cursor = allocateCursor(this, null, this.head);
  152. var acc = initialValue;
  153. while (cursor.next !== null) {
  154. item = cursor.next;
  155. cursor.next = item.next;
  156. acc = fn.call(context, acc, item.data, item, this);
  157. }
  158. // pop cursor
  159. releaseCursor(this);
  160. return acc;
  161. };
  162. List.prototype.reduceRight = function(fn, initialValue, context) {
  163. var item;
  164. if (context === undefined) {
  165. context = this;
  166. }
  167. // push cursor
  168. var cursor = allocateCursor(this, this.tail, null);
  169. var acc = initialValue;
  170. while (cursor.prev !== null) {
  171. item = cursor.prev;
  172. cursor.prev = item.prev;
  173. acc = fn.call(context, acc, item.data, item, this);
  174. }
  175. // pop cursor
  176. releaseCursor(this);
  177. return acc;
  178. };
  179. List.prototype.nextUntil = function(start, fn, context) {
  180. if (start === null) {
  181. return;
  182. }
  183. var item;
  184. if (context === undefined) {
  185. context = this;
  186. }
  187. // push cursor
  188. var cursor = allocateCursor(this, null, start);
  189. while (cursor.next !== null) {
  190. item = cursor.next;
  191. cursor.next = item.next;
  192. if (fn.call(context, item.data, item, this)) {
  193. break;
  194. }
  195. }
  196. // pop cursor
  197. releaseCursor(this);
  198. };
  199. List.prototype.prevUntil = function(start, fn, context) {
  200. if (start === null) {
  201. return;
  202. }
  203. var item;
  204. if (context === undefined) {
  205. context = this;
  206. }
  207. // push cursor
  208. var cursor = allocateCursor(this, start, null);
  209. while (cursor.prev !== null) {
  210. item = cursor.prev;
  211. cursor.prev = item.prev;
  212. if (fn.call(context, item.data, item, this)) {
  213. break;
  214. }
  215. }
  216. // pop cursor
  217. releaseCursor(this);
  218. };
  219. List.prototype.some = function(fn, context) {
  220. var cursor = this.head;
  221. if (context === undefined) {
  222. context = this;
  223. }
  224. while (cursor !== null) {
  225. if (fn.call(context, cursor.data, cursor, this)) {
  226. return true;
  227. }
  228. cursor = cursor.next;
  229. }
  230. return false;
  231. };
  232. List.prototype.map = function(fn, context) {
  233. var result = new List();
  234. var cursor = this.head;
  235. if (context === undefined) {
  236. context = this;
  237. }
  238. while (cursor !== null) {
  239. result.appendData(fn.call(context, cursor.data, cursor, this));
  240. cursor = cursor.next;
  241. }
  242. return result;
  243. };
  244. List.prototype.filter = function(fn, context) {
  245. var result = new List();
  246. var cursor = this.head;
  247. if (context === undefined) {
  248. context = this;
  249. }
  250. while (cursor !== null) {
  251. if (fn.call(context, cursor.data, cursor, this)) {
  252. result.appendData(cursor.data);
  253. }
  254. cursor = cursor.next;
  255. }
  256. return result;
  257. };
  258. List.prototype.clear = function() {
  259. this.head = null;
  260. this.tail = null;
  261. };
  262. List.prototype.copy = function() {
  263. var result = new List();
  264. var cursor = this.head;
  265. while (cursor !== null) {
  266. result.insert(createItem(cursor.data));
  267. cursor = cursor.next;
  268. }
  269. return result;
  270. };
  271. List.prototype.prepend = function(item) {
  272. // head
  273. // ^
  274. // item
  275. this.updateCursors(null, item, this.head, item);
  276. // insert to the beginning of the list
  277. if (this.head !== null) {
  278. // new item <- first item
  279. this.head.prev = item;
  280. // new item -> first item
  281. item.next = this.head;
  282. } else {
  283. // if list has no head, then it also has no tail
  284. // in this case tail points to the new item
  285. this.tail = item;
  286. }
  287. // head always points to new item
  288. this.head = item;
  289. return this;
  290. };
  291. List.prototype.prependData = function(data) {
  292. return this.prepend(createItem(data));
  293. };
  294. List.prototype.append = function(item) {
  295. return this.insert(item);
  296. };
  297. List.prototype.appendData = function(data) {
  298. return this.insert(createItem(data));
  299. };
  300. List.prototype.insert = function(item, before) {
  301. if (before !== undefined && before !== null) {
  302. // prev before
  303. // ^
  304. // item
  305. this.updateCursors(before.prev, item, before, item);
  306. if (before.prev === null) {
  307. // insert to the beginning of list
  308. if (this.head !== before) {
  309. throw new Error('before doesn\'t belong to list');
  310. }
  311. // since head points to before therefore list doesn't empty
  312. // no need to check tail
  313. this.head = item;
  314. before.prev = item;
  315. item.next = before;
  316. this.updateCursors(null, item);
  317. } else {
  318. // insert between two items
  319. before.prev.next = item;
  320. item.prev = before.prev;
  321. before.prev = item;
  322. item.next = before;
  323. }
  324. } else {
  325. // tail
  326. // ^
  327. // item
  328. this.updateCursors(this.tail, item, null, item);
  329. // insert to the ending of the list
  330. if (this.tail !== null) {
  331. // last item -> new item
  332. this.tail.next = item;
  333. // last item <- new item
  334. item.prev = this.tail;
  335. } else {
  336. // if list has no tail, then it also has no head
  337. // in this case head points to new item
  338. this.head = item;
  339. }
  340. // tail always points to new item
  341. this.tail = item;
  342. }
  343. return this;
  344. };
  345. List.prototype.insertData = function(data, before) {
  346. return this.insert(createItem(data), before);
  347. };
  348. List.prototype.remove = function(item) {
  349. // item
  350. // ^
  351. // prev next
  352. this.updateCursors(item, item.prev, item, item.next);
  353. if (item.prev !== null) {
  354. item.prev.next = item.next;
  355. } else {
  356. if (this.head !== item) {
  357. throw new Error('item doesn\'t belong to list');
  358. }
  359. this.head = item.next;
  360. }
  361. if (item.next !== null) {
  362. item.next.prev = item.prev;
  363. } else {
  364. if (this.tail !== item) {
  365. throw new Error('item doesn\'t belong to list');
  366. }
  367. this.tail = item.prev;
  368. }
  369. item.prev = null;
  370. item.next = null;
  371. return item;
  372. };
  373. List.prototype.push = function(data) {
  374. this.insert(createItem(data));
  375. };
  376. List.prototype.pop = function() {
  377. if (this.tail !== null) {
  378. return this.remove(this.tail);
  379. }
  380. };
  381. List.prototype.unshift = function(data) {
  382. this.prepend(createItem(data));
  383. };
  384. List.prototype.shift = function() {
  385. if (this.head !== null) {
  386. return this.remove(this.head);
  387. }
  388. };
  389. List.prototype.prependList = function(list) {
  390. return this.insertList(list, this.head);
  391. };
  392. List.prototype.appendList = function(list) {
  393. return this.insertList(list);
  394. };
  395. List.prototype.insertList = function(list, before) {
  396. // ignore empty lists
  397. if (list.head === null) {
  398. return this;
  399. }
  400. if (before !== undefined && before !== null) {
  401. this.updateCursors(before.prev, list.tail, before, list.head);
  402. // insert in the middle of dist list
  403. if (before.prev !== null) {
  404. // before.prev <-> list.head
  405. before.prev.next = list.head;
  406. list.head.prev = before.prev;
  407. } else {
  408. this.head = list.head;
  409. }
  410. before.prev = list.tail;
  411. list.tail.next = before;
  412. } else {
  413. this.updateCursors(this.tail, list.tail, null, list.head);
  414. // insert to end of the list
  415. if (this.tail !== null) {
  416. // if destination list has a tail, then it also has a head,
  417. // but head doesn't change
  418. // dest tail -> source head
  419. this.tail.next = list.head;
  420. // dest tail <- source head
  421. list.head.prev = this.tail;
  422. } else {
  423. // if list has no a tail, then it also has no a head
  424. // in this case points head to new item
  425. this.head = list.head;
  426. }
  427. // tail always start point to new item
  428. this.tail = list.tail;
  429. }
  430. list.head = null;
  431. list.tail = null;
  432. return this;
  433. };
  434. List.prototype.replace = function(oldItem, newItemOrList) {
  435. if ('head' in newItemOrList) {
  436. this.insertList(newItemOrList, oldItem);
  437. } else {
  438. this.insert(newItemOrList, oldItem);
  439. }
  440. this.remove(oldItem);
  441. };
  442. module.exports = List;