solists 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # Self-organizing lists in TypeScript
2
+
3
+ The library provides a powerful and flexible implementation of the [self-organizing list](https://en.wikipedia.org/wiki/Self-organizing_list) data structure written in TypeScript. It offers support for multiple heuristic algorithms such as [Frequency Count](#Frequency-Count), [Move to Front](#Move-to-Front) and [Transpose](#Transpose). In addition to custom methods, the data structure also includes a selection of methods with behavior equivalent to that of JavaScript's built-in [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) data structure.
4
+
5
+ ## Introduction
6
+
7
+ A *self-organizing list* (aka *SoList*) is a list that reorders its elements based on some self-organizing heuristic to improve average access time. The aim of a self-organizing list is to improve efficiency of linear search by moving more frequently accessed items towards the head of the list. A self-organizing list achieves near constant time for element access in the best case. A self-organizing list uses a reorganizing algorithm to adapt to various query distributions at runtime.
8
+
9
+ ### Self-organizing techniques
10
+
11
+ #### Frequency Count
12
+
13
+ This technique involves counting the number of times each node is searched for, by keeping a separate counter variable for each node that is incremented every time the node is accessed.
14
+
15
+ Pseudocode:
16
+ ```
17
+ init: count(i) = 0 for each item i
18
+ At t-th item selection:
19
+ if item i is searched:
20
+ count(i) = count(i) + 1
21
+ rearrange items based on count
22
+ ```
23
+
24
+ #### Move to Front
25
+
26
+ This technique moves the node which is accessed to the head of the list.
27
+
28
+ Pseudocode:
29
+ ```
30
+ At the t-th item selection:
31
+ if item i is selected:
32
+ move item i to head of the list
33
+ ```
34
+
35
+ #### Transpose
36
+
37
+ This technique involves swapping an accessed node with its predecessor.
38
+
39
+ Pseudocode:
40
+ ```
41
+ At the t-th item selection:
42
+ if item i is selected:
43
+ if i is not the head of list:
44
+ swap item i with item (i - 1)
45
+ ```
46
+
47
+ ### Rearrange upon creation
48
+ In literature, some self-organizing list implementations activate a heuristic after every creation operation (like `insert()`, `push()`, `unshift()`, etc.). This means that immediately after a new node is added to the list, the relevant heuristic is triggered and the list is rearranged.
49
+
50
+ This library supports both of these approaches. By default, the list is only rearranged when searched. To activate the option for rearranging the list upon creation, use `rearrange_on_creation=true` when creating a new SoList instance. See the example below for more details.
51
+
52
+ Methods which perform rearrange of the list:
53
+ - Rearrange upon search: `at()`, `find()`, `findIndex()`, `findLast()`, `findLastIndex()`, `includes()`, `indexOf()` and `lastIndexOf`.
54
+ - Rearrange upon creation: `constructor` (initialization from an iterable), `insert()`, `push()` and `unshift()`.
55
+
56
+ ## Installation
57
+ ```
58
+ npm install solists
59
+ ```
60
+
61
+ ## Example
62
+ ```javascript
63
+ const { FrequencyCountSoList, MoveToFrontSoList, TransposeSoList } = require("solists");
64
+
65
+ /** Examples of SoList without rearrange upon creation **/
66
+
67
+ // Example of SoList with Frequency Count heuristic
68
+ const fcList1 = new FrequencyCountSoList(false,[1,2,3,4,5]);
69
+ console.log(fcList1.toString()); // 1,2,3,4,5
70
+ fcList1.includes(2);
71
+ fcList1.includes(4);
72
+ console.log(fcList1.toString()); // 2,4,1,3,5
73
+
74
+ // Example of SoList with Move to Front heuristic
75
+ const mtfList1 = new MoveToFrontSoList(false,[1,2,3,4,5]);
76
+ console.log(mtfList1.toString()); // 1,2,3,4,5
77
+ mtfList1.includes(2);
78
+ mtfList1.includes(4);
79
+ console.log(mtfList1.toString()); // 4,2,1,3,5
80
+
81
+ // Example of SoList with Transpose heuristic
82
+ const tList1 = new TransposeSoList(false,[1,2,3,4,5]);
83
+ console.log(tList1.toString()); // 1,2,3,4,5
84
+ tList1.includes(2);
85
+ tList1.includes(4);
86
+ console.log(tList1.toString()); // 2,1,4,3,5
87
+
88
+ /** Examples of SoList with rearrange upon creation **/
89
+
90
+ // Example of SoList with Frequency Count heuristic
91
+ const fcList2 = new FrequencyCountSoList(true,[1,2,3,4,5]);
92
+ console.log(fcList2.toString()); // 1,2,3,4,5
93
+ fcList2.includes(2);
94
+ fcList2.includes(4);
95
+ console.log(fcList2.toString()); // 2,4,1,3,5
96
+
97
+ // Example of SoList with Move to Front heuristic
98
+ const mtfList2 = new MoveToFrontSoList(true,[1,2,3,4,5]);
99
+ console.log(mtfList2.toString()); // 5,4,3,2,1
100
+ mtfList2.includes(2);
101
+ mtfList2.includes(4);
102
+ console.log(mtfList2.toString()); // 4,2,5,3,1
103
+
104
+ // Example of SoList with Transpose heuristic
105
+ const tList2 = new TransposeSoList(true,[1,2,3,4,5]);
106
+ console.log(tList2.toString()); // 2,3,4,5,1
107
+ tList2.includes(2);
108
+ tList2.includes(4);
109
+ console.log(tList2.toString()); // 2,4,3,5,1
110
+
111
+ ```
112
+ ## Methods and properties
113
+
114
+ List of properties of SoList:
115
+
116
+ | Name | Description |
117
+ | ------------- | ------------- |
118
+ |`length`|Represents the number of elements in that SoList|
119
+
120
+ List of methods of SoList:
121
+
122
+ | Name | Description |
123
+ | ------------- | ------------- |
124
+ |`solist[Symbol.iterator]()`|Returns an iterator that yields the value of each index in the SoList|
125
+ |`at()`|Returns the value at a given index|
126
+ |`concat()`|Returns a new SoList consisting of merging the existing SoList with given iterable inputs|
127
+ |`constructor`|Creates a new empty SoList instance or from a given iterable|
128
+ |`copyWithin()`|Shallow copies part of a SoList to another location in the same SoList and returns it|
129
+ |`entries()`|Returns a SoList iterator object of key/value pairs|
130
+ |`every()`|Checks if every element in a SoList satisfies a predicate function|
131
+ |`fill()`|Changes all elements in a SoList to a static value|
132
+ |`filter()`|Creates a new SoList with every element in a SoList that satisfies a predicate function|
133
+ |`find()`|Returns the value of the first element in a SoList that satisfies a predicate function|
134
+ |`findIndex()`|Returns the index of the first element in a SoList that satisfies a predicate function|
135
+ |`findLast()`|Returns the value of the last element in a SoList that satisfies a predicate function|
136
+ |`findLastIndex()`|Returns the index of the last element in a SoList that satisfies a predicate function|
137
+ |`flat()`|Creates a new SoList with all sub-SoList elements concatenated into it recursively up to the specified depth|
138
+ |`forEach()`|Executes a provided function once for each SoList element|
139
+ |`includes()`|Determines whether a SoList includes a certain value among its entries|
140
+ |`indexOf()`|Returns the first index at which a given element can be found in a SoList|
141
+ |`insert()`|Adds an element into a specific index of a SoList|
142
+ |`isEqual()`|Checks if the SoList is equal to a given iterable|
143
+ |`isEmpty()`|Checks if the SoList does not contain any elements|
144
+ |`join()`|Joins all elements of SoList into a string separated by commas or a specified separator string|
145
+ |`keys()`|Returns a SoList iterator object of keys|
146
+ |`lastIndexOf()`|Returns the last index at which a given element can be found in a SoList|
147
+ |`map()`|Creates a new SoList populated with the results of calling a provided function on every element of a SoList|
148
+ |`pop()`|Removes the last element from a SoList and returns that element|
149
+ |`push()`|Adds one or more elements to the end of a SoList and returns the new length of the SoList|
150
+ |`reduce()`|Reduces the values of a SoList to a single value (going left-to-right)|
151
+ |`reduceRight()`|Reduces the values of a SoList to a single value (going right-to-left)|
152
+ |`remove()`|Removes an element by a specific index from a SoList|
153
+ |`reverse()`|Reverses the order of the elements in a SoList|
154
+ |`shift()`|removes the first element from a SoList and returns that removed element|
155
+ |`slice()`|Returns a shallow copy of a portion of a SoList into a new SoList selected by positions|
156
+ |`some()`|Checks if any of the elements in a SoList satisfy a predicate function|
157
+ |`sort()`|Sorts the elements of a SoList and returns the reference to the same SoList, now sorted|
158
+ |`splice()`|Changes the contents of a SoList by removing or replacing existing elements and/or adding new elements|
159
+ |`toLocaleString()`|Returns a string representing the elements of the SoList using their `toLocaleString` methods|
160
+ |`toString()`|Returns a string representing the specified SoList and its elements|
161
+ |`unshift()`|Adds one or more elements to the beginning of a SoList and returns the new length of the SoList|
162
+ |`values()`|Returns a SoList iterator object of values|
163
+
164
+ ## SoList vs JS-Array
165
+ Although SoList implements most of the methods of JS-Array (with identical behavior), there are several differences and limitations:
166
+ - Unlike JS-Array, SoList does not support empty items (for example `[1,,3]`).
167
+ - Currently, the `every()`, `filter()`, `find()`, `findIndex()`, `findLast()`, `findLastIndex()`, `flatMap()` and `some()` don't support the `thisArg` argument.
168
+ - Unsupported JS-Array methods: `group()` and `groupToMap()`.
169
+ - Additional custom methods of SoList: `insert()`, `isEqual()`, `isEmpty()` and `remove()`.
170
+
171
+ ## Contributing
172
+
173
+ Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
174
+
175
+ Please make sure to update tests as appropriate and run existing ones using:
176
+
177
+ ```
178
+ npm run test
179
+ ```
@@ -0,0 +1,72 @@
1
+ import { Node } from './Node';
2
+ export declare class DoublyLinkedList {
3
+ length: number;
4
+ protected head: Node | null;
5
+ protected tail: Node | null;
6
+ protected rearrangeOnCreation: boolean;
7
+ constructor(rearrangeOnCreation?: boolean, iterable?: null);
8
+ [Symbol.iterator](): Generator<any, any, unknown>;
9
+ at(index: number): unknown;
10
+ concat(...values: any[]): DoublyLinkedList;
11
+ copyWithin(target: any, start: any): DoublyLinkedList;
12
+ entries(): Generator<any, any, unknown>;
13
+ every(callbackFn: any): boolean;
14
+ fill(value: any): DoublyLinkedList;
15
+ filter(callbackFn: any): DoublyLinkedList;
16
+ find(callbackFn: any): unknown;
17
+ findIndex(callbackFn: any): number;
18
+ findLast(callbackFn: any): unknown;
19
+ findLastIndex(callbackFn: any): any;
20
+ flat(depth?: number): DoublyLinkedList;
21
+ flatMap(callbackFn: any): DoublyLinkedList;
22
+ forEach(callbackFn: any): void;
23
+ includes(searchedValue: any, fromIndex: any): boolean;
24
+ indexOf(searchedValue: any, fromIndex?: number): number;
25
+ join(separator: any): string;
26
+ keys(): Generator<number>;
27
+ lastIndexOf(searchElement: any): number;
28
+ map(callbackFn: any): DoublyLinkedList;
29
+ pop(): unknown;
30
+ push(...values: any): number;
31
+ reduce(callbackFn: any, initialValue: any): any;
32
+ reduceRight(callbackFn: any, initialValue: any): any;
33
+ reverse(): DoublyLinkedList;
34
+ shift(): unknown;
35
+ slice(startIndex: any, endIndex: any): DoublyLinkedList;
36
+ some(callbackFn: any): boolean;
37
+ sort(comparefn: any): DoublyLinkedList;
38
+ splice(start: any, deleteCount: any): DoublyLinkedList;
39
+ toLocaleString(locales: any, options: any): string;
40
+ toString(): string;
41
+ unshift(...values: any): number;
42
+ values(): Generator<any, any, unknown>;
43
+ insert(index: any, value: any): number | undefined;
44
+ isEmpty(): boolean;
45
+ isEqual(value: any): boolean;
46
+ remove(index: any): any;
47
+ protected _getIndexByNode(node: Node): number;
48
+ protected _getNode(index: number): Node | null;
49
+ protected _insertFirst(newNode: Node): void;
50
+ protected _insertBefore(existingNode: Node, newNode: Node): void;
51
+ protected _insertAfter(existingNode: Node, newNode: Node): void;
52
+ protected _rearrange(node: Node): number | undefined;
53
+ protected _remove(node: Node): void;
54
+ protected _removeLast(): void;
55
+ private _entries;
56
+ private _extend;
57
+ private _getSortCompare;
58
+ private _isCallable;
59
+ private _isIterable;
60
+ private _isStrictlyEqual;
61
+ private _keys;
62
+ private _merge;
63
+ private _mergeSort;
64
+ private _nodes;
65
+ private _nodesReverse;
66
+ private _sameValue;
67
+ private _sameValueZero;
68
+ private _toAbsoluteIndex;
69
+ private _toIntegerOrInfinity;
70
+ private _tryToString;
71
+ private _values;
72
+ }
@@ -0,0 +1,793 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DoublyLinkedList = void 0;
4
+ const Node_1 = require("./Node");
5
+ class DoublyLinkedList {
6
+ constructor(rearrangeOnCreation = false, iterable = null) {
7
+ this.head = null;
8
+ this.tail = null;
9
+ this.length = 0;
10
+ this.rearrangeOnCreation = rearrangeOnCreation;
11
+ if (iterable !== null) {
12
+ this._extend(iterable);
13
+ }
14
+ }
15
+ [Symbol.iterator]() {
16
+ return this._values();
17
+ }
18
+ // Public Array-like methods
19
+ at(index) {
20
+ if (this.length === 0 || Math.abs(index) >= this.length) {
21
+ return undefined;
22
+ }
23
+ index = this._toAbsoluteIndex(index, this.length);
24
+ const targetNode = this._getNode(index);
25
+ if (targetNode === null) {
26
+ return undefined;
27
+ }
28
+ targetNode.count += 1;
29
+ this._rearrange(targetNode);
30
+ return targetNode.value;
31
+ }
32
+ concat(...values) {
33
+ const { length: valuesLength } = values;
34
+ const result = this.slice(0, this.length);
35
+ for (let index = 0; index < valuesLength; index += 1) {
36
+ const value = values[index];
37
+ if (value instanceof DoublyLinkedList || Array.isArray(value)) {
38
+ for (const currentValue of value.values()) {
39
+ result.push(currentValue);
40
+ }
41
+ }
42
+ else {
43
+ result.push(value);
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+ copyWithin(target /* = 0 */, start /* = 0, end = @length */) {
49
+ const sourceStart = this._toAbsoluteIndex(start, this.length);
50
+ const sourceEnd = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.length;
51
+ const targetStart = this._toAbsoluteIndex(target, this.length);
52
+ let count = Math.min((sourceEnd === this.length ? this.length : this._toAbsoluteIndex(sourceEnd, this.length)) - sourceStart, this.length - targetStart);
53
+ const tempSrcLst = this.slice(0, this.length);
54
+ const targetNodes = this._nodes(targetStart, targetStart + count);
55
+ const sourceNodes = tempSrcLst._nodes(sourceStart, sourceStart + count);
56
+ while (count > 0) {
57
+ const targetCurrentNode = targetNodes.next();
58
+ const sourceCurrentNode = sourceNodes.next();
59
+ const targetCurrentValue = targetCurrentNode.value;
60
+ const sourceCurrentValue = sourceCurrentNode.value;
61
+ targetCurrentValue[1].value = sourceCurrentValue[1].value;
62
+ count -= 1;
63
+ }
64
+ return this;
65
+ }
66
+ entries() {
67
+ return this._entries();
68
+ }
69
+ // TODO: Support "thisArg" argument
70
+ every(callbackFn) {
71
+ for (const [index, node] of this._nodes(0, this.length)) {
72
+ if (!(callbackFn(node.value, index, this))) {
73
+ return false;
74
+ }
75
+ }
76
+ return true;
77
+ }
78
+ fill(value) {
79
+ let startIndex = undefined;
80
+ if (arguments.length > 1) {
81
+ startIndex = arguments[1];
82
+ }
83
+ startIndex = this._toAbsoluteIndex(startIndex, this.length);
84
+ let endIndex = undefined;
85
+ if (arguments.length > 2) {
86
+ endIndex = arguments[2];
87
+ }
88
+ endIndex = (endIndex === undefined) ? this.length : this._toAbsoluteIndex(endIndex, this.length);
89
+ for (const [/* index */ , node] of this._nodes(startIndex, endIndex)) {
90
+ node.value = value;
91
+ }
92
+ return this;
93
+ }
94
+ // TODO: Support "thisArg" argument
95
+ filter(callbackFn) {
96
+ const result = new DoublyLinkedList();
97
+ for (const [index, node] of this._nodes(0, this.length)) {
98
+ if (callbackFn(node.value, index, this)) {
99
+ result.push(node.value);
100
+ }
101
+ }
102
+ return result;
103
+ }
104
+ // TODO: Support "thisArg" argument
105
+ find(callbackFn) {
106
+ for (const [index, node] of this._nodes(0, this.length)) {
107
+ if (callbackFn(node.value, index, this)) {
108
+ node.count += 1;
109
+ this._rearrange(node);
110
+ return node.value;
111
+ }
112
+ }
113
+ return undefined;
114
+ }
115
+ // TODO: Support "thisArg" argument
116
+ findIndex(callbackFn) {
117
+ for (const [index, node] of this._nodes(0, this.length)) {
118
+ if (callbackFn(node.value, index, this)) {
119
+ node.count += 1;
120
+ const result = this._rearrange(node);
121
+ return (result === undefined) ? index : result;
122
+ }
123
+ }
124
+ return -1;
125
+ }
126
+ // TODO: Support "thisArg" argument
127
+ findLast(callbackFn) {
128
+ for (const [index, node] of this._nodesReverse(0, this.length)) {
129
+ if (callbackFn(node.value, index, this)) {
130
+ node.count += 1;
131
+ this._rearrange(node);
132
+ return node.value;
133
+ }
134
+ }
135
+ return undefined;
136
+ }
137
+ // TODO: Support "thisArg" argument
138
+ findLastIndex(callbackFn) {
139
+ for (const [index, node] of this._nodesReverse(0, this.length)) {
140
+ if (callbackFn(node.value, index, this)) {
141
+ node.count += 1;
142
+ const result = this._rearrange(node);
143
+ return (result === undefined) ? index : result;
144
+ }
145
+ }
146
+ return -1;
147
+ }
148
+ flat(depth = 1) {
149
+ depth = this._toIntegerOrInfinity(depth);
150
+ if (depth < 1) {
151
+ return this.slice(0, this.length);
152
+ }
153
+ return this.reduce(function (flat, toFlatten) {
154
+ return flat.concat(((toFlatten instanceof DoublyLinkedList) && (depth > 1)) ? toFlatten.flat(depth - 1) : toFlatten);
155
+ }, new DoublyLinkedList());
156
+ }
157
+ // TODO: Support "thisArg" argument
158
+ flatMap(callbackFn) {
159
+ return this.map(callbackFn).flat();
160
+ }
161
+ forEach(callbackFn) {
162
+ for (const [index, node] of this._nodes(0, this.length)) {
163
+ callbackFn(node.value, index, this);
164
+ }
165
+ return undefined;
166
+ }
167
+ includes(searchedValue, fromIndex) {
168
+ if (this.length === 0) {
169
+ return false;
170
+ }
171
+ fromIndex = this._toAbsoluteIndex(fromIndex, this.length);
172
+ for (const [/* index */ , node] of this._nodes(fromIndex, this.length)) {
173
+ if (this._sameValueZero(node.value, searchedValue)) {
174
+ node.count += 1;
175
+ this._rearrange(node);
176
+ return true;
177
+ }
178
+ }
179
+ return false;
180
+ }
181
+ indexOf(searchedValue, fromIndex = 0) {
182
+ if (this.length === 0) {
183
+ return -1;
184
+ }
185
+ fromIndex = this._toAbsoluteIndex(fromIndex, this.length);
186
+ for (const [index, node] of this._nodes(fromIndex, this.length)) {
187
+ if (this._isStrictlyEqual(node.value, searchedValue)) {
188
+ node.count += 1;
189
+ const result = this._rearrange(node);
190
+ return (result === undefined) ? index : result;
191
+ }
192
+ }
193
+ return -1;
194
+ }
195
+ join(separator) {
196
+ let result = '';
197
+ if (this.length === 0) {
198
+ return result;
199
+ }
200
+ if (separator === undefined) {
201
+ separator = ',';
202
+ }
203
+ for (const [index, node] of this._nodes(0, this.length)) {
204
+ const value = node.value == null ? "" : node.value;
205
+ if (index !== this.length - 1) {
206
+ result = result.concat(value) + separator;
207
+ }
208
+ else {
209
+ result = result.concat(value);
210
+ }
211
+ }
212
+ return result;
213
+ }
214
+ keys() {
215
+ return this._keys();
216
+ }
217
+ lastIndexOf(searchElement /* , fromIndex = @[*-1] */) {
218
+ if (this.length === 0) {
219
+ return -1;
220
+ }
221
+ let fromIndex = this.length - 1;
222
+ if (arguments.length > 1) {
223
+ fromIndex = Math.min(fromIndex, this._toIntegerOrInfinity(arguments[1]));
224
+ }
225
+ if (fromIndex < 0) {
226
+ fromIndex = this.length + fromIndex;
227
+ }
228
+ for (const [index, node] of this._nodesReverse(0, fromIndex + 1)) {
229
+ if (this._isStrictlyEqual(node.value, searchElement)) {
230
+ node.count += 1;
231
+ const result = this._rearrange(node);
232
+ return (result === undefined) ? index : result;
233
+ }
234
+ }
235
+ return -1;
236
+ }
237
+ map(callbackFn) {
238
+ const result = new DoublyLinkedList();
239
+ for (const [index, node] of this._nodes(0, this.length)) {
240
+ result.push(callbackFn(node.value, index, this));
241
+ }
242
+ return result;
243
+ }
244
+ pop() {
245
+ if (this.length === 0) {
246
+ return undefined;
247
+ }
248
+ const value = this.tail.value;
249
+ if (this.length === 1) {
250
+ this._removeLast();
251
+ }
252
+ else {
253
+ this.tail = this.tail.prev;
254
+ this.tail.next = null;
255
+ this.length -= 1;
256
+ }
257
+ return value;
258
+ }
259
+ push(...values) {
260
+ const { length: valuesLength } = values;
261
+ for (let index = 0; index < valuesLength; index += 1) {
262
+ const newNode = new Node_1.Node(values[index]);
263
+ if (this.length === 0) {
264
+ this._insertFirst(newNode);
265
+ }
266
+ else if (this.tail !== null) {
267
+ this._insertAfter(this.tail, newNode);
268
+ }
269
+ else {
270
+ continue;
271
+ }
272
+ if (this.rearrangeOnCreation) {
273
+ this._rearrange(newNode);
274
+ }
275
+ }
276
+ return this.length;
277
+ }
278
+ reduce(callbackFn, initialValue) {
279
+ if (!(this._isCallable(callbackFn))) {
280
+ throw TypeError(this._tryToString(callbackFn) + ' is not a function');
281
+ }
282
+ if (this.length === 0 && arguments.length < 2) {
283
+ throw TypeError('Reduce of empty list with no initial value');
284
+ }
285
+ let accumulator = arguments.length > 1 ? initialValue : this.head.value;
286
+ for (const [index, node] of this._nodes(0, this.length)) {
287
+ if (arguments.length > 1 || index > 0) {
288
+ accumulator = callbackFn(accumulator, node.value, index, this);
289
+ }
290
+ }
291
+ return accumulator;
292
+ }
293
+ reduceRight(callbackFn, initialValue) {
294
+ if (!(this._isCallable(callbackFn))) {
295
+ throw TypeError(this._tryToString(callbackFn) + ' is not a function');
296
+ }
297
+ if (this.length === 0 && arguments.length < 2) {
298
+ throw TypeError('Reduce of empty list with no initial value');
299
+ }
300
+ let accumulator = arguments.length > 1 ? initialValue : this.tail.value;
301
+ for (const [index, node] of this._nodesReverse(0, this.length)) {
302
+ if (index < this.length - 1) {
303
+ accumulator = callbackFn(accumulator, node.value, index, this);
304
+ }
305
+ }
306
+ return accumulator;
307
+ }
308
+ reverse() {
309
+ if (this.length > 1) {
310
+ let curr = this.head;
311
+ while (curr) {
312
+ const next = curr.next;
313
+ curr.next = curr.prev;
314
+ curr.prev = next;
315
+ if (curr.next === null) {
316
+ this.tail = curr;
317
+ }
318
+ else if (curr.prev === null) {
319
+ this.head = curr;
320
+ }
321
+ curr = next;
322
+ }
323
+ }
324
+ return this;
325
+ }
326
+ shift() {
327
+ if (this.length === 0) {
328
+ return undefined;
329
+ }
330
+ const value = this.head.value;
331
+ if (this.length === 1) {
332
+ this._removeLast();
333
+ }
334
+ else {
335
+ this.head = this.head.next;
336
+ this.head.prev = null;
337
+ this.length -= 1;
338
+ }
339
+ return value;
340
+ }
341
+ slice(startIndex, endIndex) {
342
+ const result = new DoublyLinkedList();
343
+ if (startIndex === undefined) {
344
+ startIndex = 0;
345
+ }
346
+ startIndex = this._toAbsoluteIndex(startIndex, this.length);
347
+ endIndex = this._toAbsoluteIndex(endIndex === undefined ? this.length : endIndex, this.length);
348
+ for (const [/* index */ , node] of this._nodes(startIndex, endIndex)) {
349
+ result.push(node.value);
350
+ }
351
+ return result;
352
+ }
353
+ // TODO: Support "thisArg" argument
354
+ some(callbackFn) {
355
+ for (const [index, node] of this._nodes(0, this.length)) {
356
+ if (callbackFn(node.value, index, this)) {
357
+ return true;
358
+ }
359
+ }
360
+ return false;
361
+ }
362
+ sort(comparefn) {
363
+ if (this.length > 1) {
364
+ this._mergeSort(this._getSortCompare(comparefn));
365
+ }
366
+ return this;
367
+ }
368
+ splice(start, deleteCount /* , ...items */) {
369
+ if (arguments.length === 0) {
370
+ deleteCount = 0;
371
+ }
372
+ else if (arguments.length === 1) {
373
+ start = this._toAbsoluteIndex(start, this.length);
374
+ deleteCount = this.length - start;
375
+ }
376
+ else {
377
+ start = this._toAbsoluteIndex(start, this.length);
378
+ deleteCount = Math.min(Math.max(this._toIntegerOrInfinity(deleteCount), 0), this.length - start);
379
+ }
380
+ const deleted = new DoublyLinkedList();
381
+ if (this.length > 0 && deleteCount > 0) {
382
+ let prev = this._getNode(start - 1);
383
+ let current = prev ? prev.next : this.head;
384
+ for (let index = 0; index < deleteCount; index += 1) {
385
+ deleted.push(current.value);
386
+ current = current.next;
387
+ this.length -= 1;
388
+ }
389
+ if (prev === null) {
390
+ this.head = current;
391
+ prev = current;
392
+ if (current) {
393
+ current.prev = null;
394
+ }
395
+ }
396
+ else {
397
+ prev.next = current;
398
+ if (current) {
399
+ current.prev = prev;
400
+ }
401
+ else {
402
+ this.tail = prev;
403
+ }
404
+ }
405
+ }
406
+ const items = Array.prototype.slice.call(arguments, 2, arguments.length);
407
+ if (items.length > 0) {
408
+ let current, next;
409
+ if (start === 0) {
410
+ next = this.head;
411
+ this.head = new Node_1.Node(items.shift());
412
+ current = this.head;
413
+ this.length += 1;
414
+ }
415
+ else {
416
+ current = this._getNode(start - 1);
417
+ next = current.next;
418
+ }
419
+ for (const item of items) {
420
+ current.next = new Node_1.Node(item);
421
+ current.next.prev = current;
422
+ current = current.next;
423
+ this.length += 1;
424
+ }
425
+ current.next = next;
426
+ if (next) {
427
+ next.prev = current;
428
+ }
429
+ else {
430
+ this.tail = current;
431
+ }
432
+ }
433
+ return deleted;
434
+ }
435
+ toLocaleString(locales, options) {
436
+ let result = '';
437
+ if (this.length === 0) {
438
+ return result;
439
+ }
440
+ const separator = ',';
441
+ for (const [index, node] of this._nodes(0, this.length)) {
442
+ const value = node.value == null ? "" : node.value;
443
+ if (index !== this.length - 1) {
444
+ if (locales == null || options == null) {
445
+ result = result.concat(value.toLocaleString()) + separator;
446
+ }
447
+ else {
448
+ result = result.concat(value.toLocaleString(locales, options)) + separator;
449
+ }
450
+ }
451
+ else {
452
+ if (locales == null || options == null) {
453
+ result = result.concat(value.toLocaleString());
454
+ }
455
+ else {
456
+ result = result.concat(value.toLocaleString(locales, options));
457
+ }
458
+ }
459
+ }
460
+ return result;
461
+ }
462
+ toString() {
463
+ return this.join(",");
464
+ }
465
+ unshift(...values) {
466
+ const { length: valuesLength } = values;
467
+ for (let index = valuesLength - 1; index >= 0; index -= 1) {
468
+ const newNode = new Node_1.Node(values[index]);
469
+ if (this.length === 0) {
470
+ this._insertFirst(newNode);
471
+ }
472
+ else if (this.head !== null) {
473
+ this._insertBefore(this.head, newNode);
474
+ }
475
+ else {
476
+ continue;
477
+ }
478
+ if (this.rearrangeOnCreation) {
479
+ this._rearrange(newNode);
480
+ }
481
+ }
482
+ return this.length;
483
+ }
484
+ values() {
485
+ return this._values();
486
+ }
487
+ // Public custom methods
488
+ insert(index, value) {
489
+ if (index === undefined || arguments.length < 2) {
490
+ return undefined;
491
+ }
492
+ index = this._toAbsoluteIndex(index, this.length);
493
+ if (index === 0) {
494
+ return this.unshift(value);
495
+ }
496
+ if (index === this.length) {
497
+ return this.push(value);
498
+ }
499
+ const prev = this._getNode(index - 1);
500
+ if (prev === null) {
501
+ return undefined;
502
+ }
503
+ const newNode = new Node_1.Node(value);
504
+ this._insertAfter(prev, newNode);
505
+ if (this.rearrangeOnCreation) {
506
+ this._rearrange(newNode);
507
+ }
508
+ return this.length;
509
+ }
510
+ isEmpty() {
511
+ return this.length === 0;
512
+ }
513
+ isEqual(value) {
514
+ if (!(this._isIterable(value))) {
515
+ throw TypeError("The value should be iterable");
516
+ }
517
+ if (this.length !== value.length) {
518
+ return false;
519
+ }
520
+ const values = value.values();
521
+ for (const [/* index */ , node] of this._nodes(0, this.length)) {
522
+ const currentValue = values.next();
523
+ if (!(this._sameValue(node.value, currentValue.value))) {
524
+ return false;
525
+ }
526
+ }
527
+ return true;
528
+ }
529
+ remove(index) {
530
+ if (this.length === 0 || index === undefined) {
531
+ return undefined;
532
+ }
533
+ index = this._toAbsoluteIndex(index, this.length);
534
+ if (index >= this.length) {
535
+ return undefined;
536
+ }
537
+ const node = this._getNode(index);
538
+ if (node === null) {
539
+ return undefined;
540
+ }
541
+ const value = node.value;
542
+ this._remove(node);
543
+ return value;
544
+ }
545
+ // Protected helping methods
546
+ _getIndexByNode(node) {
547
+ let count = 0;
548
+ while (node.prev) {
549
+ node = node.prev;
550
+ count += 1;
551
+ }
552
+ return count;
553
+ }
554
+ _getNode(index) {
555
+ if (index < 0 || index >= this.length) {
556
+ return null;
557
+ }
558
+ let node = this.head;
559
+ let counter = 0;
560
+ while (node) {
561
+ if (counter === index) {
562
+ break;
563
+ }
564
+ node = node.next;
565
+ counter = counter + 1;
566
+ }
567
+ return node;
568
+ }
569
+ _insertFirst(newNode) {
570
+ this.head = newNode;
571
+ this.tail = newNode;
572
+ this.length += 1;
573
+ }
574
+ _insertBefore(existingNode, newNode) {
575
+ if (existingNode.isEqual(this.head)) {
576
+ newNode.next = existingNode;
577
+ newNode.prev = null;
578
+ this.head = newNode;
579
+ }
580
+ else if (existingNode.prev !== null) {
581
+ newNode.next = existingNode;
582
+ newNode.prev = existingNode.prev;
583
+ existingNode.prev.next = newNode;
584
+ }
585
+ existingNode.prev = newNode;
586
+ this.length += 1;
587
+ }
588
+ _insertAfter(existingNode, newNode) {
589
+ newNode.prev = existingNode;
590
+ if (existingNode.isEqual(this.tail)) {
591
+ newNode.next = null;
592
+ this.tail = newNode;
593
+ }
594
+ else if (existingNode.next !== null) {
595
+ newNode.next = existingNode.next;
596
+ existingNode.next.prev = newNode;
597
+ }
598
+ existingNode.next = newNode;
599
+ this.length += 1;
600
+ }
601
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
602
+ _rearrange(node) {
603
+ return undefined;
604
+ }
605
+ _remove(node) {
606
+ if (node.isEqual(this.head)) {
607
+ this.shift();
608
+ }
609
+ else if (node.isEqual(this.tail)) {
610
+ this.pop();
611
+ }
612
+ else if (node.next !== null && node.prev !== null) {
613
+ node.next.prev = node.prev;
614
+ node.prev.next = node.next;
615
+ this.length -= 1;
616
+ }
617
+ }
618
+ _removeLast() {
619
+ this.head = null;
620
+ this.tail = null;
621
+ this.length = 0;
622
+ }
623
+ // Private helping methods
624
+ *_entries() {
625
+ let node = this.head;
626
+ let counter = 0;
627
+ while (node) {
628
+ yield [counter, node.value];
629
+ node = node.next;
630
+ counter += 1;
631
+ }
632
+ }
633
+ _extend(iterable) {
634
+ if (!(this._isIterable(iterable))) {
635
+ throw TypeError("The value should be iterable");
636
+ }
637
+ for (const currentValue of iterable) {
638
+ this.push(currentValue);
639
+ }
640
+ }
641
+ _getSortCompare(comparefn) {
642
+ return function (x, y) {
643
+ if (y === undefined)
644
+ return -1;
645
+ if (x === undefined)
646
+ return 1;
647
+ if (comparefn !== undefined)
648
+ return +comparefn(x, y) || 0;
649
+ return String(x) > String(y) ? 1 : -1;
650
+ };
651
+ }
652
+ _isCallable(fn) {
653
+ return (typeof fn === 'function');
654
+ }
655
+ _isIterable(obj) {
656
+ if (obj == null) {
657
+ return false;
658
+ }
659
+ return typeof obj[Symbol.iterator] === 'function';
660
+ }
661
+ _isStrictlyEqual(x, y) {
662
+ return x === y;
663
+ }
664
+ *_keys() {
665
+ let node = this.head;
666
+ let counter = 0;
667
+ while (node) {
668
+ yield counter;
669
+ node = node.next;
670
+ counter += 1;
671
+ }
672
+ }
673
+ _merge(head1, head2, comparefn) {
674
+ if (head1 == null) {
675
+ return head2;
676
+ }
677
+ else if (head2 == null) {
678
+ return head1;
679
+ }
680
+ let node, head;
681
+ if (comparefn(head1.value, head2.value) <= 0) {
682
+ head = node = head1;
683
+ head1 = head1.next;
684
+ }
685
+ else {
686
+ head = node = head2;
687
+ head2 = head2.next;
688
+ }
689
+ while (head1 !== null && head2 !== null) {
690
+ if (comparefn(head1.value, head2.value) <= 0) {
691
+ node.next = head1;
692
+ head1.prev = node;
693
+ node = head1;
694
+ head1 = head1.next;
695
+ }
696
+ else {
697
+ node.next = head2;
698
+ head2.prev = node;
699
+ node = head2;
700
+ head2 = head2.next;
701
+ }
702
+ }
703
+ if (head1 !== null) {
704
+ node.next = head1;
705
+ head1.prev = node;
706
+ }
707
+ else if (head2 !== null) {
708
+ node.next = head2;
709
+ head2.prev = node;
710
+ }
711
+ return head;
712
+ }
713
+ _mergeSort(comparefn) {
714
+ const lists = [];
715
+ let start = this.head;
716
+ let end;
717
+ while (start !== null) {
718
+ end = start;
719
+ while (end.next !== null && comparefn(end.value, end.next.value) <= 0) {
720
+ end = end.next;
721
+ }
722
+ const next = end.next;
723
+ start.prev = null;
724
+ end.next = null;
725
+ lists.push(start);
726
+ start = next;
727
+ }
728
+ while (lists.length > 1) {
729
+ const first = lists.shift();
730
+ const second = lists.shift();
731
+ if (first !== undefined && second !== undefined) {
732
+ lists.push(this._merge(first, second, comparefn));
733
+ }
734
+ }
735
+ this.tail = lists[0];
736
+ this.head = lists[0];
737
+ while (this.tail && this.tail.next) {
738
+ this.tail = this.tail.next;
739
+ }
740
+ }
741
+ *_nodes(startIndex, endIndex) {
742
+ let node = this.head;
743
+ let counter = 0;
744
+ while (node) {
745
+ if (startIndex <= counter && counter < endIndex) {
746
+ yield [counter, node];
747
+ }
748
+ node = node.next;
749
+ counter = counter + 1;
750
+ }
751
+ }
752
+ *_nodesReverse(startIndex, endIndex) {
753
+ let node = this.tail;
754
+ let counter = this.length - 1;
755
+ while (node) {
756
+ if (startIndex <= counter && counter < endIndex) {
757
+ yield [counter, node];
758
+ }
759
+ node = node.prev;
760
+ counter = counter - 1;
761
+ }
762
+ }
763
+ _sameValue(x, y) {
764
+ return Object.is(x, y);
765
+ }
766
+ _sameValueZero(x, y) {
767
+ return x === y || (Number.isNaN(x) && Number.isNaN(y));
768
+ }
769
+ _toAbsoluteIndex(index, length) {
770
+ const integer = this._toIntegerOrInfinity(index);
771
+ return integer < 0 ? Math.max(integer + length, 0) : Math.min(integer, length);
772
+ }
773
+ _toIntegerOrInfinity(argument) {
774
+ const number = +argument;
775
+ return number !== number || number === 0 ? 0 : Math.trunc(number);
776
+ }
777
+ _tryToString(argument) {
778
+ try {
779
+ return String(argument);
780
+ }
781
+ catch (error) {
782
+ return 'Object';
783
+ }
784
+ }
785
+ *_values() {
786
+ let node = this.head;
787
+ while (node) {
788
+ yield node.value;
789
+ node = node.next;
790
+ }
791
+ }
792
+ }
793
+ exports.DoublyLinkedList = DoublyLinkedList;
@@ -0,0 +1,6 @@
1
+ import { DoublyLinkedList } from './DoublyLinkedList';
2
+ import { Node } from './Node';
3
+ export declare class FrequencyCountSoList extends DoublyLinkedList {
4
+ constructor(...args: any);
5
+ protected _rearrange(node: Node): number;
6
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FrequencyCountSoList = void 0;
4
+ const DoublyLinkedList_1 = require("./DoublyLinkedList");
5
+ class FrequencyCountSoList extends DoublyLinkedList_1.DoublyLinkedList {
6
+ constructor(...args) {
7
+ super(...args);
8
+ }
9
+ _rearrange(node) {
10
+ let prev = node.prev;
11
+ let next = node.next;
12
+ if (prev !== null && prev.count < node.count) {
13
+ while (!(node.isEqual(this.head))) {
14
+ if (prev !== null && prev.count < node.count) {
15
+ this._remove(node);
16
+ this._insertBefore(prev, node);
17
+ }
18
+ else {
19
+ break;
20
+ }
21
+ prev = node.prev;
22
+ }
23
+ }
24
+ else if (next !== null && next.count > node.count) {
25
+ while (!(node.isEqual(this.tail))) {
26
+ if (next !== null && next.count > node.count) {
27
+ this._remove(node);
28
+ this._insertAfter(next, node);
29
+ }
30
+ else {
31
+ break;
32
+ }
33
+ next = node.next;
34
+ }
35
+ }
36
+ return this._getIndexByNode(node);
37
+ }
38
+ }
39
+ exports.FrequencyCountSoList = FrequencyCountSoList;
@@ -0,0 +1,6 @@
1
+ import { DoublyLinkedList } from './DoublyLinkedList';
2
+ import { Node } from './Node';
3
+ export declare class MoveToFrontSoList extends DoublyLinkedList {
4
+ constructor(...args: any);
5
+ protected _rearrange(node: Node): number;
6
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MoveToFrontSoList = void 0;
4
+ const DoublyLinkedList_1 = require("./DoublyLinkedList");
5
+ class MoveToFrontSoList extends DoublyLinkedList_1.DoublyLinkedList {
6
+ constructor(...args) {
7
+ super(...args);
8
+ }
9
+ _rearrange(node) {
10
+ if (!(node.isEqual(this.head))) {
11
+ if (this.head !== null) {
12
+ this._remove(node);
13
+ this._insertBefore(this.head, node);
14
+ }
15
+ }
16
+ return this._getIndexByNode(node);
17
+ }
18
+ }
19
+ exports.MoveToFrontSoList = MoveToFrontSoList;
package/dist/Node.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export declare class Node {
2
+ value: unknown;
3
+ next: Node | null;
4
+ prev: Node | null;
5
+ count: number;
6
+ constructor(value: unknown, next?: null, prev?: null);
7
+ isEqual(other: Node | null): boolean;
8
+ }
package/dist/Node.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Node = void 0;
4
+ class Node {
5
+ constructor(value, next = null, prev = null) {
6
+ this.value = value;
7
+ this.next = next;
8
+ this.prev = prev;
9
+ this.count = 0;
10
+ }
11
+ isEqual(other) {
12
+ return this === other;
13
+ }
14
+ }
15
+ exports.Node = Node;
@@ -0,0 +1,6 @@
1
+ import { DoublyLinkedList } from './DoublyLinkedList';
2
+ import { Node } from './Node';
3
+ export declare class TransposeSoList extends DoublyLinkedList {
4
+ constructor(...args: any);
5
+ protected _rearrange(node: Node): number;
6
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransposeSoList = void 0;
4
+ const DoublyLinkedList_1 = require("./DoublyLinkedList");
5
+ class TransposeSoList extends DoublyLinkedList_1.DoublyLinkedList {
6
+ constructor(...args) {
7
+ super(...args);
8
+ }
9
+ _rearrange(node) {
10
+ if (!(node.isEqual(this.head))) {
11
+ const prev = node.prev;
12
+ if (prev !== null) {
13
+ this._remove(node);
14
+ this._insertBefore(prev, node);
15
+ }
16
+ }
17
+ return this._getIndexByNode(node);
18
+ }
19
+ }
20
+ exports.TransposeSoList = TransposeSoList;
@@ -0,0 +1,4 @@
1
+ import { FrequencyCountSoList } from './FrequencyCountSoList';
2
+ import { MoveToFrontSoList } from './MoveToFrontSoList';
3
+ import { TransposeSoList } from './TransposeSoList';
4
+ export { FrequencyCountSoList, MoveToFrontSoList, TransposeSoList };
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransposeSoList = exports.MoveToFrontSoList = exports.FrequencyCountSoList = void 0;
4
+ const FrequencyCountSoList_1 = require("./FrequencyCountSoList");
5
+ Object.defineProperty(exports, "FrequencyCountSoList", { enumerable: true, get: function () { return FrequencyCountSoList_1.FrequencyCountSoList; } });
6
+ const MoveToFrontSoList_1 = require("./MoveToFrontSoList");
7
+ Object.defineProperty(exports, "MoveToFrontSoList", { enumerable: true, get: function () { return MoveToFrontSoList_1.MoveToFrontSoList; } });
8
+ const TransposeSoList_1 = require("./TransposeSoList");
9
+ Object.defineProperty(exports, "TransposeSoList", { enumerable: true, get: function () { return TransposeSoList_1.TransposeSoList; } });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "solists",
3
+ "version": "0.2.0",
4
+ "description": "Implementation of self-organizing lists",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "npm run clean && tsc",
9
+ "clean": "rimraf ./dist",
10
+ "coverage": "nyc --reporter=html ts-mocha tests/test.ts",
11
+ "lint": "eslint ./src ./tests",
12
+ "test": "ts-mocha tests/test.ts"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/emmanuel-ferdman/solists.git"
17
+ },
18
+ "keywords": [
19
+ "data-structure",
20
+ "self-organizing-list"
21
+ ],
22
+ "author": "Emmanuel Ferdman",
23
+ "license": "ISC",
24
+ "bugs": {
25
+ "url": "https://github.com/emmanuel-ferdman/solists/issues"
26
+ },
27
+ "homepage": "https://github.com/emmanuel-ferdman/solists#readme",
28
+ "devDependencies": {
29
+ "@typescript-eslint/eslint-plugin": "^5.46.0",
30
+ "@typescript-eslint/parser": "^5.46.0",
31
+ "eslint": "^8.29.0",
32
+ "nyc": "^15.1.0",
33
+ "ts-mocha": "^10.0.0",
34
+ "ts-node": "^10.9.1",
35
+ "typescript": "^4.9.4"
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ]
40
+ }