feat(List)

This commit is contained in:
2025-04-08 21:08:25 +03:00
commit 56be57ada1
12 changed files with 1942 additions and 0 deletions

133
src/List.js Normal file
View File

@ -0,0 +1,133 @@
import { SequenceContainer } from "./containers/Sequence"
import { BidirectionalIterator } from "./iterators/Bidirectional"
import { mixinClasses } from "./utils/mixin"
export class List {
constructor(...args) {
this._end = new ListIterator(new ListItem())
this._begin = this._end
this._size = 0
if (args.length >= 1) {
this.assign(...args)
}
}
move(rhs) {
this._begin = rhs._begin
this._end = rhs._end
this._size = rhs._size
rhs._begin = undefined
rhs._end = undefined
rhs._size = 0
return this
}
size() {
return this._size
}
begin() {
return this._begin.clone()
}
end() {
return this._end.clone()
}
_shiftRight(dest, generator) {
let prev, res, new_item, item = dest.deref()
for (const value of generator) {
prev = item._prev
new_item = new ListItem(value)
res = res ?? new_item
item._prev = new_item
new_item._next = item
new_item._prev = prev
if (prev) {
prev._next = new_item
} else {
this._begin = new ListIterator(res)
}
this._size++
}
return new ListIterator(res ?? dest.deref())
}
_shiftLeft(dest, generator) {
let item = dest.deref(), start_item = item
for (const _ of generator) {
item = item._prev
this._size--
}
if (item._prev) {
item._prev._next = start_item
} else {
this._begin = new ListIterator(start_item)
}
start_item._prev = item._prev
return dest
}
}
List.from = function (obj) {
const list = new List()
for (const value of obj) {
list.push_back(value)
}
return list
}
mixinClasses(List, SequenceContainer)
export class ListItem {
constructor(value) {
this.value = value
this._next = undefined
this._prev = undefined
}
get next() { return this._next }
get prev() { return this._prev }
}
export class ListIterator {
constructor(item) {
this._item = item
}
clone() {
return new ListIterator(this._item)
}
copy(rhs) {
this._item = rhs._item
return this
}
deref() {
return this._item
}
eq(rhs) {
return this._item == rhs._item
}
inc() {
this._item = this._item.next
return this
}
dec() {
this._item = this._item.prev
return this
}
}
mixinClasses(ListIterator, BidirectionalIterator)

178
src/containers/Sequence.js Normal file
View File

@ -0,0 +1,178 @@
import { BidirectionalIterator } from "../iterators/Bidirectional"
import { satisfiesConcept } from "../utils/concept"
export class SequenceContainer {
clone() {
return new this.constructor(this.begin(), this.end())
}
copy(rhs) {
this.assign(rhs.begin(), rhs.end())
return this
}
empty() {
return this.size() == 0
}
get front() {
return this.begin().value
}
set front(value) {
return this.begin().value = value
}
get back() {
return this.end().dec().value
}
set back(value) {
return this.end().dec().value = value
}
insert(dest, arg2, arg3) {
if (
BidirectionalIterator.is(arg2) &&
BidirectionalIterator.is(arg3)
) {
return this._shiftRight(dest,
{
[Symbol.iterator]: () => {
const local_begin = arg2.clone()
return {
next() {
const res = {
done: local_begin.eq(arg3),
value: local_begin.value,
}
local_begin.inc()
return res
}
}
}
}
)
} else if (
typeof arg3 == 'number' && arg3 >= 0 ||
arg3 === undefined
) {
return this._shiftRight(dest,
{
[Symbol.iterator]: () => {
let local_n = arg3 ?? 1
return {
next: () => ({ done: local_n-- <= 0, value: arg2 })
}
}
}
)
} else {
throw new Error('incorrect args')
}
}
assign(...args) {
if (this.empty()) {
return this.insert(this.begin(), ...args)
}
this.erase(this.begin(), this.insert(this.end(), ...args))
return this.begin()
}
erase(begin, end = begin.clone().inc()) {
return this._shiftLeft(end,
{
[Symbol.iterator]: () => {
let local_begin = begin.clone()
return {
next() {
const res = {
done: local_begin.eq(end)
}
local_begin.inc()
return res
}
}
}
}
)
}
clear() {
this.erase(this.begin(), this.end())
}
push_front(value) {
this.insert(this.begin(), value)
}
push_back(value) {
this.insert(this.end(), value)
}
pop_front() {
this.erase(this.begin())
}
pop_back() {
this.erase(this.end().dec())
}
toJSON() {
return this.toString()
}
toString() {
let res = []
for (const value of this) {
res.push(JSON.stringify(value))
}
return '[ ' + res.join(', ') + ' ]'
}
[Symbol.iterator]() {
let it = this.begin()
const it_end = this.end()
return {
next() {
const res = {
done: it.eq(it_end),
value: it.value,
}
it.inc()
return res
}
}
}
}
SequenceContainer.is = function (obj, ...args) {
const pure_virtual = [
'move',
'size',
'begin',
'end',
'_shiftRight',
'_shiftLeft',
]
const virtual = [
'clone',
'copy',
'empty',
'front',
'back',
'insert',
'assign',
'erase',
'clear',
'push_front',
'push_back',
'pop_front',
'pop_back',
Symbol.iterator,
'toJSON',
'toString',
]
return satisfiesConcept(obj, pure_virtual, virtual, ...args)
}

4
src/index.js Normal file
View File

@ -0,0 +1,4 @@
export { ForwardIterator } from "./iterators/Forward"
export { BidirectionalIterator } from "./iterators/Bidirectional"
export { SequenceContainer } from "./containers/Sequence"
export { List, ListIterator, ListItem } from "./List"

View File

@ -0,0 +1,11 @@
import { satisfiesConcept } from "../utils/concept"
import { mixinClasses } from "../utils/mixin"
import { ForwardIterator } from "./Forward"
export class BidirectionalIterator { }
mixinClasses(BidirectionalIterator, ForwardIterator)
BidirectionalIterator.is = function (obj, ...args) {
return ForwardIterator.is(obj, ...args) && satisfiesConcept(obj, ['dec'], [], ...args)
}

35
src/iterators/Forward.js Normal file
View File

@ -0,0 +1,35 @@
import { satisfiesConcept } from "../utils/concept"
export class ForwardIterator {
move(rhs) {
return this.copy(rhs)
}
get value() {
return this.deref().value
}
set value(value) {
return this.deref().value = value
}
neq(rhs) {
return !this.eq(rhs)
}
}
ForwardIterator.is = function (obj, ...args) {
const pure_virtual = [
'clone',
'copy',
'deref',
'inc',
'eq'
]
const virtual = [
'move',
'value',
'neq',
]
return satisfiesConcept(obj, pure_virtual, virtual, ...args)
}

34
src/utils/concept.js Normal file
View File

@ -0,0 +1,34 @@
export function satisfiesConcept(obj, pure_virtual, virtual, debug) {
if (typeof obj !== 'object' || obj === null) {
return false;
}
if (debug) {
const missingMethods = []
pure_virtual.forEach((method) => {
if (typeof obj[method] !== 'function') {
missingMethods.push(method)
}
})
virtual.forEach((method) => {
if (!(method in obj)) {
missingMethods.push(method)
}
})
if (missingMethods.length !== 0) {
console.debug(obj.constructor.name, missingMethods)
return false
}
} else {
if (!pure_virtual.every((method) => typeof obj[method] === 'function')) {
return false;
}
if (!virtual.every((method) => method in obj)) {
return false;
}
}
return true
}

7
src/utils/mixin.js Normal file
View File

@ -0,0 +1,7 @@
export function mixinClasses(dest, ...sources) {
for (const source of sources) {
const properties = Object.getOwnPropertyDescriptors(source.prototype)
delete properties.constructor
Object.defineProperties(dest.prototype, properties)
}
}