List.js 12 KB

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