SMALL METHODS. BIG IDEAS.
dslearn
Learn data structures
by using them.
Organize information. Find what matters. Build something that works. Clear JavaScript
and TypeScript classes for your next learning project.
npm install dslearn
QUEUE / FIRST IN, FIRST OUT
12→24→36
enqueue. peek. dequeue.
13data structures
TypeScriptgenerics included
Zeroruntime dependencies
YOUR TOOLBOX
Choose the shape of your data.
ONE PROJECT. ALL THE PIECES.
Build a support desk.
Follow a ticket through arrival order, urgent work, agent rotation, topic search, and
undo. Discover why each structure belongs in the flow.
Open the hands-on learning path →
TRY THE ACTUAL PACKAGE
Code examples
Explore all 13 structures in this runnable JavaScript example.
import {
BinarySearchTree,
Deque,
DisjointSet,
DoublyLinkedList,
Graph,
HashTable,
LinkedList,
MaxHeap,
MinHeap,
PriorityQueue,
Queue,
Stack,
Trie,
} from "../dist/index.js";
const list = new LinkedList();
list.append("second");
list.prepend("first");
console.log("LinkedList", list.toArray());
const doublyLinkedList = new DoublyLinkedList();
doublyLinkedList.append(1);
doublyLinkedList.append(2);
console.log("DoublyLinkedList", [...doublyLinkedList]);
const stack = new Stack();
stack.push("bottom");
stack.push("top");
console.log("Stack pop", stack.pop());
const queue = new Queue();
queue.enqueue("first");
queue.enqueue("second");
console.log("Queue dequeue", queue.dequeue());
const deque = new Deque();
deque.pushFront(2);
deque.pushFront(1);
deque.pushBack(3);
console.log("Deque", [...deque]);
const table = new HashTable();
table.set("language", "TypeScript");
console.log("HashTable", table.get("language"));
const priorityQueue = new PriorityQueue((left, right) => left.priority - right.priority);
priorityQueue.enqueue({ priority: 2, task: "later" });
priorityQueue.enqueue({ priority: 1, task: "sooner" });
console.log("PriorityQueue", priorityQueue.dequeue());
const sets = new DisjointSet();
sets.add("north");
sets.add("south");
sets.union("north", "south");
console.log("DisjointSet connected", sets.connected("north", "south"));
const tree = new BinarySearchTree((left, right) => left - right);
[5, 3, 7].forEach((value) => tree.insert(value));
console.log("BinarySearchTree", tree.inOrderTraversal());
const minHeap = new MinHeap();
[5, 1, 3].forEach((value) => minHeap.insert(value));
console.log("MinHeap minimum", minHeap.extractMin());
const maxHeap = new MaxHeap();
[5, 1, 3].forEach((value) => maxHeap.insert(value));
console.log("MaxHeap maximum", maxHeap.extractMax());
const trie = new Trie();
trie.insert("structure");
trie.insert("strong");
console.log("Trie prefix", trie.wordsWithPrefix("str"));
const graph = new Graph();
graph.addEdge("A", "B");
graph.addEdge("B", "C");
console.log("Graph BFS", graph.bfs("A"));
dslearn
Learn data structures by using them. A data structure is a way to organize information so a program can store, find, and change it. dslearn gives beginners clear JavaScript and TypeScript classes to try, with methods such as push, pop, enqueue, search, and insert. Explore lists, stacks, queues, hash tables, trees, heaps, tries, and graphs through small examples and a hands-on learning project.
Website & documentation
Installation
npm install dslearn
The published library requires Node.js 18 or newer. The development test runner (Vitest 4) requires Node.js 20 or newer.
Quick Start
import { LinkedList, Graph, MinHeap } from "dslearn";
const names = new LinkedList<string>();
names.append("Ada");
names.prepend("Grace");
console.log(names.toArray()); // ["Grace", "Ada"]
const jobs = new MinHeap<number>();
jobs.insert(8);
jobs.insert(3);
console.log(jobs.extractMin()); // 3
const graph = new Graph<string>();
graph.addEdge("home", "work");
console.log(graph.hasPath("home", "work")); // true
The complete runnable example is in examples/all-structures.mjs; after building, run it with node examples/all-structures.mjs.
To learn how the structures work together in a real flow, follow the hands-on learning path and run npm run learn. It wires all 13 structures into a support-desk ticket simulator.
Data Structures
Linear
import { LinkedList, DoublyLinkedList, Stack, Queue, Deque } from "dslearn";
const list = new LinkedList<number>();
list.append(10);
list.insertAt(0, 5);
list.removeAt(1); // 10
const doubly = new DoublyLinkedList<string>();
doubly.append("first");
doubly.prepend("new first");
const stack = new Stack<string>();
stack.push("bottom");
stack.push("top");
stack.pop(); // "top"
const queue = new Queue<number>();
queue.enqueue(1);
queue.dequeue(); // 1
const deque = new Deque<number>();
deque.pushFront(2);
deque.pushBack(3);
deque.popFront(); // 2
LinkedList<T> and DoublyLinkedList<T> provide append, prepend, insertAt, remove, removeAt, find, contains, get, clear, toArray, size, and iteration. Insertions return true on success and false for an invalid index. remove returns whether a match was removed; removeAt and get return undefined for invalid indexes. find(predicate) returns the first matching value or undefined when no item matches. Equality for remove and contains is Object.is, and remove removes only the first match. Iteration follows head-to-tail order.
Stack<T> provides push, pop, peek, isEmpty, clear, size, and iteration (top to bottom). Queue<T> provides enqueue, dequeue, peek, isEmpty, clear, size, and iteration (front to back). Empty pop, peek, and dequeue operations return undefined. Queue storage is a growable circular buffer.
Deque<T> provides pushFront, pushBack, popFront, popBack, peekFront, peekBack, isEmpty, clear, size, and front-to-back iteration. Empty pops and peeks return undefined. Its circular buffer grows geometrically.
Hash-Based
import { HashTable, PriorityQueue, DisjointSet } from "dslearn";
const scores = new HashTable<string, number>();
scores.set("Ada", 98);
scores.get("Ada"); // 98
type Job = { priority: number; name: string };
const jobs = new PriorityQueue<Job>((a, b) => a.priority - b.priority);
jobs.enqueue({ priority: 1, name: "urgent" });
jobs.dequeue()?.name; // "urgent"
const groups = new DisjointSet<string>();
groups.add("A");
groups.add("B");
groups.union("A", "B");
groups.connected("A", "B"); // true
HashTable<K, V> provides set, get, has, delete, clear, keys, values, entries, size, and [Symbol.iterator] (entries). set updates an existing key and returns the table. get returns undefined for a missing key; use has to distinguish a stored undefined value. delete reports whether a key existed. Its constructor is new HashTable<K, V>(initialCapacity = 16, hashFunction?); capacity must be a positive integer. It uses separate-chaining buckets, resizes above a 0.75 load factor, and defaults to SameValueZero key equality. Primitive keys are supported. Object and function keys use reference identity, not structural equality. Symbols use symbol identity. A custom hash function must return a stable finite number for each key; equal keys must produce equal hashes. Collisions are resolved within buckets.
PriorityQueue<T> provides enqueue, dequeue, peek, isEmpty, clear, size, and iteration (heap-array order, not sorted order). The constructor accepts a comparator where a negative result means the left item has higher priority; ties are dequeued in insertion order. Empty dequeue and peek return undefined. The default comparator uses JavaScript <; provide a comparator for objects or domain-specific ordering.
DisjointSet<T> provides add, has, find, union, connected, clear, and size. add reports whether a new value was added. find returns a representative or undefined if absent; has disambiguates an absent value from an inserted undefined. union returns true only when it joins two existing components; it returns false when either value is missing or they are already connected. connected is false if either item is absent. Values use JavaScript Map key equality (SameValueZero).
Trees and Trie
import { BinarySearchTree, MinHeap, MaxHeap, Trie } from "dslearn";
const byAge = new BinarySearchTree<{ name: string; age: number }>((a, b) => a.age - b.age);
byAge.insert({ name: "Ada", age: 36 });
byAge.inOrderTraversal();
const min = new MinHeap<number>();
const max = new MaxHeap<number>();
min.insert(4);
max.insert(4);
const words = new Trie();
words.insert("structure");
words.startsWith("str"); // true
words.wordsWithPrefix("str"); // ["structure"]
BinarySearchTree<T> provides insert, search, contains, delete, inOrderTraversal, preOrderTraversal, postOrderTraversal, levelOrderTraversal, clear, and size. Its comparator defaults to JavaScript <. Comparator-equal duplicates are ignored (insert returns false); successful insertions and deletions return true. search returns the stored value or undefined, while contains avoids that ambiguity. Traversals return new arrays. A plain BST can become unbalanced; recursive insertion, deletion, and depth-first traversals may reach call-stack limits for extremely skewed trees.
MinHeap<T> provides insert, peek, extractMin, isEmpty, clear, and size. MaxHeap<T> offers the same API with extractMax. Both accept a comparator and return undefined from an empty peek or extraction.
Trie stores case-sensitive strings and provides insert, search, contains, startsWith, wordsWithPrefix, delete, clear, and size. Insert and delete return whether the set of stored words changed. The empty string is a valid word and prefix. startsWith("") is always true, including for an empty trie. Prefix results are returned in child insertion order.
Graph
import { Graph } from "dslearn";
const graph = new Graph<string>(); // undirected by default
graph.addEdge("A", "B", 2);
graph.addEdge("B", "C");
graph.bfs("A"); // ["A", "B", "C"]
graph.hasPath("A", "C"); // true
const directed = new Graph<string>(true);
directed.addEdge("source", "target");
Graph<T> uses adjacency lists and provides addVertex, addEdge, removeVertex, removeEdge, getNeighbors, bfs, dfs, hasPath, and size. addVertex and removal methods return booleans. addEdge creates missing vertices and returns void; its optional weight defaults to 1 and must be finite. Re-adding an edge updates its weight. Undirected edges are stored in both directions; self-loops are allowed. The constructor's directed option defaults to false. getNeighbors returns a new array of { vertex, weight } records and returns [] for a missing vertex. BFS and DFS return visit-order arrays and return [] for a missing start; both visit only the start's reachable component. hasPath is false if either vertex is missing, and true for a vertex's path to itself if it exists.
API Return and Edge-Case Behavior
- Methods that remove or search by value generally return
boolean when absence must be distinguishable; value-returning get/peek/pop/dequeue methods use undefined for absence. If undefined itself is a valid stored value, use a membership method (has, contains, or size) when available to distinguish it.
- List indexes must be integers.
insertAt permits indexes from 0 through size; retrieval/removal require indexes from 0 through size - 1. Invalid indexes do not mutate the list.
- List and graph values can include
null or undefined; JavaScript equality rules apply as described per structure. Custom-object list matching uses identity via Object.is.
- Hash-table object keys compare by reference. To use structural object keys, supply a stable custom hash strategy and use primitive canonical keys, or wrap the object in an identity-aware key chosen by the application. A custom hash function still relies on SameValueZero equality.
- Ordered structures require a consistent comparator (negative, zero, positive). The default comparator is suitable for consistently ordered primitive values; use custom comparators for records and mixed/domain-specific ordering.
- Duplicate list items are allowed; list
remove removes the first. Hash-table duplicate keys replace values. BST and Trie duplicate insertion is ignored. Graph duplicate edges update weights. Priority-queue ties remain stable.
Complexity
| Structure / operation |
Expected time |
Extra space |
| Linked-list append/prepend |
O(1) |
O(1) per node |
| Linked-list indexed access/insert/remove/search |
O(n) |
O(1) excluding returned arrays |
| Doubly-linked-list append/prepend |
O(1) |
O(1) per node |
| Doubly-linked-list indexed access/insert/remove/search |
O(n) |
O(1) excluding returned arrays |
| Stack push/pop/peek |
O(1) |
O(1) per node |
| Queue enqueue/dequeue/peek |
Amortized O(1); resize O(n) |
O(n) |
| Deque endpoint operations |
Amortized O(1); resize O(n) |
O(n) |
| Hash-table get/set/delete |
Expected O(1), worst O(n) |
O(capacity + entries) |
| Hash-table iteration |
O(capacity + entries) |
O(1) excluding yielded entries |
| Priority-queue enqueue/dequeue |
O(log n) |
O(n) |
| Priority-queue peek |
O(1) |
O(1) |
| Disjoint-set find/union |
Amortized O(α(n)) |
O(n) |
| BST operations |
Average O(log n), worst O(n) |
O(n) |
| Heap insert/extract |
O(log n) |
O(n) |
| Heap peek |
O(1) |
O(1) |
| Trie operation on a string of length L |
O(L), excluding output |
O(total characters) |
| Trie wordsWithPrefix |
O(P + returned text) |
O(returned text) |
| Graph add-edge/get-neighbors |
Expected O(1) / O(degree) |
O(V + E) |
| Graph BFS/DFS/hasPath |
O(V + E) |
O(V) |
| Graph remove-vertex |
O(V + degree) |
O(1) |
Here n is the number of stored items, V vertices, E stored adjacency entries, L string length, P prefix length, and α the inverse Ackermann function. Hash-table bounds assume a well-distributed hash; adversarial collisions degrade operations to linear time. Graph E counts both stored directions for undirected edges.
Development
npm install
npm test
npm run build
npm run lint
npm run format:check
npm run benchmark
Benchmarks are informational microbenchmarks, not performance guarantees. They compare operations with compatible built-in operations and depend on the runtime and machine.
Contributing
Keep changes focused, add tests for public behavior and edge cases, and run npm run validate before opening a pull request. Public API or behavior changes should be documented in this README and the changelog.
See the npm publishing guide for release steps and the Git guide for repository workflow and command explanations.
License
MIT. See LICENSE.
Data Structures Learning Path
Learn the structures by following a support ticket from intake to assignment. The runnable version is examples/support-desk.mjs; start it with:
npm run learn
The script prints the result of each part of the workflow. Read it top to bottom, change one structure at a time, and rerun it.
The Ticket Flow
flowchart LR
A[New tickets] --> B[Queue: preserve arrival order]
B --> C[Priority queue: urgent work first]
C --> D[Deque: rotate available agents]
D --> E[Hash table: lookup by ticket ID]
D --> F[Stack: undo last assignment]
D --> G[Linked lists: assignment/event history]
E --> H[BST: tickets in ID order]
A --> I[Heaps: next deadline and highest severity]
A --> J[Trie: topic prefix search]
A --> K[Graph: office routes]
A --> L[Disjoint set: related system incidents]
Step 1: Choose the Right Structure
The same ticket appears in more than one structure because each structure answers a different question:
| Question |
Structure |
Why it fits |
| Which ticket arrived first? |
Queue<Ticket> |
FIFO order, with efficient front removal. |
| Which ticket is most urgent? |
PriorityQueue<Ticket> |
The comparator ranks severity; equal priorities remain stable. |
| Which agent is next, and who can be rotated? |
Deque<Agent> |
Add and remove agents at either end. |
| Find a ticket when its ID is known |
HashTable<string, Ticket> |
Expected constant-time lookup by key. |
| Undo the most recent assignment |
Stack<Assignment> |
LIFO naturally reverses the last action first. |
| Keep assignment and event history |
LinkedList and DoublyLinkedList |
Append events and inspect or remove positions. |
| List tickets in ID order |
BinarySearchTree<Ticket> |
In-order traversal visits comparator order. |
| Find the next SLA deadline / highest severity |
MinHeap<number> / MaxHeap<number> |
Read the smallest or largest value efficiently. |
| Find topics by prefix |
Trie |
Prefix lookup follows only the requested character path. |
| Check whether two offices are connected |
Graph<Office> |
BFS-based path search over adjacency lists. |
| Group systems affected by one incident |
DisjointSet<System> |
Efficiently merge and compare connected groups. |
Step 2: Trace One Ticket
Open the example and follow newTickets. Each ticket is enqueued for arrival order, indexed by ID, inserted into the ID tree, and used to update deadline/severity summaries and topic search.
Then inspect the two loops:
- Arrival queue entries are transferred to the priority queue. This changes the order policy from “first arrived” to “most severe first.”
- Each selected ticket takes the next agent from the front of the deque. That agent goes to the back, so the next ticket gets a fair turn.
- Before changing ticket status, the previous state is pushed to the assignment stack. The event and ticket ID are appended to linked-list histories.
- The final operation pops one assignment and restores its previous ticket state.
The output is deterministic. The assignments begin HD-101, HD-102, HD-103, even though HD-103 arrived first, because severity determines triage order.
Step 3: Make a Change
Try these small exercises in the example, then run npm run learn again:
- Add a severity-5 ticket and observe where it appears. If it ties with another ticket, check which was enqueued first.
- Change
availableAgents to add an agent at the front. Trace how rotation changes.
- Add a disconnected office to
officeRoutes; hasPath should remain false until an edge connects it.
- Add another system to the Wi-Fi incident with
affectedSystems.union, then compare it with connected.
- Search the trie for a topic prefix that is not present. Add the topic and rerun.
- Add a ticket with an ID between existing IDs and inspect the in-order BST result.
- Undo twice. Decide what should happen when the stack is empty before changing the code.
- Replace the severity comparator with a deadline comparator. Explain how that changes the scheduler policy.
Step 4: Verify the Contracts
The Vitest suite exercises normal, empty, duplicate, invalid-index, collision, comparator, traversal, and removal behavior across the public classes:
npm test
npm run validate
When changing the lesson, add a test for the behavior you expect. In particular, test both a successful operation and its missing/empty counterpart; the return values are part of the data structure contract.
Keep Exploring
Use the API reference for each method's exact return behavior and the complexity table to compare tradeoffs. The data structure that makes one operation simple may make another one slower; choosing well starts with asking what questions the application needs to answer.
Contributing to dslearn
Required: Use Pull Requests
Everyone except the repository owner Owaix must submit changes to the default branch through a pull request. Only Owaix may bypass the pull-request requirement and push directly to the default branch. Owaix should still prefer pull requests when review or discussion is useful.
An active GitHub ruleset named Protect master with PRs targets the default branch, currently master:
- Require a pull request before merging.
Owaix is the only bypass actor; everyone else, including administrators, must use a pull request.
- Verify that a non-
Owaix direct push to master is rejected.
Update the ruleset target if the default branch is renamed. The ruleset is enforced by GitHub; this document records the policy.
Change Workflow
git switch -c feat/short-description
npm ci
npm run validate
git add <intended-files>
git diff --cached
git commit -m "feat: describe the change"
git push -u origin feat/short-description
Open a pull request from the topic branch to master. Include the purpose, tests run, and any behavior or compatibility changes. Do not force-push a branch after others have based work on it.
Use fix/ for bug fixes, docs/ for documentation, and test/ for test-only changes. Keep changes focused and add tests for public behavior.
Git Workflow Guide
This guide explains a small, reviewable Git workflow for dslearn. Git records source history; npm publishing is a separate release action.
Pull-Request-Only Policy
Everyone except repository owner Owaix must send changes to the default branch through a pull request. Owaix is the only person allowed to bypass that requirement and push directly to the default branch, but should still prefer a pull request when review or discussion is useful. Contributors and maintainers other than Owaix must push work to a topic branch and merge through a pull request.
GitHub enforces this policy with an active ruleset named Protect master with PRs targeting the default branch, currently master:
- The rule requires a pull request before merging.
Owaix is the only actor on the bypass list; all other users, including administrators, must use pull requests.
- Verify that a non-
Owaix direct push to master is rejected.
If the default branch is renamed, update the rule's target. See CONTRIBUTING.md for the contributor workflow.
Check Repository State
git status --short --branch
git branch --show-current
git log --oneline -5
status shows changed and untracked files, branch --show-current prints the active branch, and log shows recent commits. Review status before staging so generated or unrelated files are not included by accident. node_modules/, dist/, coverage output, and tarballs are ignored by .gitignore.
Start a Change
If the project has not been initialized as a Git repository yet, create its first commit locally:
git init
git add .
git diff --cached
git commit -m "chore: initialize dslearn repository"
This is appropriate for a new, clean repository; .gitignore keeps generated output and dependencies out of the commit. Do not push this commit directly to the default branch. Push it on a setup branch and merge it through a pull request. In an existing repository, stage only the files you intend to commit as shown below.
Create a short-lived branch for each change:
git switch -c feat/describe-change
Use a descriptive branch name such as feat/trie-prefix-search or fix/hash-table-resize.
Stage and Commit
Run checks before committing:
npm run validate
Review the diff, stage only the intended files, then commit:
git diff
git add README.md package.json package-lock.json CHANGELOG.md LICENSE .gitignore
git add src tests docs examples benchmarks
git add tsconfig.json vitest.config.ts eslint.config.js prettier.config.js
git diff --cached
git commit -m "feat: describe the change"
git add stages changes for the next commit; git diff --cached lets you review exactly what is staged. Use a concise commit message describing the change, for example fix: handle NaN graph vertices.
Push and Review
If no remote is configured, add the repository URL first:
git remote add origin https://github.com/<owner>/dslearn.git
git remote -v
Skip remote add when origin already exists; use git remote -v to inspect it.
git push -u origin feat/describe-change
The -u option sets the upstream branch so later pushes can use git push. Everyone except Owaix must open a pull request against master and include the behavior change, test results, and any compatibility impact. Owaix may push directly but should prefer a PR when collaboration is useful.
Release Tags
After a release has been reviewed and published, tag the release commit and push the tag:
git tag -a v1.0.0 -m "Release dslearn 1.0.0"
git push origin v1.0.0
Tags mark Git history; they do not publish an npm package. npm publish is a separate external action documented in the npm publishing guide.
Useful Recovery Commands
git diff # inspect unstaged edits
git diff --cached # inspect staged edits
git restore --staged . # unstage changes without deleting edits
git switch main # switch branches when the worktree permits it
Avoid git reset --hard and forced pushes unless the consequences are understood and the team explicitly approves them. Never discard another contributor's work to make a branch appear clean.
npm Publishing Guide
This guide prepares and publishes dslearn. The package is authored by imowaix; publishing requires an npm account that owns or has publish access to the package name. No publish command is run as part of normal validation.
Before Publishing
Confirm that dslearn is available or that your npm account has access:
npm view dslearn version
A 404 Not Found means no public package currently resolves under that name. Availability can change before publication.
Use Node.js 20 or newer for the development scripts. The published library itself supports Node.js 18 or newer.
Install the locked dependencies and run all release checks:
npm ci
npm run validate
npm pack --dry-run
The prepublishOnly lifecycle also runs npm run validate before a publish.
Review the tarball contents. It should contain compiled files in dist/, package metadata, README, license, changelog, and the guides under docs/; it should not contain tests, development dependencies, or credentials.
Check the version in package.json. npm does not allow publishing a version that is already present. Update it before release and commit the version change through your usual Git review process.
Authenticate and Publish
Authenticate interactively so credentials stay in npm's own prompt:
npm login
npm whoami
When the account, name, version, and packed contents are confirmed, publish the public package:
npm publish --access public
This command publishes externally. Run it only when the release is approved. For a scoped public package, --access public is required; it is also safe to include for this unscoped package.
Verify the published version and package metadata afterward:
npm view dslearn version
npm view dslearn dist.tarball
Release Notes
- The package exports native ESM from
dist/index.js and declarations from dist/index.d.ts.
files in package.json controls the package allowlist.
npm pack --dry-run previews the archive; npm pack creates a local archive but does not publish it.
- Never commit npm tokens or place them in the repository. Use npm's login flow and account-level two-factor authentication.
- If publication fails because the name is claimed or access is denied, do not change the package identity silently. Resolve ownership or choose a scoped name, then update imports and metadata consistently.
Changelog
1.0.2
- Link the documentation website from the README and npm homepage.
1.0.1
- Clarify the package introduction for beginners and link core data structure operations to hands-on use.
1.0.0
- Initial release of the dslearn data structure collection.
MIT License
Copyright (c) 2026 imowaix
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
BEHIND THE PACKAGE
imowaix
Creator of dslearn
Maintained by imowaix, known as Owaix on GitHub. dslearn makes data structures
easier to explore through code. Start small, try a method, and connect the pieces
into something useful.
Contact me
Questions, ideas, or a bug to share? Get in touch through GitHub.