What
Brief
This is a standalone Linked List data structure from the data-structure-typed collection. If you wish to access more
data structures or advanced features, you can transition to directly installing the
complete data-structure-typed package
How
install
npm
npm i linked-list-typed --save
yarn
yarn add linked-list-typed
snippet
text editor operation history
const actions = [
{ type: 'insert', content: 'first line of text' },
{ type: 'insert', content: 'second line of text' },
{ type: 'delete', content: 'delete the first line' }
];
const editorHistory = new DoublyLinkedList<{ type: string; content: string }>(actions);
console.log(editorHistory.last?.type);
console.log(editorHistory.pop()?.content);
console.log(editorHistory.last?.type);
Browser history
const browserHistory = new DoublyLinkedList<string>();
browserHistory.push('home page');
browserHistory.push('search page');
browserHistory.push('details page');
console.log(browserHistory.last);
console.log(browserHistory.pop());
console.log(browserHistory.last);
Use DoublyLinkedList to implement music player
interface Song {
title: string;
artist: string;
duration: number;
}
class Player {
private playlist: DoublyLinkedList<Song>;
private currentSong: ReturnType<typeof this.playlist.getNodeAt> | undefined;
constructor(songs: Song[]) {
this.playlist = new DoublyLinkedList<Song>();
songs.forEach(song => this.playlist.push(song));
this.currentSong = this.playlist.head;
}
playNext(): Song | undefined {
if (!this.currentSong?.next) {
this.currentSong = this.playlist.head;
} else {
this.currentSong = this.currentSong.next;
}
return this.currentSong?.value;
}
playPrevious(): Song | undefined {
if (!this.currentSong?.prev) {
this.currentSong = this.playlist.tail;
} else {
this.currentSong = this.currentSong.prev;
}
return this.currentSong?.value;
}
getCurrentSong(): Song | undefined {
return this.currentSong?.value;
}
loopThroughPlaylist(): Song[] {
const playedSongs: Song[] = [];
const initialNode = this.currentSong;
for (let i = 0; i < this.playlist.size * 2; i++) {
playedSongs.push(this.currentSong!.value);
this.currentSong = this.currentSong!.next || this.playlist.head;
}
this.currentSong = initialNode;
return playedSongs;
}
}
const songs = [
{ title: 'Bohemian Rhapsody', artist: 'Queen', duration: 354 },
{ title: 'Hotel California', artist: 'Eagles', duration: 391 },
{ title: 'Shape of You', artist: 'Ed Sheeran', duration: 233 },
{ title: 'Billie Jean', artist: 'Michael Jackson', duration: 294 }
];
let player = new Player(songs);
player = new Player(songs);
const firstSong = player.getCurrentSong();
const nextSong = player.playNext();
console.log(nextSong);
console.log(firstSong);
player = new Player(songs);
player.playNext();
const currentSong = player.getCurrentSong();
const previousSong = player.playPrevious();
console.log(previousSong);
console.log(currentSong);
player = new Player(songs);
player.playNext();
player.playNext();
player.playNext();
const nextSongToFirst = player.playNext();
console.log(nextSongToFirst);
player = new Player(songs);
player.playNext();
player.playNext();
player.playNext();
player.playNext();
const previousToLast = player.playPrevious();
console.log(previousToLast);
player = new Player(songs);
const playedSongs = player.loopThroughPlaylist();
console.log(playedSongs);
Use DoublyLinkedList to implement LRU cache
interface CacheEntry<K, V> {
key: K;
value: V;
}
class LRUCache<K = string, V = any> {
private readonly capacity: number;
private list: DoublyLinkedList<CacheEntry<K, V>>;
private map: Map<K, DoublyLinkedListNode<CacheEntry<K, V>>>;
constructor(capacity: number) {
if (capacity <= 0) {
throw new Error('lru cache capacity must be greater than 0');
}
this.capacity = capacity;
this.list = new DoublyLinkedList<CacheEntry<K, V>>();
this.map = new Map<K, DoublyLinkedListNode<CacheEntry<K, V>>>();
}
get(key: K): V | undefined {
const node = this.map.get(key);
if (!node) return undefined;
this.moveToFront(node);
return node.value.value;
}
set(key: K, value: V): void {
const node = this.map.get(key);
if (node) {
node.value.value = value;
this.moveToFront(node);
return;
}
if (this.list.size >= this.capacity) {
const removedNode = this.list.tail;
if (removedNode) {
this.map.delete(removedNode.value.key);
this.list.pop();
}
}
const newEntry: CacheEntry<K, V> = { key, value };
this.list.unshift(newEntry);
const newNode = this.list.head;
if (newNode) {
this.map.set(key, newNode);
}
}
private moveToFront(node: DoublyLinkedListNode<CacheEntry<K, V>>): void {
this.list.delete(node);
this.list.unshift(node.value);
}
delete(key: K): boolean {
const node = this.map.get(key);
if (!node) return false;
this.list.delete(node);
this.map.delete(key);
return true;
}
clear(): void {
this.list.clear();
this.map.clear();
}
get size(): number {
return this.list.size;
}
get isEmpty(): boolean {
return this.list.isEmpty();
}
}
const cache = new LRUCache<string, number>(3);
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);
console.log(cache.get('a'));
console.log(cache.get('b'));
console.log(cache.get('c'));
cache.clear();
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);
cache.set('d', 4);
console.log(cache.get('a'));
console.log(cache.get('b'));
console.log(cache.get('c'));
console.log(cache.get('d'));
cache.clear();
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);
cache.get('a');
cache.set('d', 4);
console.log(cache.get('a'));
console.log(cache.get('b'));
console.log(cache.get('c'));
console.log(cache.get('d'));
cache.clear();
cache.set('a', 1);
cache.set('a', 10);
console.log(cache.get('a'));
cache.clear();
cache.set('a', 1);
cache.set('b', 2);
console.log(cache.delete('a'));
console.log(cache.get('a'));
console.log(cache.size);
cache.clear();
cache.set('a', 1);
cache.set('b', 2);
cache.clear();
console.log(cache.size);
console.log(cache.isEmpty);
finding lyrics by timestamp in Coldplay's "Fix You"
const lyricsList = new DoublyLinkedList<{ time: number; text: string }>();
const lyrics = [
{ time: 0, text: "When you try your best, but you don't succeed" },
{ time: 4000, text: 'When you get what you want, but not what you need' },
{ time: 8000, text: "When you feel so tired, but you can't sleep" },
{ time: 12000, text: 'Stuck in reverse' },
{ time: 16000, text: 'And the tears come streaming down your face' },
{ time: 20000, text: "When you lose something you can't replace" },
{ time: 24000, text: 'When you love someone, but it goes to waste' },
{ time: 28000, text: 'Could it be worse?' },
{ time: 32000, text: 'Lights will guide you home' },
{ time: 36000, text: 'And ignite your bones' },
{ time: 40000, text: 'And I will try to fix you' }
];
lyrics.forEach(lyric => lyricsList.push(lyric));
const exactTimeLyric = lyricsList.getBackward(lyric => lyric.value.time <= 36000);
console.log(exactTimeLyric?.text);
const betweenTimeLyric = lyricsList.getBackward(lyric => lyric.value.time <= 22000);
console.log(betweenTimeLyric?.text);
const earlyTimeLyric = lyricsList.getBackward(lyric => lyric.value.time <= -1000);
console.log(earlyTimeLyric);
const lateTimeLyric = lyricsList.getBackward(lyric => lyric.value.time <= 50000);
console.log(lateTimeLyric?.text);
cpu process schedules
class Process {
constructor(
public id: number,
public priority: number
) {}
execute(): string {
return `Process ${this.id} executed.`;
}
}
class Scheduler {
private queue: DoublyLinkedList<Process>;
constructor() {
this.queue = new DoublyLinkedList<Process>();
}
addProcess(process: Process): void {
let current = this.queue.head;
while (current && current.value.priority >= process.priority) {
current = current.next;
}
if (!current) {
this.queue.push(process);
} else {
this.queue.addBefore(current, process);
}
}
executeNext(): string | undefined {
const process = this.queue.shift();
return process ? process.execute() : undefined;
}
listProcesses(): string[] {
return this.queue.toArray().map(process => `Process ${process.id} (Priority: ${process.priority})`);
}
clear(): void {
this.queue.clear();
}
}
let scheduler = new Scheduler();
scheduler.addProcess(new Process(1, 10));
scheduler.addProcess(new Process(2, 20));
scheduler.addProcess(new Process(3, 15));
console.log(scheduler.listProcesses());
scheduler = new Scheduler();
scheduler.addProcess(new Process(1, 10));
scheduler.addProcess(new Process(2, 20));
console.log(scheduler.executeNext());
console.log(scheduler.listProcesses());
scheduler = new Scheduler();
scheduler.addProcess(new Process(1, 10));
scheduler.addProcess(new Process(2, 20));
scheduler.clear();
console.log(scheduler.listProcesses());
API docs & Examples
API Docs
Live Examples
Examples Repository
Data Structures
Standard library data structure comparison
Data Structure Typed | C++ STL | java.util | Python collections |
---|
DoublyLinkedList<E> | list<T> | LinkedList<E> | - |
SinglyLinkedList<E> | - | - | - |
Benchmark
doubly-linked-list
test name | time taken (ms) | executions per sec | sample deviation |
---|
1,000,000 push | 221.57 | 4.51 | 0.03 |
1,000,000 unshift | 229.02 | 4.37 | 0.07 |
1,000,000 unshift & shift | 169.21 | 5.91 | 0.02 |
1,000,000 insertBefore | 314.48 | 3.18 | 0.07 |
singly-linked-list
test name | time taken (ms) | executions per sec | sample deviation |
---|
10,000 push & pop | 212.98 | 4.70 | 0.01 |
10,000 insertBefore | 250.68 | 3.99 | 0.01 |
Built-in classic algorithms
Algorithm | Function Description | Iteration Type |
---|
Software Engineering Design Standards
Principle | Description |
---|
Practicality | Follows ES6 and ESNext standards, offering unified and considerate optional parameters, and simplifies method names. |
Extensibility | Adheres to OOP (Object-Oriented Programming) principles, allowing inheritance for all data structures. |
Modularization | Includes data structure modularization and independent NPM packages. |
Efficiency | All methods provide time and space complexity, comparable to native JS performance. |
Maintainability | Follows open-source community development standards, complete documentation, continuous integration, and adheres to TDD (Test-Driven Development) patterns. |
Testability | Automated and customized unit testing, performance testing, and integration testing. |
Portability | Plans for porting to Java, Python, and C++, currently achieved to 80%. |
Reusability | Fully decoupled, minimized side effects, and adheres to OOP. |
Security | Carefully designed security for member variables and methods. Read-write separation. Data structure software does not need to consider other security aspects. |
Scalability | Data structure software does not involve load issues. |