List.cjs 13 KB

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