presidium-websocket
Version:
Presidium WebSocket client and server for Node.js
395 lines (353 loc) • 7.58 kB
JavaScript
/**
* @name LinkedList
*
* @synopsis
* ```coffeescript [specscript]
* type Predicate = (o object|any)=>boolean
*
* type LinkedList = {
* insertBefore: (o object|any, existing object|any)=>undefined,
* prepend: (o object|any)=>undefined,
* insertAfter: (o object|any, existing object|any)=>undefined,
* append: (o object|any)=>undefined,
* delete: (o object|any)=>undefined,
* getByKey: (key string)=>(o? object|any),
* next: (o object|any)=>(o? object|any)
* prev: (o object|any)=>(o? object|any)
* sliceFromElement: (o object|any, count number)=>Array,
* findLeft: (predicate Predicate)=>(o? object|any),
* findRight: (predicate Predicate)=>(o? object|any),
* findIndex: (predicate Predicate)=>(o? object|any),
* slice: (from number, xto? number)=>Array,
* length: number,
* }
*
* new LinkedList(options? {
* keyname?: string,
* }) -> LinkedList
* ```
*/
class LinkedList {
constructor(options = {}) {
// nextMap Map<(element object)=>(nextElement object)>
this.nextMap = new Map()
this.prevMap = new Map()
this.last = null
this.first = null
if (options.keyname) {
this.keyname = options.keyname
this.keyMap = new Map()
}
}
/**
* @name length
*
* @synopsis
* ```coffeescript [specscript]
* length number
* ```
*/
get length() {
return Math.max(this.nextMap.size, this.prevMap.size)
}
/**
* @name insertBefore
*
* @synopsis
* ```coffeescript [specscript]
* insertBefore(o object|any, existing? object|any) -> undefined
* ```
*/
insertBefore(o, existing) {
const { nextMap, prevMap, first, last, keyname } = this
if (existing != null) {
const existingPrev = prevMap.get(existing)
if (existingPrev != null) {
prevMap.set(o, existingPrev)
nextMap.set(existingPrev, o)
} else {
prevMap.set(o, null)
}
prevMap.set(existing, o)
nextMap.set(o, existing)
} else {
prevMap.set(o, null)
}
if (first == null || first == existing) {
this.first = o
}
if (last == null) {
this.last = o
}
if (keyname) {
const key = o[keyname]
this.keyMap.set(key, o)
}
}
/**
* @name prepend
*
* @synopsis
* ```coffeescript [specscript]
* prepend(o object|any) -> undefined
* ```
*/
prepend(o) {
this.insertBefore(o, this.first)
}
/**
* @name insertAfter
*
* @synopsis
* ```coffeescript [specscript]
* insertAfter(o object|any, existing? object|any) -> undefined
* ```
*/
insertAfter(o, existing) {
const { nextMap, prevMap, first, last, keyname } = this
if (existing != null) {
const existingNext = nextMap.get(existing)
if (existingNext != null) {
nextMap.set(o, existingNext)
prevMap.set(existingNext, o)
} else {
nextMap.set(o, null)
}
nextMap.set(existing, o)
prevMap.set(o, existing)
} else {
nextMap.set(o, null)
}
if (first == null) {
this.first = o
}
if (last == null || last == existing) {
this.last = o
}
if (keyname) {
const key = o[keyname]
this.keyMap.set(key, o)
}
}
/**
* @name append
*
* @synopsis
* ```coffeescript [specscript]
* append(o object|any) -> undefined
* ```
*/
append(o) {
this.insertAfter(o, this.last)
}
/**
* @name delete
*
* @synopsis
* ```coffeescript [specscript]
* delete(o object|any) -> undefined
* ```
*/
delete(o) {
const { nextMap, prevMap, first, last, keyname } = this
const next = nextMap.get(o)
const prev = prevMap.get(o)
if (next && prev) { // o is middle item
nextMap.set(prev, next)
prevMap.set(next, prev)
} else if (next) { // o is first item, next is after o
prevMap.set(next, null) // nothing before next once o is gone
} else if (prev) { // o is last item, prev is before o
nextMap.set(prev, null) // nothing after prev once o is gone
}
nextMap.delete(o)
prevMap.delete(o)
if (o == first) {
this.first = next
}
if (o == last) {
this.last = prev
}
if (keyname) {
const key = o[keyname]
this.keyMap.delete(key)
}
}
/**
* @name
*
* @synopsis
* ```coffeescript [specscript]
* getByKey(key string) -> o? object|any
* ```
*/
getByKey(key) {
return this.keyMap.get(key)
}
/**
* @name next
*
* @synopsis
* ```coffeescript [specscript]
* next(o object|any) -> nextObject
* ```
*/
next(o) {
return this.nextMap.get(o)
}
/**
* @name prev
*
* @synopsis
* ```coffeescript [specscript]
* prev(o object|any) -> nextObject
* ```
*/
prev(o) {
return this.prevMap.get(o)
}
/**
* @name sliceFromElement
*
* @synopsis
* ```coffeescript [specscript]
* sliceFromElement(o object|any, count? number) -> Array<object|any>
* ```
*/
sliceFromElement(o, count = Infinity) {
const { nextMap } = this
const result = []
if (!nextMap.has(o)) {
return []
}
let cur = o
result.push(cur)
cur = nextMap.get(cur)
while (cur && result.length < count) {
result.push(cur)
cur = nextMap.get(cur)
}
return result
}
/**
* @name findLeft
*
* @synopsis
* ```coffeescript [specscript]
* type Predicate = (o object|any)=>boolean
*
* findLeft(predicate Predicate) -> o? object|any
* ```
*/
findLeft(predicate) {
let cur = this.first
if (predicate(cur)) {
return cur
}
cur = this.next(cur)
while (cur) {
if (predicate(cur)) {
return cur
}
cur = this.next(cur)
}
return undefined
}
/**
* @name findRight
*
* @synopsis
* ```coffeescript [specscript]
* type Predicate = (o object|any)=>boolean
*
* findRight(predicate Predicate) -> o? object|any
* ```
*/
findRight(predicate) {
let cur = this.last
if (predicate(cur)) {
return cur
}
cur = this.prev(cur)
while (cur) {
if (predicate(cur)) {
return cur
}
cur = this.prev(cur)
}
return undefined
}
/**
* @name findIndex
*
* @synopsis
* ```coffeescript [specscript]
* type Predicate = (o object|any)=>boolean
*
* findIndex(predicate Predicate) -> index number
* ```
*/
findIndex(predicate) {
let index = 0
let cur = this.first
if (predicate(cur)) {
return index
}
while (cur != null) {
index += 1
cur = this.next(cur)
if (predicate(cur)) {
return index
}
}
return -1
}
/**
* @name slice
*
* @synopsis
* ```coffeescript [specscript]
* slice(from number, xto? number) -> Array
* ```
*/
slice(from, xto = Infinity) {
if (this.first == null) {
return []
}
let cur = this.first
let index = 0
while (index < from) {
cur = this.next(cur)
if (cur == null) {
return []
}
index += 1
}
// index == from
const result = [cur]
index += 1
while (index < xto) {
cur = this.next(cur)
if (cur == null) {
break
}
result.push(cur)
index += 1
}
return result
}
/**
* @name shift
*
* @synopsis
* ```coffeescript [specscript]
* shift()
* ```
*/
shift() {
const first = this.first
this.delete(first)
return first
}
}
module.exports = LinkedList