Compare commits

...

27 Commits

Author SHA1 Message Date
a4b8468cba Disallow search crawling
All checks were successful
Deploy to birb co. production / deploy (push) Successful in 6s
2026-02-25 16:58:40 -08:00
2cb09552c2 Also group assignments in the same way as notes
All checks were successful
Deploy to birb co. production / deploy (push) Successful in 6s
2026-02-25 16:50:15 -08:00
49c7132863 Add queues and homework 2026-02-25 16:46:21 -08:00
0b2a2e5cd0 Map to local port 8080 for the file server
All checks were successful
Deploy to birb co. production / deploy (push) Successful in 7s
2026-02-25 16:45:09 -08:00
427e0bf72a Add Caddyfile and public hosting support
Some checks failed
Deploy to birb co. production / deploy (push) Failing after 10s
2026-02-25 16:09:55 -08:00
9f3449353b Add a script for generating a static site 2026-02-25 15:39:23 -08:00
d54543b160 Remove manually zipped lesson
Some checks failed
Deploy to Homelab / deploy (push) Failing after 8s
2026-02-25 15:15:48 -08:00
b939e70084 Add CLAUDE.md 2026-02-25 15:15:41 -08:00
a45e1e9585 Add linked list solution 2026-02-25 15:15:35 -08:00
a94ec30f08 Remove Docker and Tailscale configurations 2026-02-25 15:13:24 -08:00
73cfc7cf13 revert 8170c42bab
All checks were successful
Deploy to Homelab / deploy (push) Successful in 8s
revert Update funnel configuration
2026-02-18 21:15:16 +00:00
8170c42bab Update funnel configuration
All checks were successful
Deploy to Homelab / deploy (push) Successful in 14s
2026-02-18 09:11:00 -08:00
fcb3f1480b Add submission email
All checks were successful
Deploy to Homelab / deploy (push) Successful in 5s
2026-02-11 16:05:32 -08:00
862f236886 Add notes for 2026.02.11
All checks were successful
Deploy to Homelab / deploy (push) Successful in 6s
2026-02-11 16:00:00 -08:00
4f452e8634 Remove .zip files for lessons with only one file
All checks were successful
Deploy to Homelab / deploy (push) Successful in 6s
2026-02-11 15:24:36 -08:00
f02ac3a88f Update the example again
All checks were successful
Deploy to Homelab / deploy (push) Successful in 6s
2026-02-11 15:16:01 -08:00
8f9225ec62 Update full linked list example
All checks were successful
Deploy to Homelab / deploy (push) Successful in 7s
2026-02-11 15:11:14 -08:00
711e5aa626 Replace Markdown with PDF
All checks were successful
Deploy to Homelab / deploy (push) Successful in 6s
2026-02-11 12:53:02 -08:00
f617fd33ec Add YouTube playlists assignment
All checks were successful
Deploy to Homelab / deploy (push) Successful in 6s
2026-02-11 20:51:37 +00:00
06b916640e Delete redundant README
All checks were successful
Deploy to Homelab / deploy (push) Successful in 6s
2026-02-11 20:50:18 +00:00
b07d965296 Add .zip files
All checks were successful
Deploy to Homelab / deploy (push) Successful in 6s
2026-02-11 12:49:37 -08:00
84c2526f67 Update shared/README.md
All checks were successful
Deploy to Homelab / deploy (push) Successful in 6s
2026-02-11 20:48:47 +00:00
593c2c4775 Update config/tailscale-serve-config.json
All checks were successful
Deploy to Homelab / deploy (push) Successful in 8s
2026-02-11 20:46:43 +00:00
43c6605f7d Change directory name
All checks were successful
Deploy to Homelab / deploy (push) Successful in 16s
2026-02-09 23:05:09 +00:00
886c4ba63b Change the name of the container and host
All checks were successful
Deploy to Homelab / deploy (push) Successful in 6s
2026-02-08 17:08:12 -08:00
84caa4a8e2 Merge branch 'main' of https://gitea.tail8f43b.ts.net/brendan/homelab-dsa-tutoring 2026-02-08 17:07:22 -08:00
9d20401d30 Update paths and actually add files 2026-02-08 17:06:29 -08:00
20 changed files with 816 additions and 50 deletions

View File

@@ -1,4 +1,4 @@
name: "Deploy to Homelab" name: "Deploy to birb co. production"
on: on:
push: push:
@@ -23,23 +23,21 @@ jobs:
- name: Remove directory from server - name: Remove directory from server
run: | run: |
ssh -i ~/.ssh/id_ed25519 github@${{ vars.HOST }} << 'EOF' ssh -i ~/.ssh/id_ed25519 ${{ vars.USERNAME }}@${{ vars.HOST }} << 'EOF'
rm -rf ~/homelab-static rm -rf ~/homelab-dsa-tutoring
EOF EOF
# Avoid needing to set up SSH access to GitHub for this user # Avoid needing to set up SSH access to GitHub for this user
- name: Transfer repository files to server - name: Transfer repository files to server
run: | run: |
scp -i ~/.ssh/id_ed25519 -r ./* github@${{ vars.HOST }}:~/homelab-static scp -i ~/.ssh/id_ed25519 -r ./* ${{ vars.USERNAME }}@${{ vars.HOST }}:~/homelab-dsa-tutoring
- name: Deploy on server with Docker - name: Deploy on server with Docker
run: | run: |
ssh -i ~/.ssh/id_ed25519 github@${{ vars.HOST }} << 'EOF' ssh -i ~/.ssh/id_ed25519 ${{ vars.USERNAME }}@${{ vars.HOST }} << 'EOF'
cd ~/homelab-static cd ~/homelab-dsa-tutoring
export TS_AUTHKEY=${{ secrets.TS_CONTAINER_AUTHKEY }} export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
docker compose -f docker-compose.yml down docker compose -f docker-compose.yml down
docker compose -f docker-compose.yml up -d --build docker compose -f docker-compose.yml up -d --build
EOF EOF

179
.gitignore vendored Normal file
View File

@@ -0,0 +1,179 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
.DS_Store
# Generated build output
public/

32
CLAUDE.md Normal file
View File

@@ -0,0 +1,32 @@
# homelab-dsa-tutoring
## Overview
This is a collection of Python projects for a tutoring side-gig focused on Data Structures and Algorithms (DSA).
It was previously deployed to a personal homelab server as a Tailscale file server (see `.github/workflows/deploy-homelab.yml`). The workflow SCP'd the repo to the server and ran a Docker Compose stack that served the files.
## Structure
```
shared/
├── assignments/ # PDFs and other assignment materials
└── notes-and-examples/ # Lesson files organized by date (YYYY.MM.DD)
```
Each lesson folder under `notes-and-examples/` contains one or more Python files used during the tutoring session.
## Lessons
| Date | Topics |
|------|--------|
| 2026.01.21 | Sorting algorithms (bubble sort, insertion sort) |
| 2026.01.28 | Object references, shallow/deep copies, linked lists |
| 2026.02.04 | Queue data structure |
| 2026.02.11 | Stack data structure |
## Notes
- No external dependencies — pure Python, no `requirements.txt` needed.
- Student homework submissions go to `me@bchen.dev`.
- Lesson files often include skeleton classes with method stubs for students to implement.

13
Caddyfile Normal file
View File

@@ -0,0 +1,13 @@
# DSA Tutoring microsite
#
# For local use, serves on port 8080
# Replace :80 with your domain (e.g. dsa-tutoring.example.com) for
# automatic HTTPS via Let's Encrypt.
#
# Run `python build.py` first to generate the public/ directory.
:8080 {
root * /srv/public
encode gzip
file_server
}

287
build.py Normal file
View File

@@ -0,0 +1,287 @@
#!/usr/bin/env python3
"""Build script: scans shared/, generates zip downloads and public/index.html."""
import html
import os
import shutil
import zipfile
from dataclasses import dataclass, field
from pathlib import Path
SHARED_DIR = Path("shared")
PUBLIC_DIR = Path("public")
ZIPS_DIR = PUBLIC_DIR / "zips"
EXCLUDE_NAMES = {".DS_Store", "__pycache__", ".git", "Thumbs.db"}
EXCLUDE_SUFFIXES = {".pyc"}
@dataclass
class SubSection:
name: str
zip_url: str
files: list[str]
@dataclass
class Section:
title: str
subsections: list[SubSection] = field(default_factory=list)
zip_url: str | None = None
files: list[str] | None = None
# --- File filtering ---
def should_include(path: Path) -> bool:
return path.name not in EXCLUDE_NAMES and path.suffix not in EXCLUDE_SUFFIXES
# --- Zip and file listing ---
def create_zip(source_dir: Path, zip_path: Path) -> None:
"""Zip source_dir into zip_path; contents extract under a named folder."""
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
for file_path in sorted(source_dir.rglob("*")):
if not file_path.is_file():
continue
relative = file_path.relative_to(source_dir.parent)
if any(part in EXCLUDE_NAMES for part in relative.parts):
continue
if not should_include(file_path):
continue
zf.write(file_path, arcname=relative)
def list_files(source_dir: Path) -> list[str]:
"""Return sorted list of filtered relative file paths for display."""
results = []
for file_path in sorted(source_dir.rglob("*")):
if not file_path.is_file():
continue
relative = file_path.relative_to(source_dir)
if any(part in EXCLUDE_NAMES for part in relative.parts):
continue
if not should_include(file_path):
continue
results.append(str(relative))
return results
# --- Data model construction ---
def scan_shared() -> list[Section]:
sections = []
assignments_dir = SHARED_DIR / "assignments"
if assignments_dir.exists():
subdirs = sorted(
d for d in assignments_dir.iterdir()
if d.is_dir() and should_include(d)
)
if subdirs:
subsections = []
for subdir in subdirs:
zip_name = f"assignments-{subdir.name}.zip"
create_zip(subdir, ZIPS_DIR / zip_name)
subsections.append(SubSection(
name=subdir.name,
zip_url=f"zips/{zip_name}",
files=list_files(subdir),
))
sections.append(Section(title="Assignments", subsections=subsections))
else:
zip_name = "assignments.zip"
create_zip(assignments_dir, ZIPS_DIR / zip_name)
sections.append(Section(
title="Assignments",
zip_url=f"zips/{zip_name}",
files=list_files(assignments_dir),
))
notes_dir = SHARED_DIR / "notes-and-examples"
if notes_dir.exists():
date_dirs = sorted(
d for d in notes_dir.iterdir()
if d.is_dir() and should_include(d)
)
subsections = []
for date_dir in date_dirs:
zip_name = f"notes-and-examples-{date_dir.name}.zip"
create_zip(date_dir, ZIPS_DIR / zip_name)
subsections.append(SubSection(
name=date_dir.name,
zip_url=f"zips/{zip_name}",
files=list_files(date_dir),
))
sections.append(Section(title="Notes and Examples", subsections=subsections))
return sections
# --- HTML rendering ---
HTML_TEMPLATE = """\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DSA Tutoring</title>
<style>
*, *::before, *::after {{ box-sizing: border-box; }}
body {{
font-family: system-ui, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 2rem 1rem;
color: #1a1a1a;
background: #f5f5f5;
}}
h1 {{ font-size: 1.75rem; margin-bottom: 0.25rem; }}
.subtitle {{ color: #666; margin-bottom: 2rem; font-size: 0.95rem; }}
.section {{
background: white;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
}}
.section > h2 {{
margin: 0 0 1rem 0;
font-size: 1.2rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid #eee;
}}
.section-flat {{
display: flex;
flex-direction: column;
gap: 0.75rem;
}}
.subsection {{
padding-top: 0.75rem;
border-top: 1px solid #f0f0f0;
}}
.subsection:first-child {{ border-top: none; padding-top: 0; }}
.subsection-header {{
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.5rem;
}}
.subsection-header h3 {{
margin: 0;
font-size: 0.95rem;
font-family: monospace;
color: #333;
}}
.download-btn {{
display: inline-block;
padding: 0.35rem 0.85rem;
background: #2563eb;
color: white;
text-decoration: none;
border-radius: 5px;
font-size: 0.8rem;
white-space: nowrap;
}}
.download-btn:hover {{ background: #1d4ed8; }}
.file-list {{
list-style: none;
padding: 0;
margin: 0;
}}
.file-list li {{
font-family: monospace;
font-size: 0.82rem;
color: #555;
padding: 0.15rem 0;
}}
.file-list li::before {{ content: ""; color: #bbb; }}
</style>
</head>
<body>
<h1>DSA Tutoring</h1>
<p class="subtitle">Lesson files and assignments. Click a download button to get a .zip archive.</p>
{body}
</body>
</html>"""
def render_file_list(files: list[str]) -> str:
items = "".join(f" <li>{html.escape(f)}</li>\n" for f in files)
return f" <ul class=\"file-list\">\n{items} </ul>"
def render_download_btn(zip_url: str, label: str) -> str:
return (
f"<a class=\"download-btn\" href=\"{html.escape(zip_url)}\" download>"
f"Download {html.escape(label)} (.zip)</a>"
)
def render_section(section: Section) -> str:
if section.subsections:
parts = []
for sub in section.subsections:
parts.append(
f" <div class=\"subsection\">\n"
f" <div class=\"subsection-header\">\n"
f" <h3>{html.escape(sub.name)}</h3>\n"
f" {render_download_btn(sub.zip_url, sub.name)}\n"
f" </div>\n"
f"{render_file_list(sub.files)}\n"
f" </div>"
)
inner = "\n".join(parts)
else:
inner = (
f" <div class=\"section-flat\">\n"
f" {render_download_btn(section.zip_url, section.title)}\n"
f"{render_file_list(section.files or [])}\n"
f" </div>"
)
return (
f" <section class=\"section\">\n"
f" <h2>{html.escape(section.title)}</h2>\n"
f"{inner}\n"
f" </section>"
)
def render_html(sections: list[Section]) -> str:
body = "\n".join(render_section(s) for s in sections)
return HTML_TEMPLATE.format(body=body)
# --- Entry point ---
def main() -> None:
os.chdir(Path(__file__).parent)
if PUBLIC_DIR.exists():
shutil.rmtree(PUBLIC_DIR)
PUBLIC_DIR.mkdir()
ZIPS_DIR.mkdir()
sections = scan_shared()
html_content = render_html(sections)
output = PUBLIC_DIR / "index.html"
output.write_text(html_content, encoding="utf-8")
print(f"Built {output}")
robots = PUBLIC_DIR / "robots.txt"
robots.write_text("User-agent: *\nDisallow: /\n", encoding="utf-8")
print(f"Built {robots}")
print(f"Zips in {ZIPS_DIR}:")
for z in sorted(ZIPS_DIR.iterdir()):
print(f" {z.name}")
if __name__ == "__main__":
main()

View File

@@ -1,20 +0,0 @@
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"static.tail8f43b.ts.net:443": {
"Handlers": {
"/": {
"Path": "/shared"
}
}
}
},
"AllowFunnel": {
"static.tail8f43b.ts.net:443": true
}
}

View File

@@ -1,22 +1,24 @@
services: services:
tailscale: builder:
image: tailscale/tailscale:stable image: python:3-alpine
container_name: tailscale-static command: python /app/build.py
hostname: static
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/tailscale-serve-config.json
volumes: volumes:
- tailscale-static-state:/var/lib/tailscale - .:/app
- /dev/net/tun:/dev/net/tun # shared interface across all Tailscale instances
- ./config:/config caddy:
- ./shared:/shared image: caddy:2-alpine
cap_add:
- NET_ADMIN
- SYS_MODULE
restart: unless-stopped restart: unless-stopped
depends_on:
builder:
condition: service_completed_successfully
ports:
- "8080:8080"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./public:/srv/public:ro
- caddy_data:/data
- caddy_config:/config
volumes: volumes:
tailscale-static-state: caddy_data:
caddy_config:

View File

@@ -1,4 +1,3 @@
# Shared directory # Data structures tutoring
Files here are accessible to Tailscale under the `/shared` directory in the Docker container. To make a directory accessible to the public internet, configure `config/tailscale-serve-config` with the desired path-to-directory mapping.
Lesson files for data structures tutoring. Each date under `notes-and-examples` has a corresponding .zip file which can be downloaded to your computer.

View File

@@ -1 +0,0 @@
Hello World!

View File

@@ -0,0 +1,25 @@
def bubble_sort_example():
numbers = [5, 24, 18, 95, 45, 502, 1]
for i in range(len(numbers)):
for j in range(i, len(numbers)):
if numbers[i] > numbers[j]:
numbers[j], numbers[i] = numbers[i], numbers[j]
print(numbers)
def insertion_sort_example():
numbers = [5, 24, 18, 95, 502, 45, 1]
for i in range(1, len(numbers)):
for j in range(i - 1, -1, -1):
if numbers[j] > numbers[j + 1]:
numbers[j + 1], numbers[j] = numbers[j], numbers[j + 1]
else:
break
print(numbers)
# bubble_sort_example();
insertion_sort_example();

View File

@@ -0,0 +1,33 @@
class Node:
next_node = None
def __init__(self, value):
self.value = value
# Homework:
# Make a linked list class with the functions:
# - append(node): add a node to the end of the list
# - insert(node, index): add a node to the middle of the list
# - remove(index): remove the first matching node from the list
class LinkedList:
head = None
tail = None
def append(self, thing):
if self.head==None:
self.head=Node(value=thing)
self.tail=self.head
return
self.tail.next_node=Node(value=thing)
self.tail=self.tail.next_node
list1=LinkedList()
list1.append('hi')
list1.append('hi again')
list1.append('why am I saying hi so many times?')
print(list1.head.value)
print(list1.head.next_node.value)
print(list1.head.next_node.next_node.value)

View File

@@ -0,0 +1,51 @@
some_numbers = [2, 5, 20, 18, 4]
more_numbers = some_numbers.copy()
some_numbers[2] = 100
# print(more_numbers[2]) # prints 100?
class Sample:
other_samples = []
value = 0
samples = [Sample(), Sample(), Sample()]
more_samples = samples.copy() # shallow copy
samples[1].value = 10
samples[1].other_samples.append(samples[0])
# print(more_samples[1].value) # 10
samples.append(Sample())
samples[3].value = 100
# print(more_samples[3].value)
other_sample.value = 40
# print(sample.value)
class Dog:
def __init__(self, age, name):
self.age = age
self.name = name
# dog_1 = Dog()
class Node:
next_node = None
def __init__(self, value):
self.value = value
node_1 = Node(0)
node_2 = Node(0)
node_2.value = 10
node_1.next_node = node_2
node_2.value = 20
current_node = node_1
while (current_node != None):
print(current_node.value)
current_node = current_node.next_node

View File

@@ -0,0 +1,8 @@
class Sample:
other_samples = []
value = 0
sample = Sample()
other_sample = sample
sample.value = 20
print(other_sample.value)

View File

@@ -0,0 +1,13 @@
class Node:
next = None
previous = None
class Queue:
front = None
tail = None
def enqueue(self, node: Node):
pass
def dequeue(self):
pass

View File

@@ -0,0 +1,23 @@
class Node:
prev_node = None
def __init__(self, value) -> None:
self.value = value
class Stack:
top: None | Node = None
def add(self, value):
pass
def remove(self) -> None | Node:
pass
def peek(self) -> None | Node:
pass
# Homework:
# - Implement the other methods on the stack
# - Implement linked list methods (see homework from 2026.01.28)
#
# Submit to me by creating a .zip file and sending to: me@bchen.dev

View File

@@ -0,0 +1,2 @@
# Homework:
# See youtube-playlists under Assignments

View File

@@ -0,0 +1,71 @@
# Solution to 2026.01.28 homework
class Node:
prev_node: "Node | None" = None
next_node: "Node | None" = None
def __init__(self, value) -> None:
self.value = value
class LinkedList:
head: "Node | None" = None
tail: "Node | None" = None
def append(self, thing):
if self.head==None:
self.head=Node(value=thing)
self.tail=self.head
return
if self.tail is not None:
self.tail.next_node=Node(value=thing)
if self.tail.next_node is not None:
self.tail.next_node.prev_node = self.tail
self.tail=self.tail.next_node
def prepend(self, thing):
previous_head = self.head
self.head = Node(value=thing)
self.head.next_node = previous_head
if previous_head is not None:
previous_head.prev_node = self.head
def insert(self, thing, index):
current_node = self.head
if current_node is None:
self.append(thing)
return
if index == 0:
self.prepend(thing)
return
for _ in range(1, index):
if current_node is not None:
current_node = current_node.next_node
if current_node is None:
raise IndexError
prev_next = current_node.next_node
current_node.next_node = Node(thing)
current_node.next_node.prev_node = current_node
current_node.next_node.next_node = prev_next
def print_list(self):
current_node = self.head
while current_node is not None:
print(current_node.value)
current_node = current_node.next_node
example_list = LinkedList()
example_list.append("first")
example_list.append("second")
example_list.append("fourth")
example_list.insert("third", 2)
example_list.insert("zero", 0)
example_list.insert("last", 5)
example_list.print_list()

View File

@@ -0,0 +1,51 @@
class Node:
next_node: "None | Node" = None
prev_node: "None | Node" = None
def __init__(self, value):
self.value = value
class Queue:
front: "Node | None" = None
back: "Node | None" = None
def append(self, value):
if self.back is None:
self.front = Node(value)
self.back = self.front
else:
new_node = Node(value)
self.back.prev_node = new_node
new_node.next_node = self.back
self.back = new_node
def popleft(self):
if self.front is not None:
prev_front = self.front
prev_node_in_queue = self.front.prev_node
self.front.next_node = None
self.front.prev_node = None
if prev_node_in_queue is not None:
prev_node_in_queue.next_node = None
self.front = prev_node_in_queue
return prev_front
else:
return None
def print_queue(self):
current_node = self.front
while current_node is not None:
print(current_node.value)
current_node = current_node.prev_node
queue = Queue()
queue.append("first")
queue.append("second")
queue.append("third")
queue.popleft()
queue.popleft()
queue.print_queue()