Typescript での双方向連結リストの実装例です。
※2024/02/25 にバグを修正
■プログラム(list.ts)
/**
*
* bidirectional linked list
*
*/
export class Node {
public obj: any;
public prev: Node | null = null;
public next: Node | null = null;
constructor(obj: any) {
this.obj = obj;
}
}
export class List {
public length: number = 0;
public head: Node | null = null;
public tail: Node | null = null;
constructor(arr?: Array<any>) {
if (arr === undefined) return;
for (let obj of arr) {
this.push(obj);
}
}
/**
* add node to tail
*/
public push_node(node: Node) {
if (this.head === null) {
this.head = node;
}
if (this.tail !== null) {
this.tail.next = node;
node.prev = this.tail;
}
this.tail = node;
this.length += 1;
return this;
}
/**
* add new obj to tail
*/
public push(obj: any) {
const node = new Node(obj);
return this.push_node(node);
}
/**
* add node to head
*/
public unshift_node(node: Node) {
if (this.head === null) {
this.head = this.tail = node;
}
else {
const head = this.head;
head.prev = node;
node.next = head;
this.head = node;
}
this.length += 1;
return this;
}
/**
* add new obj to head
*/
public unshift(obj: any) {
const node = new Node(obj);
return this.unshift_node(node);
}
/**
* remove tail node
*/
public pop_node() {
if (this.tail === null) return null;
const node = this.tail;
this.tail = node.prev;
if (node.prev === null) this.head = null;
node.prev = null;
node.next = null;
this.length -= 1;
return node;
}
/**
* remove tail
*/
public pop() {
const node = this.pop_node();
return node.obj;
}
/**
* remove head node
*/
public shift_node() {
if (this.head === null) return null;
// head -> node <-> next
const node = this.head;
this.head = node.next;
if (this.head !== null) this.head.prev = null;
if (node.next === null) this.tail = null;
node.prev = null;
node.next = null;
this.length -= 1;
return node;
}
/**
* remove head
*/
public shift() {
const node = this.shift_node();
return node.obj;
}
/**
* return nth node
*/
public nth_node(n: number) {
if (n < 0) return null;
if (n >= this.length) return null;
let node = this.head;
if (node === null) return null;
for (let i = 0; i < n; i++) {
if (node.next === null) return null;
node = node.next;
}
if (node === null) return null;
return node;
}
/**
* return nth obj
*/
public nth(n: number) {
const node = this.nth_node(n)
return node.obj;
}
/**
* remove node
*/
public remove_node(node: Node) {
if (node === null) return null; // for tsc
if (node.prev === null) {
this.head = node.next;
}
else {
node.prev.next = node.next;
}
if (node.next === null) {
this.tail = node.prev;
}
else {
node.next.prev = node.prev;
}
this.length -= 1;
return node.obj;
}
/**
* remove nth obj
*/
public remove(n: number) {
if (n < 0) return null;
if (n >= this.length) return null;
let node = this.head;
if (node === null) return null; // for tsc
for (let i = 0; i < n; i++) {
if (node === null) return null; // for tsc
node = node.next;
}
return this.remove_node(node);
}
public slice(from: number, to?: number) {
if (from < 0) return null;
if (from >= this.length) return null;
if (to === undefined) to = this.length;
if (to < 0) return null;
if (to > this.length) return null;
// skip head
let node = this.head;
if (node === null) return null; // for tsc
for (let i = 0; i < from; i++) {
if (node === null) return null; // for tsc
node = node.next;
}
if (node === null) return null; // for tsc
const ret_head = node;
const head_tail = node.prev;
// slice
for (let i = from; i < to; i++) {
if (node === null) return null; // for tsc
node = node.next;
}
if (node === null) return null; // for tsc
const ret_tail = node.prev;
const tail_head = node;
// this
if (head_tail === null) {
this.head = tail_head;
}
else {
head_tail.next = tail_head;
}
if (tail_head === null) {
this.tail = head_tail;
}
else {
tail_head.prev = head_tail;
}
this.length -= (to - from);
// ret
const ret = new List();
if (ret_head !== null) {
ret.head = ret_head;
ret_head.prev = null;
}
if (ret_tail !== null) {
ret.tail = ret_tail;
ret_tail.next = null;
}
ret.length = to - from;
return ret;
}
public copy() {
const new_list = new List();
let node = this.head;
while (node !== null) {
new_list.push(node.obj);
node = node.next;
}
new_list.length = this.length;
return new_list;
}
/**
* insert list at idx (0 - length)
*/
public insert(idx: number, list: List) {
if (idx < 0) return null;
if (idx > this.length) return null;
if (list === null) return null;
if (list.head === null) return this;
if (list.tail === null) return this;
const new_list = list.copy();
if (new_list.head === null) return this;
if (new_list.tail === null) return this;
if (this.head === null) this.head = new_list.head;
if (this.tail === null) this.tail = new_list.tail;
if (this.length === 0) {
this.length = new_list.length;
return this;
}
if (idx === 0) {
const head: Node | null = this.head;
new_list.tail.next = this.head;
this.head = new_list.head;
new_list.tail.next = head;
this.length += new_list.length;
}
else if (idx === this.length) {
const tail: Node | null = this.tail;
new_list.head.prev = tail;
tail.next = new_list.head;
this.tail = new_list.tail;
this.length += new_list.length;
}
else {
// skip
let node: Node | null = this.head;
for (let i = 0; i < idx; i++) {
if (node === null) return null;
node = node.next;
}
// insert
if (node === null) return null;
const prev = node.prev;
if (prev !== null) prev.next = new_list.head;
new_list.head.prev = prev;
new_list.tail.next = node;
this.length += new_list.length;
}
return this;
}
public to_array() {
const arr = new Array(this.length);
let node = this.head;
for (let i = 0; i < this.length; i++) {
if (node === null) return null;
arr[i] = node.obj;
node = node.next;
}
return arr;
}
}
■動作確認
import { List } from '../lib/list';
(() => {
// push
const list1 = new List(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n']);
list1.push('o');
console.log(`push('o')`);
console.log(list1.to_array());
console.log(list1.length);
if (list1.head == null) {
console.log('error: list1.head is null');
return;
}
console.log(`head: obj: ${list1.head.obj}, prev: ${list1.head.prev}, next: ${list1.head.next}`);
// pop
let obj;
obj = list1.pop();
console.log(`pop()`);
console.log(obj);
console.log(list1.to_array());
console.log(list1.length);
if (list1.head == null) {
console.log('error: list1.head is null');
return;
}
console.log(`head: obj: ${list1.head.obj}, prev: ${list1.head.prev}, next: ${list1.head.next}`);
// unshift
list1.unshift('-');
console.log(`unshift('-')`);
console.log(list1.to_array());
console.log(list1.length);
if (list1.head == null) {
console.log('error: list1.head is null');
return;
}
console.log(`head: obj: ${list1.head.obj}, prev: ${list1.head.prev}, next: ${list1.head.next}`);
// shift
obj = list1.shift();
console.log(`shift()`);
console.log(obj);
console.log(list1.to_array());
console.log(list1.length);
if (list1.head == null) {
console.log('error: list1.head is null');
return;
}
console.log(`head: obj: ${list1.head.obj}, prev: ${list1.head.prev}, next: ${list1.head.next}`);
// nth
obj = list1.nth(2);
console.log(`nth(2)`);
console.log(obj);
console.log(list1.to_array());
// remove_node
let node;
node = list1.head;
if (node == null) {
console.log('error: list1.head is null');
return;
}
console.log(`head: obj: ${node.obj}, prev: ${node.prev}, next: ${node.next}`);
obj = list1.remove_node(node);
console.log(`remove_node(head)`);
console.log(obj);
console.log(list1.to_array());
node = list1.head;
if (node == null) {
console.log('error: list1.head.next is null');
return;
}
node = node.next;
if (node == null) {
console.log('error: list1.head.next is null');
return;
}
list1.remove_node(node);
console.log(`remove_node(head.next)`);
console.log(list1.to_array());
node = list1.tail;
if (node == null) {
console.log('error: list1.tail is null');
return;
}
list1.remove_node(node);
console.log(`remove_node(tail)`);
console.log(list1.to_array());
// remove
obj = list1.remove(2);
console.log(`remove(2)`);
console.log(obj);
console.log(list1.to_array());
// slice
obj = list1.slice(1, 4);
console.log(`slice(1, 4)`);
if (obj !== null) console.log(obj.to_array());
console.log(list1.to_array());
// copy
obj = list1.copy();
console.log(`copy()`);
if (obj !== null) console.log(obj.to_array());
console.log(list1.to_array());
// insert
const list2 = new List(['x', 'y', 'z']);
obj = list1.insert(1, list2);
console.log(`insert(1, ['x', 'y', 'z'])`);
if (obj !== null) console.log(obj.to_array());
console.log(list1.length);
obj = list1.insert(0, new List(['-']));
console.log(`insert(0, ['-'])`);
if (obj !== null) console.log(obj.to_array());
console.log(list1.length);
obj = list1.insert(8, new List(['--']));
console.log(`insert(8, ['--'])`);
if (obj !== null) console.log(obj.to_array());
console.log(list1.length);
})();
■実行例
push('o')
[
'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l',
'm', 'n', 'o'
]
15
head: obj: a, prev: null, next: [object Object]
pop()
o
[
'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l',
'm', 'n'
]
14
head: obj: a, prev: null, next: [object Object]
unshift('-')
[
'-', 'a', 'b', 'c',
'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k',
'l', 'm', 'n'
]
15
head: obj: -, prev: null, next: [object Object]
shift()
-
[
'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l',
'm', 'n'
]
14
head: obj: a, prev: null, next: [object Object]
nth(2)
c
[
'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l',
'm', 'n'
]
head: obj: a, prev: null, next: [object Object]
remove_node(head)
a
[
'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm',
'n'
]
remove_node(head.next)
[
'b', 'd', 'e', 'f',
'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n'
]
remove_node(tail)
[
'b', 'd', 'e', 'f',
'g', 'h', 'i', 'j',
'k', 'l', 'm'
]
remove(2)
e
[
'b', 'd', 'f', 'g',
'h', 'i', 'j', 'k',
'l', 'm'
]
slice(1, 4)
[ 'd', 'f', 'g' ]
[
'b', 'h', 'i',
'j', 'k', 'l',
'm'
]
copy()
[
'b', 'h', 'i',
'j', 'k', 'l',
'm'
]
[
'b', 'h', 'i',
'j', 'k', 'l',
'm'
]
insert(1, ['x', 'y', 'z'])
[
'b', 'x', 'y', 'z',
'h', 'i', 'j', 'k',
'l', 'm'
]
10
insert(0, ['-'])
[
'-', 'b', 'x', 'y',
'z', 'h', 'i', 'j',
'k', 'l', 'm'
]
11
insert(8, ['--'])
[
'-', 'b', 'x', 'y',
'z', 'h', 'i', 'j',
'--', 'k', 'l', 'm'
]
12