|
| 1 | +(function (exports) { |
| 2 | + // helper functions |
| 3 | + const compose = (...fns) => data => fns.reduce((v, f) => f(v), data) |
| 4 | + |
| 5 | + // makes Concatenative Inheritance |
| 6 | + const inherit = (...protos) => Object.assign({}, ...protos) |
| 7 | + |
| 8 | + /** |
| 9 | + base prototype for all linked list types |
| 10 | +
|
| 11 | + all functions are pure functions, since they don't realy on external state |
| 12 | + and produces no side effects, since they are not mutating external objects |
| 13 | + **/ |
| 14 | + const base = { |
| 15 | + node (data, ...rest) { |
| 16 | + const prev = (rest[0] === 'double') ? {prev: null} : {} |
| 17 | + return Object.assign({}, {data, next: null}, prev) |
| 18 | + }, |
| 19 | + setHead (options) { |
| 20 | + let {head, data} = options |
| 21 | + Object.assign(options, {head: data}) |
| 22 | + return options |
| 23 | + }, |
| 24 | + setCurrent (options) { |
| 25 | + let {current, data} = options |
| 26 | + Object.assign(options, {current: data}) |
| 27 | + return options |
| 28 | + }, |
| 29 | + setLength (options) { |
| 30 | + let {length, data} = options |
| 31 | + length += ((data) ? 1 : -1) |
| 32 | + Object.assign(options, {length}) |
| 33 | + return options |
| 34 | + }, |
| 35 | + setNext (options) { |
| 36 | + let {data} = options |
| 37 | + Object.assign(options.current, {next: data}) |
| 38 | + return options |
| 39 | + }, |
| 40 | + findPrev (options) { |
| 41 | + let {head, data} = options |
| 42 | + let c = head |
| 43 | + while (!(c.next === null)) { |
| 44 | + if (c.next.data === data) { |
| 45 | + return c |
| 46 | + } else { |
| 47 | + c = c.next |
| 48 | + } |
| 49 | + } |
| 50 | + return false |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + // single-linked list prototype |
| 55 | + const single = { |
| 56 | + getState () { |
| 57 | + return this |
| 58 | + }, |
| 59 | + setState (options) { |
| 60 | + Object.assign(this, options) |
| 61 | + }, |
| 62 | + add (data) { |
| 63 | + // retrieves the variables and concatenate the new node to be added |
| 64 | + const options = Object.assign(this.getState(), {data: this.node(data)}) |
| 65 | + const fns = (!this.head && !this.current) ? [this.setHead] : [this.setNext] |
| 66 | + // and through function composition, adds an element to the list |
| 67 | + // and the result object it's going to be re assign it to the current state |
| 68 | + compose(...fns, this.setCurrent, this.setLength, this.setState)(options) |
| 69 | + }, |
| 70 | + remove (data) { |
| 71 | + // retrieves variables |
| 72 | + const options = Object.assign(this.getState(), {data}) |
| 73 | + // look if there's a previous link |
| 74 | + const prev = this.findPrev(options) |
| 75 | + let values, fns |
| 76 | + if (prev && !(prev.next === null)) { |
| 77 | + // updates the corresponding values to remove the requested object |
| 78 | + values = {current: prev, data: prev.next.next} |
| 79 | + // functions need to compose to remove the object requested |
| 80 | + fns = [this.setNext, this.setState] |
| 81 | + } else { |
| 82 | + values = {data: options.head.next} |
| 83 | + fns = [this.setHead, this.setState] |
| 84 | + } |
| 85 | + compose(...fns)(Object.assign(options, values)) // removes the object |
| 86 | + this.setLength(Object.assign(options, {data: false})) // decreases the length |
| 87 | + }, |
| 88 | + reverse() { |
| 89 | + let prev = null |
| 90 | + let node = this.head |
| 91 | + |
| 92 | + while (node) { |
| 93 | + let save = node.next |
| 94 | + node.next = prev |
| 95 | + prev = node |
| 96 | + node = save |
| 97 | + } |
| 98 | + Object.assign(this.getState(), {head: prev}) |
| 99 | + }, |
| 100 | + display () { |
| 101 | + let c = this.head |
| 102 | + let show = '' |
| 103 | + while (!(c === null)) { |
| 104 | + show += `${c.data} ${(c.next !== null) ? ' -> ' : ''}` |
| 105 | + c = c.next |
| 106 | + } |
| 107 | + return show |
| 108 | + }, |
| 109 | + contains (data) { |
| 110 | + let c = this.head |
| 111 | + while (!(c === null)) { |
| 112 | + if (c.data === data) { |
| 113 | + return true |
| 114 | + } |
| 115 | + c = c.next |
| 116 | + } |
| 117 | + return false |
| 118 | + }, // the next functions returns a copy of the object requested |
| 119 | + getCurrent() { |
| 120 | + return Object.assign({}, this.current) |
| 121 | + }, |
| 122 | + getList() { |
| 123 | + return Object.assign({}, this.head) |
| 124 | + }, |
| 125 | + size() { |
| 126 | + return this.length |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + // double-linked list prototype |
| 131 | + const double = { |
| 132 | + add (data) { |
| 133 | + const options = Object.assign(this.getState(), {data: this.node(data, 'double')}) |
| 134 | + const fns = (!this.head && !this.current) ? [this.setHead] : [this.setNext, this.setPrev] |
| 135 | + compose(...fns, this.setCurrent, this.setLength, this.setState)(options) |
| 136 | + }, |
| 137 | + remove (data) { |
| 138 | + const options = Object.assign(this.getState(), {data}) |
| 139 | + let prev = this.findPrev(options) |
| 140 | + let values, fns |
| 141 | + if (prev && !(prev.next === null)) { |
| 142 | + values = {current: prev, data: prev.next.next} |
| 143 | + fns = [this.setPrev, this.setNext, this.setState] |
| 144 | + } else { |
| 145 | + values = { |
| 146 | + data: { |
| 147 | + data: options.head.next.data, |
| 148 | + next: options.head.next.next, |
| 149 | + prev: null |
| 150 | + } |
| 151 | + } |
| 152 | + fns = [this.setHead, this.setState] |
| 153 | + } |
| 154 | + compose(...fns)(Object.assign(options, values)) |
| 155 | + this.setLength(Object.assign(options, {data: false})) |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + // circular-linked list prototype |
| 160 | + const circular = { |
| 161 | + display () { |
| 162 | + let c = this.head |
| 163 | + let show = `${c.data} ${(c.next !== this.head) ? ' -> ' : ''}` |
| 164 | + while (!(c.next === this.head)) { |
| 165 | + show += `${c.next.data} ${(c.next !== this.head) ? ' -> ' : ''}` |
| 166 | + c = c.next |
| 167 | + } |
| 168 | + return show |
| 169 | + }, |
| 170 | + contains(data) { |
| 171 | + let c = this.head |
| 172 | + while (!(c.next === this.head)) { |
| 173 | + if (c.data === data) { |
| 174 | + return true |
| 175 | + } |
| 176 | + c = c.next |
| 177 | + } |
| 178 | + return false |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + const singleLL = () => { |
| 183 | + const variables = { head: null, current: null, length: 0 } |
| 184 | + const proto = inherit(base, single) |
| 185 | + return Object.assign(Object.create(proto), variables) |
| 186 | + } |
| 187 | + |
| 188 | + const doubleLL = () => { |
| 189 | + // fn to set the prev link object in the double-linked list |
| 190 | + const setPrev = (options) => { |
| 191 | + let {current, data} = options |
| 192 | + if (data !== null) { |
| 193 | + Object.assign(options.data, {prev: current}) |
| 194 | + } |
| 195 | + return options |
| 196 | + } |
| 197 | + |
| 198 | + const variables = { head: null, current: null, length: 0 } |
| 199 | + // first is updated the base prototype |
| 200 | + const b = Object.assign(base, {setPrev}) |
| 201 | + /* |
| 202 | + then we make an Concatenative Inheritance |
| 203 | + with the base prototype that give us the basic operations, |
| 204 | + with the single prototype that gives the linked lists interface |
| 205 | + and finally set the double prototype that overrides the needed methods |
| 206 | + */ |
| 207 | + const proto = inherit(b, single, double) |
| 208 | + /* |
| 209 | + and finally return the created prototype with its variables |
| 210 | + creating the variables in each type of linked list |
| 211 | + avoids the shared state on the prototype |
| 212 | + */ |
| 213 | + return Object.assign(Object.create(proto), variables) |
| 214 | + } |
| 215 | + |
| 216 | + const circularLL = () => { |
| 217 | + const setHead = (options) => { |
| 218 | + let {data} = options |
| 219 | + Object.assign(options, {head: data}) |
| 220 | + Object.assign(options.head, {next: options.head}) |
| 221 | + return options |
| 222 | + } |
| 223 | + const setNext = (options) => { |
| 224 | + let {data} = options |
| 225 | + let next |
| 226 | + if(data.next === null) { |
| 227 | + Object.assign(data, {next: options.head}) |
| 228 | + } |
| 229 | + Object.assign(options.current, {next: data}) |
| 230 | + return options |
| 231 | + } |
| 232 | + const setPrev = (options) => { |
| 233 | + let {data} = options |
| 234 | + let next |
| 235 | + if(data === null) { |
| 236 | + Object.assign(data, {data: options.head}) |
| 237 | + } else { |
| 238 | + Object.assign(data, {prev: options.current}) |
| 239 | + } |
| 240 | + return options |
| 241 | + } |
| 242 | + |
| 243 | + const variables = {head: null, current: null, length: 0} |
| 244 | + const b = Object.assign(base, {setHead, setNext, setPrev}) |
| 245 | + const proto = inherit(b, single, double, circular) |
| 246 | + |
| 247 | + return Object.assign(Object.create(proto), variables) |
| 248 | + } |
| 249 | + |
| 250 | + Object.assign(exports, {singleLL, doubleLL, circularLL}) |
| 251 | + |
| 252 | +}((typeof module.exports !== undefined) ? module.exports : window)) |
0 commit comments