diff --git a/.gitignore b/.gitignore index 9fb14505..d88df1db 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .next **/node_modules coding-challenges/yarn.lock +**/__pycache__ diff --git a/profile-specific-challenges/backend/custom_rest_framework/Pipfile b/profile-specific-challenges/backend/custom_rest_framework/Pipfile new file mode 100644 index 00000000..878700a6 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +django = "*" +djangorestframework = "*" + +[dev-packages] + +[requires] +python_version = "3.12" diff --git a/profile-specific-challenges/backend/custom_rest_framework/Pipfile.lock b/profile-specific-challenges/backend/custom_rest_framework/Pipfile.lock new file mode 100644 index 00000000..c84b7ea5 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/Pipfile.lock @@ -0,0 +1,55 @@ +{ + "_meta": { + "hash": { + "sha256": "709498677962b597deb7755151c892205b5602857f054bab7de772221152085a" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "asgiref": { + "hashes": [ + "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", + "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590" + ], + "markers": "python_version >= '3.8'", + "version": "==3.8.1" + }, + "django": { + "hashes": [ + "sha256:236e023f021f5ce7dee5779de7b286565fdea5f4ab86bae5338e3f7b69896cf0", + "sha256:de450c09e91879fa5a307f696e57c851955c910a438a35e6b4c895e86bedc82a" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==5.1.4" + }, + "djangorestframework": { + "hashes": [ + "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20", + "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.15.2" + }, + "sqlparse": { + "hashes": [ + "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", + "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca" + ], + "markers": "python_version >= '3.8'", + "version": "==0.5.3" + } + }, + "develop": {} +} diff --git a/profile-specific-challenges/backend/custom_rest_framework/challenge.md b/profile-specific-challenges/backend/custom_rest_framework/challenge.md new file mode 100644 index 00000000..20e22677 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/challenge.md @@ -0,0 +1,29 @@ +# Custom Model Serializer + +Your task is to implement a custom version of the `ModelSerializer` class, originally provided by the rest_framework library, with the following features: + +1. The serializer should automatically define its fields based on the `fields` parameter in the `Meta` class. +2. Implement support for the following field types: `CharField`, `IntegerField`, `BooleanField`, `FloatField`, `EmailField` and `SlugField` +3. Implement the `read_only` and `write_only` parameters for the fields. +4. Implement proper validations for: `IntegerField`, `BooleanField`, `EmailField`, `SlugField` +5. Implement the following properties and methods: + - .data - Returns the outgoing primitive representation. + - .is_valid() - Deserializes and validates incoming data. + - .validated_data - Returns the validated incoming data. + - .errors - Returns any errors during validation. + - .save() - Persists the validated data into an object instance. + - .to_representation(instance) - for read operations. + - .to_internal_value(data) - for write operations. + - .create(validated_data) and .update(instance, validated_data) - to support saving instances. + +The app `custom_rest_framework` has already been created with a dummy model named `TestSerializerModel` with the necessary fields. Additionally, a test file has been pre-configured, containing all the necessary test cases. + +You should change the import from the original `restframework` and import your custom one while having the test still working fine. + +For running the tests you should first install `Django` and `restframework` + +``` +$ pipenv install +$ pipenv shell +$ python manage.py test +``` \ No newline at end of file diff --git a/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/__init__.py b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/admin.py b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/apps.py b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/apps.py new file mode 100644 index 00000000..7cb50274 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class RestframeworkConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "custom_rest_framework" diff --git a/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/migrations/0001_initial.py b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/migrations/0001_initial.py new file mode 100644 index 00000000..02d28254 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 5.1.4 on 2025-01-10 12:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="TestSerializerModel", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("text", models.CharField(max_length=50)), + ("number", models.IntegerField()), + ("is_something", models.BooleanField()), + ("email", models.CharField(max_length=256)), + ("slug", models.CharField(max_length=64)), + ("real", models.FloatField()), + ("password", models.CharField(max_length=50)), + ("generated", models.CharField(max_length=256)), + ], + ), + ] diff --git a/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/migrations/__init__.py b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/models.py b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/models.py new file mode 100644 index 00000000..352143a9 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/models.py @@ -0,0 +1,16 @@ +from django.db import models + + +class TestSerializerModel(models.Model): + text = models.CharField(max_length=50) + number = models.IntegerField() + is_something = models.BooleanField() + email = models.CharField(max_length=256) + slug = models.CharField(max_length=64) + real = models.FloatField() + password = models.CharField(max_length=50) + generated = models.CharField(max_length=256) + + def save(self, *args, **kwargs): + self.generated = f"{self.slug}:{str(self.number)}" + super().save() diff --git a/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/tests.py b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/tests.py new file mode 100644 index 00000000..2213fee4 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/tests.py @@ -0,0 +1,176 @@ +import random +from typing import Any + +from django.test import TestCase +from rest_framework.serializers import EmailField, ModelSerializer, SlugField, CharField + +from .models import TestSerializerModel +# from .serializers import EmailField, ModelSerializer, SlugField, CharField + + +# Create your tests here. +class TestModelSerializer(ModelSerializer): + email = EmailField() + slug = SlugField() + password = CharField(write_only=True) + + class Meta: + model = TestSerializerModel + fields = [ + "text", + "number", + "is_something", + "email", + "slug", + "real", + "password", + "generated", + ] + read_only_fields = ["generated"] + + +class TestSerializer(TestCase): + def create_random_data(self) -> dict: + """Creates random data for test purposes.""" + return dict( + text=str(random.randint(1000, 2000)), + number=random.randint(0, 1000), + is_something=random.choices([True, False])[0], + slug=str(random.randint(1000, 2000)), + email=f"{str(random.randint(10000, 20000))}@{random.randint(1000, 2000)}.com", + real=random.randint(1000, 9999) / 100, + password=str(random.randint(100000, 999999)), + ) + + def assertInstance(self, instance: Any, data: dict, exclude: list = list()): + """Assert that all the values in the dictionary exists as a property in the + instance.""" + if data and instance: + for k, v in data.items(): + if k not in exclude: + assert hasattr(instance, k), ( + f"Property {k} should exist in instance" + ) + assert getattr(instance, k) == v, ( + f"Property value '{v}' should be equals to instance value" + ) + + def assertEqualDictionary(self, left: dict, right: dict, exclude: list = list()): + """Assert that all the values in the right should exists in the left.""" + if right is not None and left is not None: + for k, v in right.items(): + if k not in exclude: + assert left.get(k) is not None, ( + f"Property {k} should be in left dictionary" + ) + assert left.get(k) == v, ( + f"Property value '{left.get(k)}' should be equals to right value {v}" + ) + + for k, v in left.items(): + if k not in exclude: + assert right.get(k) is not None, ( + f"Property {k} should be in right dictionary" + ) + assert right.get(k) == v, ( + f"Property value '{left.get(k)}' should be equals to right value {v}" + ) + + def test_1_read(self): + """We create a model on DB and then check the serialized data is correct.""" + data = self.create_random_data() + instance = TestSerializerModel.objects.create(**data) + serializer = TestModelSerializer(instance) + self.assertIsNotNone(serializer.data, "Serializer should return some data") + self.assertInstance(instance, serializer.data) + self.assertEqualDictionary(serializer.data, data, ["password", "generated"]) + + def test_2_create(self): + """We create a model through the serializer and check that it was created + correctly in DB.""" + data = self.create_random_data() + serializer = TestModelSerializer(data=data) + self.assertTrue(serializer.is_valid()) + serializer.save() + instance = TestSerializerModel.objects.last() + self.assertInstance(instance, data) + + def test_3_update(self): + """We update a model through the serializer and check that the values were + correctly updated in DB.""" + data = self.create_random_data() + instance = TestSerializerModel.objects.create(**data) + previous_count = TestSerializerModel.objects.count() + new_data = self.create_random_data() + serializer = TestModelSerializer(instance, data=new_data) + self.assertTrue(serializer.is_valid()) + serializer.save() + self.assertEqual(previous_count, TestSerializerModel.objects.count()) + new_instance = TestSerializerModel.objects.get(id=instance.id) + self.assertInstance(new_instance, new_data) + + def test_4_error_int(self): + """We force an error on creation on the number field.""" + data = self.create_random_data() + data["number"] = "15.5" + serializer = TestModelSerializer(data=data) + self.assertFalse(serializer.is_valid()) + self.assertIsNotNone(serializer.errors) + self.assertEqual(len(serializer.errors), 1) + self.assertTrue("number" in serializer.errors) + + def test_5_error_bool(self): + """We force an error on creation on the bool field.""" + data = self.create_random_data() + data["is_something"] = "jojo" + serializer = TestModelSerializer(data=data) + self.assertFalse(serializer.is_valid()) + self.assertIsNotNone(serializer.errors) + self.assertEqual(len(serializer.errors), 1) + self.assertTrue("is_something" in serializer.errors) + + def test_6_error_slug(self): + """We force an error on creation on the slug field.""" + data = self.create_random_data() + data["slug"] = "not_an_slug./$" + serializer = TestModelSerializer(data=data) + self.assertFalse(serializer.is_valid()) + self.assertIsNotNone(serializer.errors) + self.assertEqual(len(serializer.errors), 1) + self.assertTrue("slug" in serializer.errors) + + def test_7_error_email(self): + """We force an error on creation on the email field.""" + data = self.create_random_data() + data["email"] = "not_an_email" + serializer = TestModelSerializer(data=data) + self.assertFalse(serializer.is_valid()) + self.assertIsNotNone(serializer.errors) + self.assertEqual(len(serializer.errors), 1) + self.assertTrue("email" in serializer.errors) + + def test_8_write_only(self): + """We create a model on DB and then check the serialized data without the + write only field.""" + data = self.create_random_data() + instance = TestSerializerModel.objects.create(**data) + serializer = TestModelSerializer(instance) + serialized_data = serializer.data + self.assertIsNotNone(serialized_data) + self.assertInstance(instance, serialized_data) + self.assertFalse("password" in serialized_data) + self.assertEqualDictionary(serializer.data, data, ["password", "generated"]) + + def test_9_read_only(self): + """We create a model through the serializer and check that it was created + correctly skipping the read only field.""" + data = self.create_random_data() + data["generated"] = "fixed" # we write a wrong value in a generated field + serializer = TestModelSerializer(data=data) + self.assertTrue(serializer.is_valid()) + serializer.save() + instance = TestSerializerModel.objects.last() + self.assertInstance(instance, data, ["generated"]) + self.assertNotEqual(instance.generated, data["generated"]) + self.assertEqual(instance.generated, f"{data['slug']}:{str(data['number'])}") + self.assertEqualDictionary(serializer.data, data, ["password", "generated"]) diff --git a/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/views.py b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/custom_rest_framework/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/profile-specific-challenges/backend/custom_rest_framework/manage.py b/profile-specific-challenges/backend/custom_rest_framework/manage.py new file mode 100755 index 00000000..9856e029 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/profile-specific-challenges/backend/custom_rest_framework/server/__init__.py b/profile-specific-challenges/backend/custom_rest_framework/server/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/profile-specific-challenges/backend/custom_rest_framework/server/asgi.py b/profile-specific-challenges/backend/custom_rest_framework/server/asgi.py new file mode 100644 index 00000000..bb306865 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/server/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for server project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings") + +application = get_asgi_application() diff --git a/profile-specific-challenges/backend/custom_rest_framework/server/settings.py b/profile-specific-challenges/backend/custom_rest_framework/server/settings.py new file mode 100644 index 00000000..e0254934 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/server/settings.py @@ -0,0 +1,127 @@ +""" +Django settings for server project. + +Generated by 'django-admin startproject' using Django 5.1.4. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-^jwo-=8d0i)7v41o0tpp&#%hk01gd&w*9pfnt11j@9-9&=k@%v" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + # Libs + "rest_framework", + # Own + "custom_rest_framework", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "server.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "server.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.1/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ + +STATIC_URL = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/profile-specific-challenges/backend/custom_rest_framework/server/urls.py b/profile-specific-challenges/backend/custom_rest_framework/server/urls.py new file mode 100644 index 00000000..4aa6dfa0 --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/server/urls.py @@ -0,0 +1,23 @@ +""" +URL configuration for server project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" + +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path("admin/", admin.site.urls), +] diff --git a/profile-specific-challenges/backend/custom_rest_framework/server/wsgi.py b/profile-specific-challenges/backend/custom_rest_framework/server/wsgi.py new file mode 100644 index 00000000..6db91fbc --- /dev/null +++ b/profile-specific-challenges/backend/custom_rest_framework/server/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for server project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings") + +application = get_wsgi_application() diff --git a/technical-fundamentals/python/README.md b/technical-fundamentals/python/README.md new file mode 100644 index 00000000..9be4d8c0 --- /dev/null +++ b/technical-fundamentals/python/README.md @@ -0,0 +1,42 @@ +# Coding Challenges — Python + +Python version of the Cracking the Coding Interview exercises. + +## Getting Started + +1. Install dependencies: + ```bash + python3 -m venv venv + source venv/bin/activate + pip install -r requirements.txt + ``` + +2. Run all tests: + ```bash + pytest + ``` + +## Running Tests by Category + +```bash +pytest coding/problems/tests/strings/ +pytest coding/problems/tests/lists/ +pytest coding/problems/tests/stacks/ +pytest coding/problems/tests/trees/ +pytest coding/problems/tests/recursion/ +``` + +## Running a Single Problem + +```bash +pytest -k is_unique +pytest -k minimal_tree +pytest -k triple_step +``` + +## How It Works + +Each problem lives in `coding/problems/NN_problem_name.py` as a stub function or class. +Fill in the implementation and run the tests to verify your solution. + +Tests import directly from the solution files — same pattern as the JavaScript side. diff --git a/technical-fundamentals/python/__pycache__/conftest.cpython-314-pytest-9.0.3.pyc b/technical-fundamentals/python/__pycache__/conftest.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 00000000..db043736 Binary files /dev/null and b/technical-fundamentals/python/__pycache__/conftest.cpython-314-pytest-9.0.3.pyc differ diff --git a/technical-fundamentals/python/__pycache__/conftest.cpython-314.pyc b/technical-fundamentals/python/__pycache__/conftest.cpython-314.pyc new file mode 100644 index 00000000..9a37c6a5 Binary files /dev/null and b/technical-fundamentals/python/__pycache__/conftest.cpython-314.pyc differ diff --git a/technical-fundamentals/python/coding/__init__.py b/technical-fundamentals/python/coding/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/technical-fundamentals/python/coding/__pycache__/__init__.cpython-314.pyc b/technical-fundamentals/python/coding/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 00000000..f9e85c3f Binary files /dev/null and b/technical-fundamentals/python/coding/__pycache__/__init__.cpython-314.pyc differ diff --git a/technical-fundamentals/python/coding/problems/01_is_unique.py b/technical-fundamentals/python/coding/problems/01_is_unique.py new file mode 100644 index 00000000..9be90514 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/01_is_unique.py @@ -0,0 +1,12 @@ +# 1. Is Unique: +# Implement an algorithm to determine if a string has all unique characters. +# What if you cannot use additional data structures? + + +def is_unique(s: str) -> bool: + hashmap = {} + for c in s: + if c in hashmap: + return False + hashmap[c] = True + return True diff --git a/technical-fundamentals/python/coding/problems/02_check_permutations.py b/technical-fundamentals/python/coding/problems/02_check_permutations.py new file mode 100644 index 00000000..c5bff379 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/02_check_permutations.py @@ -0,0 +1,18 @@ +# 2. Check Permutation: +# Given two strings, write a method to decide if one is a permutation of the other. + + +def check_permutations(s1: str, s2: str) -> bool: + hashmap = {} + for c in s1: + if c not in hashmap: + hashmap[c] = 0 + hashmap[c] += 1 + + for c in s2: + if c in hashmap: + hashmap[c] -= 1 + else: + return False + + return all([x == 0 for x in hashmap.values()]) \ No newline at end of file diff --git a/technical-fundamentals/python/coding/problems/03_urlify.py b/technical-fundamentals/python/coding/problems/03_urlify.py new file mode 100644 index 00000000..6c84a1e7 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/03_urlify.py @@ -0,0 +1,14 @@ +# 3. URLify: +# Write a method to replace all spaces in a string with '%20'. +# You may assume that the string has sufficient space at the end to hold the additional +# characters, and that you are given the "true" length of the string. + + +def urlify(s: str) -> str: + answer = "" + for c in s: + if c == ' ': + answer += '%20' + else: + answer += c + return answer \ No newline at end of file diff --git a/technical-fundamentals/python/coding/problems/04_palindrome_permutation.py b/technical-fundamentals/python/coding/problems/04_palindrome_permutation.py new file mode 100644 index 00000000..e36b7033 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/04_palindrome_permutation.py @@ -0,0 +1,26 @@ +# 4. Palindrome Permutation: +# Given a string, write a function to check if it is a permutation of a palindrome. +# A palindrome is a word or phrase that is the same forwards and backwards. +# A permutation is a rearrangement of letters. The palindrome does not need to be +# limited to just dictionary words. +# +# EXAMPLE +# Input: Tact Coa +# Output: True (permutations: "taco cat", "atco cta", etc.) + + +def palindrome_permutation(s: str) -> bool: + hashmap = {} + counter = 0 + for c in s: + c = c.lower() + if c == ' ': + continue + if c not in hashmap: + hashmap[c] = True + counter += 1 + else: + del hashmap[c] + counter -= 1 + return counter <= 1 + diff --git a/technical-fundamentals/python/coding/problems/05_one_away.py b/technical-fundamentals/python/coding/problems/05_one_away.py new file mode 100644 index 00000000..648e15b4 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/05_one_away.py @@ -0,0 +1,48 @@ +# 5. One Away: +# There are three types of edits that can be performed on strings: +# insert a character, remove a character, or replace a character. +# Given two strings, write a function to check if they are one edit (or zero edits) away. + + +def is_one_away(s1: str, s2: str) -> bool: + if s1 == s2: + return True + + len_s1 = len(s1) + len_s2 = len(s2) + + if len_s1 == len_s2: + diffs = 0 + for i in range(len_s1): + if s1[i] != s2[i]: + if diffs > 0: + return False + diffs += 1 + return True + + if abs(len_s1 - len_s2) > 1: + return False + + if len_s1 > len_s2: + small = s2 + big = s1 + else: + small = s1 + big = s2 + + diffs = 0 + for i in range(len(small)): + if small[i] != big[i+diffs]: + if diffs > 0: + return False + diffs += 1 + + return True + + + + + + + + return False diff --git a/technical-fundamentals/python/coding/problems/06_string_compression.py b/technical-fundamentals/python/coding/problems/06_string_compression.py new file mode 100644 index 00000000..baee10f0 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/06_string_compression.py @@ -0,0 +1,27 @@ +# 6. String Compression: +# Implement a method to perform basic string compression using the counts of repeated +# characters. For example, the string aabcccccaaa would become a2b1c5a3. +# If the "compressed" string would not become smaller than the original string, +# your method should return the original string. +# You can assume the string has only uppercase and lowercase letters (a-z). + + +def string_compression(s: str) -> str: + if not s: + return s + + len_s = len(s) + c = s[0] + counter = 1 + answer = "" + for i in range(1, len(s)): + if s[i] == c: + counter += 1 + else: + answer += c + str(counter) + c = s[i] + counter = 1 + answer += c + str(counter) + if len(answer) >= len_s: + return s + return answer diff --git a/technical-fundamentals/python/coding/problems/07_rotate_matrix.py b/technical-fundamentals/python/coding/problems/07_rotate_matrix.py new file mode 100644 index 00000000..ea56a8fd --- /dev/null +++ b/technical-fundamentals/python/coding/problems/07_rotate_matrix.py @@ -0,0 +1,27 @@ +# 7. Rotate Matrix: +# Given an image represented by an NxN matrix, where each pixel in the image is 4 +# bytes, write a method to rotate the image by 90 degrees. Can you do this in place? + +from typing import List + +Matrix = List[List[int]] + +# 1, "2", 3, 4 +# 5, 6, 7, "8" +# "9", 10, 11, 12 +# 13, 14, "15", 16 +# +# 0, 1 - 1, 3 - 3, 2 - 2, 0 + +def rotate_matrix(matrix: Matrix) -> None: + n = len(matrix) + for i in range(n//2): + for j in range(i, n - i - 1): + first = matrix[i][j] + second = matrix[j][n-i-1] + third = matrix[n-i-1][n-j-1] + fourth = matrix[n-j-1][i] + matrix[i][j] = fourth + matrix[j][n-i-1] = first + matrix[n-i-1][n-j-1] = second + matrix[n-j-1][i] = third \ No newline at end of file diff --git a/technical-fundamentals/python/coding/problems/08_zero_matrix.py b/technical-fundamentals/python/coding/problems/08_zero_matrix.py new file mode 100644 index 00000000..f61278da --- /dev/null +++ b/technical-fundamentals/python/coding/problems/08_zero_matrix.py @@ -0,0 +1,37 @@ +# 8. Zero Matrix: +# Write an algorithm such that if an element in an MxN matrix is 0, +# its entire row and column are set to 0. + +from typing import List + +Matrix = List[List[int]] + + +def zero_matrix(matrix: Matrix) -> None: + first_row = False + first_column = False + print(matrix) + for i in range(len(matrix)): + for j in range(len(matrix[i])): + if matrix[i][j] == 0: + if i == 0: + first_row = True + if j == 0: + first_column = True + matrix[i][0] = 0 + matrix[0][j] = 0 + + for i in range(1, len(matrix)): + for j in range(1, len(matrix[i])): + if matrix[i][0] == 0: + matrix[i][j] = 0 + if matrix[0][j] == 0: + matrix[i][j] = 0 + + if first_row: + for j in range(len(matrix[0])): + matrix[0][j] = 0 + if first_column: + for i in range(len(matrix)): + matrix[i][0] = 0 + diff --git a/technical-fundamentals/python/coding/problems/09_string_rotation.py b/technical-fundamentals/python/coding/problems/09_string_rotation.py new file mode 100644 index 00000000..fa7a021e --- /dev/null +++ b/technical-fundamentals/python/coding/problems/09_string_rotation.py @@ -0,0 +1,11 @@ +# 9. String Rotation: +# Assume you have a method is_substring which checks if one word is a substring of another. +# Given two strings, s1 and s2, write code to check if s2 is a rotation of s1 using +# only one call to is_substring. +# e.g., "waterbottle" is a rotation of "erbottlewat" + +from coding.problems.utils.strings import is_substring + + +def string_rotation(s1: str, s2: str) -> bool: + return s1 in s2 + s2 diff --git a/technical-fundamentals/python/coding/problems/10_linked_list.py b/technical-fundamentals/python/coding/problems/10_linked_list.py new file mode 100644 index 00000000..36e1ca7a --- /dev/null +++ b/technical-fundamentals/python/coding/problems/10_linked_list.py @@ -0,0 +1,100 @@ +# 10. *Implement a Linked List* +# +# Create the data structure with the corresponding initial functions. + +from __future__ import annotations +from typing import TypeVar, Generic, Optional, Callable + +T = TypeVar("T") + + +class Node(Generic[T]): + def __init__(self, value: T, next: Optional["Node[T]"] = None): + self.value = value + self.next = next + + +class LinkedList(Generic[T]): + def __init__(self, head: Optional[Node[T]] = None): + self.head: Optional[Node[T]] = head + self.tail: Optional[Node[T]] = head + self.length: int = 0 + def counter(index: int, value: T, node: Node[T]): + self.length += 1 + self.visit(counter) + + def visit(self, callback: Callable[[int, T, Node[T]], None]) -> None: + pointer = self.head + visited = {} + index = 0 + while pointer: + if pointer in visited: + return pointer + callback(index, pointer.value, pointer) + visited[pointer] = True + pointer = pointer.next + index += 1 + + def push(self, value: T) -> None: + node = Node(value) + if self.tail: + self.tail.next = node + else: + self.head = node + self.tail = node + self.length += 1 + + def get(self, index: int) -> Node[T] | None: + pointer = self.head + counter = 0 + while pointer: + if counter == index: + return pointer + pointer = pointer.next + counter += 1 + + def remove_by_position(self, index: int) -> None: + pointer = self.head + counter = 0 + while pointer: + if counter + 1 == index: + if pointer.next: + pointer.next = pointer.next.next + break + pointer = pointer.next + + def merge(self, other: "LinkedList[T]") -> None: + if self.tail: + self.tail.next = other.head + self.tail = other.tail + else: + self.head = other.head + self.tail = other.head + self.length += other.length + + def to_array(self) -> list[T]: + answer = [] + def str_node(index: int, value: T, node: Node[T]): + answer.append(value) + self.visit(str_node) + return answer + + def filter(self, predicate: Callable[[T], bool]) -> "LinkedList[T]": + new_list = LinkedList() + if self.head: + pointer = self.head + while pointer: + if not predicate(pointer.value): + new_list.push(Node(pointer.value)) + pointer = pointer.next + return new_list + + def print(self) -> None: + values = [] + if self.head: + pointer = self.head + while pointer: + values.append(str(pointer.value)) + pointer = pointer.next + print(",".join(values)) + diff --git a/technical-fundamentals/python/coding/problems/11_remove_dups.py b/technical-fundamentals/python/coding/problems/11_remove_dups.py new file mode 100644 index 00000000..b2fae624 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/11_remove_dups.py @@ -0,0 +1,42 @@ +# 11. Remove Dups: +# Write code to remove duplicates from an unsorted linked list. +# FOLLOW UP: How would you solve this problem if a temporary buffer is not allowed? +# +# 1 -> 2 -> 2 -> 2 -> 4 + +from __future__ import annotations +from typing import TypeVar, Generic, Optional +from importlib import import_module + +linked_list = import_module("coding.problems.10_linked_list") +LinkedList = linked_list.LinkedList +Node = linked_list.Node +T = linked_list.T + + +def remove_dups(head: Optional[Node[T]] = None) -> Optional[Node[T]]: + lista = LinkedList(head) + + if lista.length == 1: + return head + + new_list = LinkedList() + hashmap = {} + + def add_if_not_duplicate(index: int, value: T, node: Node[T]): + if value not in hashmap: + new_list.push(value) + hashmap[value] = True + + lista.visit(add_if_not_duplicate) + lista.print() + new_list.print() + return new_list.head + + # pointer = head + # while pointer and pointer.next: + # if pointer.value == pointer.next.value: + # pointer.next = pointer.next.next + # else: + # pointer = pointer.next + # return head diff --git a/technical-fundamentals/python/coding/problems/12_kth_to_last.py b/technical-fundamentals/python/coding/problems/12_kth_to_last.py new file mode 100644 index 00000000..a0c5f028 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/12_kth_to_last.py @@ -0,0 +1,37 @@ +# 12. Return Kth to Last: +# Implement an algorithm to find the kth to last element of a singly linked list. + +from __future__ import annotations +from typing import TypeVar, Generic, Optional +from importlib import import_module + +linked_list = import_module("coding.problems.10_linked_list") +LinkedList = linked_list.LinkedList +Node = linked_list.Node +T = linked_list.T + + +def kth_to_last(head: Node[T], k: int) -> Optional[Node[T]]: + linked_list = LinkedList(head) + + return linked_list.get(linked_list.length - k) + + # if k < 1: + # return None + + # length = 0 + # pointer = head + # while pointer: + # length += 1 + # pointer = pointer.next + + # if k > length: + # return None + + # counter = 0 + # pointer = head + # while pointer and counter < length - k: + # counter += 1 + # pointer = pointer.next + + # return pointer diff --git a/technical-fundamentals/python/coding/problems/13_delete_middle_node.py b/technical-fundamentals/python/coding/problems/13_delete_middle_node.py new file mode 100644 index 00000000..ceeed5bb --- /dev/null +++ b/technical-fundamentals/python/coding/problems/13_delete_middle_node.py @@ -0,0 +1,41 @@ +# 13. Delete Middle Node: +# Implement an algorithm to delete a node in the middle +# (i.e., any node but the first and last node, not necessarily the exact middle) +# of a singly linked list, given only access to that node. +# +# EXAMPLE +# Input: the node c from the linked list a->b->c->d->e->f +# Result: nothing is returned, but the new linked list looks like a->b->d->e->f + +from __future__ import annotations +from typing import TypeVar, Generic, Optional +from importlib import import_module + +linked_list = import_module("coding.problems.10_linked_list") +LinkedList = linked_list.LinkedList +Node = linked_list.Node +T = linked_list.T + + +def delete_middle_node(head: Node[T], position: int) -> Optional[Node[T]]: + linked_list = LinkedList(head) + if position == 0 or position == linked_list.length - 1: + return head + + linked_list.remove_by_position(position) + return linked_list.head + # if position == 0: + # return head + + # index = 0 + # previous = None + # pointer = head + # while pointer and index < position: + # previous = pointer + # pointer = pointer.next + # index += 1 + + # if pointer and previous: + # previous.next = pointer.next + + # return head diff --git a/technical-fundamentals/python/coding/problems/14_partition.py b/technical-fundamentals/python/coding/problems/14_partition.py new file mode 100644 index 00000000..3bccef7d --- /dev/null +++ b/technical-fundamentals/python/coding/problems/14_partition.py @@ -0,0 +1,48 @@ +# 14. Partition: +# Write code to partition a linked list around a value x, such that all nodes less +# than x come before all nodes greater than or equal to x. If x is contained within +# the list, the values of x only need to be after the elements less than x. +# The partition element x can appear anywhere in the "right partition". +# +# EXAMPLE +# Input: 3 -> 5 -> 8 -> 5 -> 10 -> 2 -> 1 [partition=5] +# Output: 3 -> 1 -> 2 -> 10 -> 5 -> 5 -> 8 + +from __future__ import annotations +from typing import TypeVar, Generic, Optional +from importlib import import_module + +linked_list = import_module("coding.problems.10_linked_list") +LinkedList = linked_list.LinkedList +Node = linked_list.Node +T = linked_list.T + + +def partition(head: Optional[Node[T]], x: T) -> Optional[Node[T]]: + linked_list = LinkedList(head) + left = LinkedList() + right = LinkedList() + def split(index:int, value: T, node: Node[T]): + if value < x: + left.push(value) + else: + right.push(value) + linked_list.visit(split) + left.merge(right) + return left.head + # left_dummy = Node(x) + # right_dummy = Node(x) + # left_pointer = left_dummy + # right_pointer = right_dummy + # pointer = head + # while pointer: + # if pointer.value < x: + # left_pointer.next = pointer + # left_pointer = left_pointer.next + # else: + # right_pointer.next = pointer + # right_pointer = right_pointer.next + # pointer = pointer.next + # right_pointer.next = None + # left_pointer.next = right_dummy.next + # return left_dummy.next \ No newline at end of file diff --git a/technical-fundamentals/python/coding/problems/15_sum_lists.py b/technical-fundamentals/python/coding/problems/15_sum_lists.py new file mode 100644 index 00000000..3859f35f --- /dev/null +++ b/technical-fundamentals/python/coding/problems/15_sum_lists.py @@ -0,0 +1,55 @@ +# 15. Sum Lists: +# You have two numbers represented by a linked list, where each node contains a +# single digit. The digits are stored in reverse order, such that the 1's digit is +# at the head of the list. Write a function that adds the two numbers and returns +# the sum as a linked list. +# +# EXAMPLE +# Input: (7 -> 1 -> 6) + (5 -> 9 -> 2). That is, 617 + 295. +# Output: 2 -> 1 -> 9. That is, 912. + +from __future__ import annotations +from typing import Optional +from importlib import import_module + +linked_list = import_module("coding.problems.10_linked_list") +LinkedList = linked_list.LinkedList +Node = linked_list.Node +T = linked_list.T + + +def sum_lists( + list1: Optional[Node], + list2: Optional[Node], +) -> Optional[Node]: + + ll1 = "".join(str(x) for x in LinkedList(list1).to_array()) + ll2 = "".join(str(x) for x in LinkedList(list2).to_array()) + suma = int(ll1[::-1]) + int(ll2[::-1]) + linked_list = LinkedList() + for c in str(suma)[::-1]: + linked_list.push(int(c)) + return linked_list.head + + + + + # total = 0 + # carry = 0 + # dummy = Node(0) + # pointer = dummy + # while list1 or list2: + # value1 = list1.value if list1 else 0 + # value2 = list2.value if list2 else 0 + # total = value1 + value2 + carry + # pointer.next = Node(total % 10) + # pointer = pointer.next + # carry = total // 10 + # list1 = list1.next if list1 else None + # list2 = list2.next if list2 else None + + # if carry: + # pointer.next = Node(carry) + + # return dummy.next + diff --git a/technical-fundamentals/python/coding/problems/16_sum_lists_forward_order.py b/technical-fundamentals/python/coding/problems/16_sum_lists_forward_order.py new file mode 100644 index 00000000..075c6ab5 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/16_sum_lists_forward_order.py @@ -0,0 +1,76 @@ +# 16. Sum Lists (Forward Order): +# Suppose the digits are stored in forward order. Repeat the above problem. +# +# EXAMPLE +# Input: (6 -> 1 -> 7) + (2 -> 9 -> 5). That is, 617 + 295. +# Output: 9 -> 1 -> 2. That is, 912. + +from __future__ import annotations +from typing import Optional +from importlib import import_module + +linked_list = import_module("coding.problems.10_linked_list") +LinkedList = linked_list.LinkedList +Node = linked_list.Node +T = linked_list.T + + +def invert_order(node: Optional[Node]) -> Optional[Node]: + pointer = node + previous = None + while pointer: + old = pointer + pointer = pointer.next + old.next = previous + previous = old + return previous + +def sum_lists_forward_order( + list1: Optional[Node], + list2: Optional[Node], +) -> Optional[Node]: + + if not list1 and not list2: + return None + + if not list1: + return list2 + if not list2: + return list1 + + ll1 = "".join(str(x) for x in LinkedList(list1).to_array()) + ll2 = "".join(str(x) for x in LinkedList(list2).to_array()) + suma = int(ll1) + int(ll2) + linked_list = LinkedList() + for c in str(suma): + linked_list.push(int(c)) + return linked_list.head + + + # if not list1 and not list2: + # return None + + # if not list1 or not list2: + # return list1 or list2 + + # list1 = invert_order(list1) + # list2 = invert_order(list2) + # total = 0 + # carry = 0 + # dummy = Node(0) + # pointer = dummy + # while list1 or list2: + # value1 = list1.value if list1 else 0 + # value2 = list2.value if list2 else 0 + # total = value1 + value2 + carry + # pointer.next = Node(total % 10) + # pointer = pointer.next + # carry = total // 10 + # list1 = list1.next if list1 else None + # list2 = list2.next if list2 else None + + # if carry: + # pointer.next = Node(carry) + + + # return invert_order(dummy.next) diff --git a/technical-fundamentals/python/coding/problems/17_palindrome.py b/technical-fundamentals/python/coding/problems/17_palindrome.py new file mode 100644 index 00000000..ecda0e9e --- /dev/null +++ b/technical-fundamentals/python/coding/problems/17_palindrome.py @@ -0,0 +1,25 @@ +# 17. Palindrome: +# Implement a function to check if a linked list is a palindrome. + +from __future__ import annotations +from typing import TypeVar, Generic, Optional +from importlib import import_module + +linked_list = import_module("coding.problems.10_linked_list") +LinkedList = linked_list.LinkedList +Node = linked_list.Node +T = linked_list.T + + +def is_palindrome(head: Optional[Node[T]]) -> bool: + # pointer = head + # normal = [] + # opposite = [] + # while pointer: + # normal.append(pointer.value) + # opposite.insert(0, pointer.value) + # pointer = pointer.next + # return normal == opposite + + text = "".join(str(x) for x in LinkedList(head).to_array()) + return text == text[::-1] diff --git a/technical-fundamentals/python/coding/problems/18_intersection.py b/technical-fundamentals/python/coding/problems/18_intersection.py new file mode 100644 index 00000000..9211890f --- /dev/null +++ b/technical-fundamentals/python/coding/problems/18_intersection.py @@ -0,0 +1,45 @@ +# 18. Intersection: +# Given two (singly) linked lists, determine if the two lists intersect. +# Return the first intersecting node. Note that the intersection is defined +# based on reference, not value. + +from __future__ import annotations +from typing import TypeVar, Generic, Optional +from importlib import import_module + +linked_list = import_module("coding.problems.10_linked_list") +LinkedList = linked_list.LinkedList +Node = linked_list.Node +T = linked_list.T + + +def intersection( + list1: Optional[Node[T]], + list2: Optional[Node[T]], +) -> Optional[Node[T]]: + items = [] + def pick(index: int, value: T, node: Node[T]): + items.append(node) + ll1 = LinkedList(list1) + ll1.visit(pick) + + answer = None + def check(index: int, value: T, node: Node[T]): + nonlocal answer + if node in items and answer is None: + answer = node + + ll2 = LinkedList(list2) + ll2.visit(check) + + return answer + + # p1 = list1 + # while p1: + # p2 = list2 + # while p2: + # if p1 == p2: + # return p1 + # p2 = p2.next + # p1 = p1.next + # return None diff --git a/technical-fundamentals/python/coding/problems/19_loop_detection.py b/technical-fundamentals/python/coding/problems/19_loop_detection.py new file mode 100644 index 00000000..58d6becf --- /dev/null +++ b/technical-fundamentals/python/coding/problems/19_loop_detection.py @@ -0,0 +1,37 @@ +# 19. Loop Detection: +# Given a circular linked list, implement an algorithm that returns the node +# at the beginning of the loop. +# +# DEFINITION +# Circular linked list: A (corrupt) linked list in which a node's next pointer +# points to an earlier node, so as to make a loop in the linked list. +# +# EXAMPLE +# Input: A->B->C->D->E->C [the same C as earlier] +# Output: C + +from __future__ import annotations +from typing import TypeVar, Generic, Optional +from importlib import import_module + +linked_list = import_module("coding.problems.10_linked_list") +LinkedList = linked_list.LinkedList +Node = linked_list.Node +T = linked_list.T + + +def detect_loop(head: Optional[Node[T]]) -> Optional[Node[T]]: + + def visit(index: int, value: T, node: Node[T]): + pass + linked_list = LinkedList(head) + return linked_list.visit(visit) + + # bag = [] + # pointer = head + # while pointer: + # if pointer in bag: + # return pointer + # bag.append(pointer) + # pointer = pointer.next + # return None diff --git a/technical-fundamentals/python/coding/problems/21_three_stacks.py b/technical-fundamentals/python/coding/problems/21_three_stacks.py new file mode 100644 index 00000000..2692efa2 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/21_three_stacks.py @@ -0,0 +1,20 @@ +# 21. Three in One: +# Describe how you could use a single array to implement three stacks. + +from typing import TypeVar, Generic, Optional, List + +T = TypeVar("T") + + +class ThreeStacks(Generic[T]): + def __init__(self, array_length: int): + pass + + def push(self, stack_num: int, value: T) -> None: + pass + + def pop(self, stack_num: int) -> Optional[T]: + pass + + def peek(self, stack_num: int) -> Optional[T]: + pass diff --git a/technical-fundamentals/python/coding/problems/22_stack_min.py b/technical-fundamentals/python/coding/problems/22_stack_min.py new file mode 100644 index 00000000..9d2a9d04 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/22_stack_min.py @@ -0,0 +1,22 @@ +# 22. Stack Min: +# How would you design a stack which, in addition to push and pop, has a function +# min which returns the minimum element? +# Push, pop, and min should all operate in O(1) time. + +from typing import TypeVar, Generic, Optional + +T = TypeVar("T") + + +class StackMin(Generic[T]): + def __init__(self): + pass + + def push(self, value: T) -> None: + pass + + def pop(self) -> Optional[T]: + pass + + def min(self) -> Optional[T]: + pass diff --git a/technical-fundamentals/python/coding/problems/23_stack_of_plates.py b/technical-fundamentals/python/coding/problems/23_stack_of_plates.py new file mode 100644 index 00000000..da20200a --- /dev/null +++ b/technical-fundamentals/python/coding/problems/23_stack_of_plates.py @@ -0,0 +1,24 @@ +# 23. Stack of Plates: +# Imagine a (literal) stack of plates. If the stack gets too high, it might topple. +# Therefore, in real life, we would likely start a new stack when the previous stack +# exceeds some threshold. Implement a data structure SetOfStacks that mimics this. +# SetOfStacks should be composed of several stacks and should create a new stack once +# the previous one exceeds capacity. SetOfStacks.push() and SetOfStacks.pop() should +# behave identically to a single stack. +# +# FOLLOW UP: Implement a function pop_at(index) which performs a pop on a specific sub-stack. + +from typing import TypeVar, Generic, Optional + +T = TypeVar("T") + + +class StackOfPlates(Generic[T]): + def __init__(self, capacity: int): + pass + + def push(self, value: T) -> None: + pass + + def pop(self) -> Optional[T]: + pass diff --git a/technical-fundamentals/python/coding/problems/24_queue_via_stacks.py b/technical-fundamentals/python/coding/problems/24_queue_via_stacks.py new file mode 100644 index 00000000..46cc0d62 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/24_queue_via_stacks.py @@ -0,0 +1,23 @@ +# 24. Queue via Stacks: +# Implement a MyQueue class which implements a queue using two stacks. + +from typing import TypeVar, Generic, Optional + +T = TypeVar("T") + + +class MyQueue(Generic[T]): + def __init__(self): + pass + + def enqueue(self, value: T) -> None: + pass + + def dequeue(self) -> Optional[T]: + pass + + def peek(self) -> Optional[T]: + pass + + def is_empty(self) -> bool: + pass diff --git a/technical-fundamentals/python/coding/problems/25_sort_stack.py b/technical-fundamentals/python/coding/problems/25_sort_stack.py new file mode 100644 index 00000000..3f784e66 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/25_sort_stack.py @@ -0,0 +1,26 @@ +# 25. Sort Stack: +# Write a program to sort a stack such that the smallest items are on the top. +# You can use an additional temporary stack, but you may not copy the elements +# into any other data structure (such as an array). +# The stack supports the following operations: push, pop, peek, and is_empty. + +from typing import TypeVar, Generic, Optional + +T = TypeVar("T") + + +class SortStack(Generic[T]): + def __init__(self): + pass + + def push(self, value: T) -> None: + pass + + def pop(self) -> Optional[T]: + pass + + def peek(self) -> Optional[T]: + pass + + def is_empty(self) -> bool: + pass diff --git a/technical-fundamentals/python/coding/problems/26_animal_shelter.py b/technical-fundamentals/python/coding/problems/26_animal_shelter.py new file mode 100644 index 00000000..a29ba359 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/26_animal_shelter.py @@ -0,0 +1,34 @@ +# 26. Animal Shelter: +# An animal shelter, which holds only dogs and cats, operates on a strictly +# "first in, first out" basis. People must adopt either the "oldest" (based on +# arrival time) of all animals at the shelter, or they can select whether they +# would prefer a dog or a cat (and will receive the oldest animal of that type). +# They cannot select which specific animal they would like. +# Create the data structures to maintain this system and implement operations +# such as enqueue, dequeue_any, dequeue_dog, and dequeue_cat. + +from typing import Literal, Optional + +AnimalType = Literal["dog", "cat"] + + +class Animal: + def __init__(self, animal_type: AnimalType): + self.type = animal_type + + +class AnimalShelter: + def __init__(self): + pass + + def enqueue(self, animal_type: AnimalType) -> None: + pass + + def dequeue_any(self) -> Optional[Animal]: + pass + + def dequeue_dog(self) -> Optional[Animal]: + pass + + def dequeue_cat(self) -> Optional[Animal]: + pass diff --git a/technical-fundamentals/python/coding/problems/30_trees.py b/technical-fundamentals/python/coding/problems/30_trees.py new file mode 100644 index 00000000..52155864 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/30_trees.py @@ -0,0 +1,36 @@ +# 30. Write the basic tree algorithms of Depth-First Search and Breadth-First Search. + +from __future__ import annotations +from typing import TypeVar, Generic, Optional, Callable + +T = TypeVar("T") + + +class TreeNode(Generic[T]): + def __init__( + self, + value: T, + left: Optional["TreeNode[T]"] = None, + right: Optional["TreeNode[T]"] = None, + parent: Optional["TreeNode[T]"] = None, + ): + self.value = value + self.left = left + self.right = right + self.parent = parent + + +class Tree(Generic[T]): + def bfs( + self, + node: Optional[TreeNode[T]], + visit: Callable[[TreeNode[T]], None], + ) -> None: + pass + + def dfs( + self, + node: Optional[TreeNode[T]], + visit: Callable[[TreeNode[T]], None], + ) -> None: + pass diff --git a/technical-fundamentals/python/coding/problems/31_has_route_between_nodes.py b/technical-fundamentals/python/coding/problems/31_has_route_between_nodes.py new file mode 100644 index 00000000..bb810886 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/31_has_route_between_nodes.py @@ -0,0 +1,16 @@ +# 31. Route Between Nodes: +# Given a directed graph, design an algorithm to find out whether there is a route +# between two nodes. + +from __future__ import annotations +from typing import List + + +class GraphNode: + def __init__(self, value: int, neighbors: List["GraphNode"] = None): + self.value = value + self.neighbors: List["GraphNode"] = neighbors if neighbors is not None else [] + + +def has_route_between_nodes(start: GraphNode, end: GraphNode) -> bool: + pass diff --git a/technical-fundamentals/python/coding/problems/32_minimal_tree.py b/technical-fundamentals/python/coding/problems/32_minimal_tree.py new file mode 100644 index 00000000..d0f16553 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/32_minimal_tree.py @@ -0,0 +1,33 @@ +# 32. Minimal Tree: +# Given a sorted (increasing order) array with unique integer elements, write an +# algorithm to create a binary search tree with minimal height. +# +# A binary search tree is a search where for each node, lesser elements are on +# the left node, and greater elements on the right node. +# +# Input: [1,2,3,4,5,6,7,8] +# Output: +# 5 +# 2 | 7 +# 1 3|6 8 + +from __future__ import annotations +from typing import TypeVar, Generic, Optional, List + +T = TypeVar("T") + + +class TreeNode(Generic[T]): + def __init__( + self, + value: T, + left: Optional["TreeNode[T]"] = None, + right: Optional["TreeNode[T]"] = None, + ): + self.value = value + self.left = left + self.right = right + + +def minimal_tree(sorted_array: List[T]) -> Optional[TreeNode[T]]: + pass diff --git a/technical-fundamentals/python/coding/problems/33_list_of_depths.py b/technical-fundamentals/python/coding/problems/33_list_of_depths.py new file mode 100644 index 00000000..196849d6 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/33_list_of_depths.py @@ -0,0 +1,30 @@ +# 33. List of Depths: +# Given a binary tree, design an algorithm which creates a linked list of all the +# nodes at each depth (e.g., if you have a tree with depth D, you'll have D linked lists). + +from __future__ import annotations +from typing import TypeVar, Generic, Optional, List + +T = TypeVar("T") + + +class TreeNode(Generic[T]): + def __init__( + self, + value: T, + left: Optional["TreeNode[T]"] = None, + right: Optional["TreeNode[T]"] = None, + ): + self.value = value + self.left = left + self.right = right + + +class ListNode(Generic[T]): + def __init__(self, value: T, next: Optional["ListNode[T]"] = None): + self.value = value + self.next = next + + +def list_of_depths(root: Optional[TreeNode[T]]) -> List[ListNode[T]]: + pass diff --git a/technical-fundamentals/python/coding/problems/34_check_balanced.py b/technical-fundamentals/python/coding/problems/34_check_balanced.py new file mode 100644 index 00000000..0d8e1617 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/34_check_balanced.py @@ -0,0 +1,25 @@ +# 34. Check Balanced: +# Implement a function to check if a binary tree is balanced. +# For the purposes of this question, a balanced tree is defined to be a tree such that +# the heights of the two subtrees of any node never differ by more than one. + +from __future__ import annotations +from typing import TypeVar, Generic, Optional + +T = TypeVar("T") + + +class TreeNode(Generic[T]): + def __init__( + self, + value: T, + left: Optional["TreeNode[T]"] = None, + right: Optional["TreeNode[T]"] = None, + ): + self.value = value + self.left = left + self.right = right + + +def check_balanced(tree: Optional[TreeNode[T]]) -> bool: + pass diff --git a/technical-fundamentals/python/coding/problems/35_validate_bst.py b/technical-fundamentals/python/coding/problems/35_validate_bst.py new file mode 100644 index 00000000..a16d7b04 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/35_validate_bst.py @@ -0,0 +1,23 @@ +# 35. Validate BST: +# Implement a function to check if a binary tree is a binary search tree. + +from __future__ import annotations +from typing import TypeVar, Generic, Optional + +T = TypeVar("T") + + +class TreeNode(Generic[T]): + def __init__( + self, + value: T, + left: Optional["TreeNode[T]"] = None, + right: Optional["TreeNode[T]"] = None, + ): + self.value = value + self.left = left + self.right = right + + +def validate_bst(node: Optional[TreeNode[T]]) -> bool: + pass diff --git a/technical-fundamentals/python/coding/problems/36_successor.py b/technical-fundamentals/python/coding/problems/36_successor.py new file mode 100644 index 00000000..9b54ba2f --- /dev/null +++ b/technical-fundamentals/python/coding/problems/36_successor.py @@ -0,0 +1,26 @@ +# 36. Successor: +# Write an algorithm to find the "next" node (i.e., in-order successor) of a given +# node in a binary search tree. You may assume that each node has a link to its parent. + +from __future__ import annotations +from typing import TypeVar, Generic, Optional + +T = TypeVar("T") + + +class TreeNode(Generic[T]): + def __init__( + self, + value: T, + left: Optional["TreeNode[T]"] = None, + right: Optional["TreeNode[T]"] = None, + parent: Optional["TreeNode[T]"] = None, + ): + self.value = value + self.left = left + self.right = right + self.parent = parent + + +def successor(node: TreeNode[T]) -> Optional[TreeNode[T]]: + pass diff --git a/technical-fundamentals/python/coding/problems/37_build_order.py b/technical-fundamentals/python/coding/problems/37_build_order.py new file mode 100644 index 00000000..4d40411c --- /dev/null +++ b/technical-fundamentals/python/coding/problems/37_build_order.py @@ -0,0 +1,19 @@ +# 37. Build Order: +# You are given a list of projects and a list of dependencies (which is a list of pairs +# of projects, where the second project is dependent on the first project). All of a +# project's dependencies must be built before the project is. Find a build order that +# will allow the projects to be built. If there is no valid build order, return an error. +# +# EXAMPLE +# Input: +# projects: a, b, c, d, e, f +# dependencies: (a, d), (f, b), (b, d), (f, a), (d, c) +# Output: e, f, a, b, d, c + +from typing import List, Union + + +def build_order( + projects: List[str], dependencies: List[List[str]] +) -> Union[List[str], str]: + pass diff --git a/technical-fundamentals/python/coding/problems/38_first_common_ancestor.py b/technical-fundamentals/python/coding/problems/38_first_common_ancestor.py new file mode 100644 index 00000000..13b1922e --- /dev/null +++ b/technical-fundamentals/python/coding/problems/38_first_common_ancestor.py @@ -0,0 +1,29 @@ +# 38. First Common Ancestor: +# Design an algorithm and write code to find the first common ancestor of two nodes +# in a binary tree. Avoid storing additional nodes in a data structure. +# NOTE: This is not necessarily a binary search tree. + +from __future__ import annotations +from typing import TypeVar, Generic, Optional + +T = TypeVar("T") + + +class TreeNode(Generic[T]): + def __init__( + self, + value: T, + left: Optional["TreeNode[T]"] = None, + right: Optional["TreeNode[T]"] = None, + ): + self.value = value + self.left = left + self.right = right + + +def first_common_ancestor( + root: Optional[TreeNode[T]], + p: TreeNode[T], + q: TreeNode[T], +) -> Optional[TreeNode[T]]: + pass diff --git a/technical-fundamentals/python/coding/problems/39_bst_sequences.py b/technical-fundamentals/python/coding/problems/39_bst_sequences.py new file mode 100644 index 00000000..498fb6e9 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/39_bst_sequences.py @@ -0,0 +1,32 @@ +# 39. BST Sequences: +# A binary search tree was created by traversing through an array from left to right +# and inserting each element. Given a binary search tree with distinct elements, +# print all possible arrays that could have led to this tree. +# +# EXAMPLE +# Input: +# 2 +# / \ +# 1 3 +# Output: [[2, 1, 3], [2, 3, 1]] + +from __future__ import annotations +from typing import TypeVar, Generic, Optional, List + +T = TypeVar("T") + + +class TreeNode(Generic[T]): + def __init__( + self, + value: T, + left: Optional["TreeNode[T]"] = None, + right: Optional["TreeNode[T]"] = None, + ): + self.value = value + self.left = left + self.right = right + + +def bst_sequences(root: TreeNode[T]) -> List[List[T]]: + pass diff --git a/technical-fundamentals/python/coding/problems/41_triple_steps.py b/technical-fundamentals/python/coding/problems/41_triple_steps.py new file mode 100644 index 00000000..bc8c4baa --- /dev/null +++ b/technical-fundamentals/python/coding/problems/41_triple_steps.py @@ -0,0 +1,8 @@ +# 41. Triple Step: +# A child is running up a staircase with n steps and can hop either +# 1 step, 2 steps, or 3 steps at a time. Implement a method to count +# how many possible ways the child can run up the stairs. + + +def triple_step(n: int) -> int: + pass diff --git a/technical-fundamentals/python/coding/problems/42_robot_in_a_grid.py b/technical-fundamentals/python/coding/problems/42_robot_in_a_grid.py new file mode 100644 index 00000000..d17f14fc --- /dev/null +++ b/technical-fundamentals/python/coding/problems/42_robot_in_a_grid.py @@ -0,0 +1,14 @@ +# 42. Robot in a Grid: +# Imagine a robot sitting on the upper left corner of a grid with r rows and c columns. +# The robot can only move in two directions, right and down, but certain cells are +# "off limits" such that the robot cannot step on them. +# Design an algorithm to find a path for the robot from the top left to the bottom right. + +from typing import List, Tuple, Union + +Grid = List[List[bool]] +Path = List[Tuple[int, int]] + + +def robot_in_a_grid(grid: Grid) -> Union[Path, bool]: + pass diff --git a/technical-fundamentals/python/coding/problems/43_magic_index.py b/technical-fundamentals/python/coding/problems/43_magic_index.py new file mode 100644 index 00000000..e52872a9 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/43_magic_index.py @@ -0,0 +1,16 @@ +# 43. Magic Index: +# A magic index in an array A[0...n-1] is defined to be an index such that A[i] = i. +# Given a sorted array of distinct integers, write a method to find a magic index, +# if one exists, in array A. +# +# FOLLOW UP: What if the values are not distinct? + +from typing import List, Optional + + +def find_magic_index_distinct(arr: List[int]) -> Optional[int]: + pass + + +def find_magic_index_non_distinct(arr: List[int]) -> Optional[int]: + pass diff --git a/technical-fundamentals/python/coding/problems/44_power_set.py b/technical-fundamentals/python/coding/problems/44_power_set.py new file mode 100644 index 00000000..63fcf39f --- /dev/null +++ b/technical-fundamentals/python/coding/problems/44_power_set.py @@ -0,0 +1,12 @@ +# 44. Power Set: +# Write a method to return all subsets of a set. +# +# Example +# Input: [1, 2, 3] +# Output: [ [], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3] ] + +from typing import List + + +def power_set(s: List[int]) -> List[List[int]]: + pass diff --git a/technical-fundamentals/python/coding/problems/45_recursive_multiply.py b/technical-fundamentals/python/coding/problems/45_recursive_multiply.py new file mode 100644 index 00000000..afee936a --- /dev/null +++ b/technical-fundamentals/python/coding/problems/45_recursive_multiply.py @@ -0,0 +1,8 @@ +# 45. Recursive Multiply: +# Write a recursive function to multiply two positive integers without using the * +# operator. You can use addition, subtraction, and bit shifting, but you should +# minimize the number of those operations. + + +def recursive_multiply(a: int, b: int) -> int: + pass diff --git a/technical-fundamentals/python/coding/problems/46_towers_of_hanoi.py b/technical-fundamentals/python/coding/problems/46_towers_of_hanoi.py new file mode 100644 index 00000000..a49ca9d9 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/46_towers_of_hanoi.py @@ -0,0 +1,20 @@ +# 46. Towers of Hanoi: +# In the classic problem of the Towers of Hanoi, you have 3 towers and N disks of +# different sizes which can slide onto any tower. The puzzle starts with disks sorted +# in ascending order of size from top to bottom (i.e., each disk sits on top of an +# even larger one). +# +# Constraints: +# - Only one disk can be moved at a time. +# - A disk is slid off the top of one tower onto another tower. +# - A disk cannot be placed on top of a smaller disk. +# +# Write a program to move the disks from the first tower to the last using stacks. + +from typing import List, Tuple + +Tower = List[int] + + +def towers_of_hanoi(n: int) -> Tuple[Tower, Tower, Tower]: + pass diff --git a/technical-fundamentals/python/coding/problems/47_permutations_with_dups.py b/technical-fundamentals/python/coding/problems/47_permutations_with_dups.py new file mode 100644 index 00000000..b1a46382 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/47_permutations_with_dups.py @@ -0,0 +1,16 @@ +# 47. Permutations without Dups: +# Write a method to compute all permutations of a string of unique characters. + +# Permutations with Dups: +# Write a method to compute all permutations of a string whose characters are not +# necessarily unique. The list of permutations should not have duplicates. + +from typing import List + + +def permutations_without_dups(s: str) -> List[str]: + pass + + +def permutations_with_dups(s: str) -> List[str]: + pass diff --git a/technical-fundamentals/python/coding/problems/48_coin_change.py b/technical-fundamentals/python/coding/problems/48_coin_change.py new file mode 100644 index 00000000..4f84b963 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/48_coin_change.py @@ -0,0 +1,19 @@ +# 48. Coin Change: https://leetcode.com/problems/coin-change-ii/description/ +# You are given an integer array coins representing coins of different denominations +# and an integer amount representing a total amount of money. +# Return the number of combinations that make up that amount. +# If that amount of money cannot be made up by any combination of the coins, return 0. +# +# Input: amount = 5, coins = [1,2,5] +# Output: 4 +# Explanation: there are four ways to make up the amount: +# 5=5 +# 5=2+2+1 +# 5=2+1+1+1 +# 5=1+1+1+1+1 + +from typing import List + + +def coin_change(amount: int, coins: List[int]) -> int: + pass diff --git a/technical-fundamentals/python/coding/problems/__init__.py b/technical-fundamentals/python/coding/problems/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/technical-fundamentals/python/coding/problems/tests/__init__.py b/technical-fundamentals/python/coding/problems/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/technical-fundamentals/python/coding/problems/tests/__pycache__/__init__.cpython-314.pyc b/technical-fundamentals/python/coding/problems/tests/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 00000000..5ff4d218 Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/__pycache__/__init__.cpython-314.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/__pycache__/conftest.cpython-314-pytest-9.0.3.pyc b/technical-fundamentals/python/coding/problems/tests/__pycache__/conftest.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 00000000..90c2075d Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/__pycache__/conftest.cpython-314-pytest-9.0.3.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/lists/__init__.py b/technical-fundamentals/python/coding/problems/tests/lists/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/technical-fundamentals/python/coding/problems/tests/lists/test_11_remove_dups.py b/technical-fundamentals/python/coding/problems/tests/lists/test_11_remove_dups.py new file mode 100644 index 00000000..3adc11b3 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/lists/test_11_remove_dups.py @@ -0,0 +1,65 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("11_remove_dups.py") +remove_dups = _m.remove_dups +Node = _m.Node + + +def _node(value, next=None): + n = Node(value) + n.next = next + return n + + +def _equal(a, b): + while a and b: + if a.value != b.value: + return False + a, b = a.next, b.next + return a is None and b is None + + +class TestRemoveDups: + def test_removes_duplicates(self): + """remove duplicates on linked list""" + n1, n2, n3 = Node("a"), Node("a"), Node("b") + n1.next, n2.next = n2, n3 + result = remove_dups(n1) + expected = Node("a") + expected.next = Node("b") + assert _equal(result, expected) + + def test_no_duplicates(self): + """no duplicates in linked list""" + n1, n2, n3 = Node("a"), Node("b"), Node("c") + n1.next, n2.next = n2, n3 + result = remove_dups(n1) + assert _equal(result, n1) + + def test_multiple_duplicates(self): + """multiple duplicates in linked list""" + n1, n2, n3 = Node("a"), Node("a"), Node("a") + n1.next, n2.next = n2, n3 + result = remove_dups(n1) + expected = Node("a") + assert _equal(result, expected) + + def test_empty_list(self): + """empty linked list""" + assert remove_dups() is None + + def test_single_node(self): + """linked list with one node""" + n = Node("a") + assert remove_dups(n) is n diff --git a/technical-fundamentals/python/coding/problems/tests/lists/test_12_kth_to_last.py b/technical-fundamentals/python/coding/problems/tests/lists/test_12_kth_to_last.py new file mode 100644 index 00000000..320ea71c --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/lists/test_12_kth_to_last.py @@ -0,0 +1,41 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("12_kth_to_last.py") +kth_to_last = _m.kth_to_last +Node = _m.Node + + +class TestKthToLast: + def test_returns_none_if_k_is_less_than_1(self): + """returns undefined if k is less than 1""" + n = Node(1) + assert kth_to_last(n, 0) is None + + def test_returns_none_if_k_greater_than_length(self): + """returns undefined if k is greater than the length of the list""" + n = Node(1) + assert kth_to_last(n, 2) is None + + def test_returns_kth_to_last_element(self): + """returns the kth to last element when k is valid""" + n1, n2, n3, n4, n5 = Node(1), Node(2), Node(3), Node(4), Node(5) + n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5 + result = kth_to_last(n1, 2) + assert result is n4 + + def test_returns_head_if_k_equals_length(self): + """returns the head if k is equal to the length of the list""" + n1, n2, n3, n4, n5 = Node(1), Node(2), Node(3), Node(4), Node(5) + n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5 + assert kth_to_last(n1, 5) is n1 diff --git a/technical-fundamentals/python/coding/problems/tests/lists/test_13_delete_middle_node.py b/technical-fundamentals/python/coding/problems/tests/lists/test_13_delete_middle_node.py new file mode 100644 index 00000000..2de617fd --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/lists/test_13_delete_middle_node.py @@ -0,0 +1,68 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("13_delete_middle_node.py") +delete_middle_node = _m.delete_middle_node +Node = _m.Node + + +def _values(head): + result = [] + cur = head + while cur: + result.append(cur.value) + cur = cur.next + return result + + +class TestDeleteMiddleNode: + def test_deletes_middle_node_at_position_1(self): + """deletes middle node at position 1""" + n0 = Node(0) + n0.next = Node(1) + n0.next.next = Node(2) + n0.next.next.next = Node(3) + result = delete_middle_node(n0, 1) + assert _values(result) == [0, 2, 3] + + def test_no_deletion_if_position_out_of_range(self): + """no deletion if position is out of range""" + head = Node(1) + head.next = Node(2) + head.next.next = Node(3) + result = delete_middle_node(head, 4) + assert _values(result) == [1, 2, 3] + + def test_no_deletion_if_position_less_than_1(self): + """no deletion if position is less than 1""" + head = Node(1) + head.next = Node(2) + head.next.next = Node(3) + result = delete_middle_node(head, 0) + assert _values(result) == [1, 2, 3] + + def test_no_deletion_if_list_has_only_one_node(self): + """no deletion if list has only one node""" + head = Node(1) + result = delete_middle_node(head, 2) + assert result.value == 1 + assert result.next is None + + def test_no_deletion_if_list_has_only_two_nodes(self): + """no deletion if list has only two nodes""" + head = Node(1) + head.next = Node(2) + result = delete_middle_node(head, 2) + assert result.value == 1 + assert result.next.value == 2 + assert result.next.next is None diff --git a/technical-fundamentals/python/coding/problems/tests/lists/test_14_partition.py b/technical-fundamentals/python/coding/problems/tests/lists/test_14_partition.py new file mode 100644 index 00000000..8bbd118a --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/lists/test_14_partition.py @@ -0,0 +1,62 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("14_partition.py") +partition = _m.partition +Node = _m.Node + + +def _values(head): + result = [] + cur = head + while cur: + result.append(cur.value) + cur = cur.next + return result + + +class TestPartition: + def test_partitions_the_list_correctly(self): + """partitions the list correctly""" + n1, n2, n3, n4, n5, n6, n7 = ( + Node(3), Node(5), Node(8), Node(5), Node(10), Node(2), Node(1), + ) + n1.next, n2.next, n3.next, n4.next, n5.next, n6.next = n2, n3, n4, n5, n6, n7 + result = partition(n1, 5) + vals = _values(result) + left = [v for v in vals if v < 5] + right = [v for v in vals if v >= 5] + assert len(left) == 3 + assert len(right) == 4 + assert vals.index(left[-1]) < vals.index(right[0]) + + def test_handles_single_node_list(self): + """handles single node list correctly""" + n = Node(5) + result = partition(n, 5) + assert result.value == 5 + assert result.next is None + + def test_handles_all_nodes_less_than_x(self): + """handles all nodes less than x""" + n1, n2, n3, n4, n5 = Node(3), Node(2), Node(1), Node(4), Node(5) + n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5 + result = partition(n1, 6) + assert all(v < 6 for v in _values(result)) + + def test_handles_all_nodes_greater_than_or_equal_to_x(self): + """handles all nodes greater than or equal to x""" + n1, n2, n3, n4, n5 = Node(3), Node(2), Node(1), Node(4), Node(5) + n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5 + result = partition(n1, 0) + assert all(v >= 0 for v in _values(result)) diff --git a/technical-fundamentals/python/coding/problems/tests/lists/test_15_sum_lists.py b/technical-fundamentals/python/coding/problems/tests/lists/test_15_sum_lists.py new file mode 100644 index 00000000..d7a255f8 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/lists/test_15_sum_lists.py @@ -0,0 +1,47 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("15_sum_lists.py") +sum_lists = _m.sum_lists +Node = _m.Node + + +def _equal(a, b): + while a and b: + if a.value != b.value: + return False + a, b = a.next, b.next + return a is None and b is None + + +class TestSumLists: + def test_sums_two_lists_without_carryover(self): + """sums two non-empty lists without carryover""" + list1 = Node(1, Node(2, Node(3))) + list2 = Node(4, Node(5, Node(6))) + expected = Node(5, Node(7, Node(9))) + assert _equal(sum_lists(list1, list2), expected) + + def test_sums_two_lists_with_carryover(self): + """sums two non-empty lists with carryover""" + list1 = Node(9, Node(9, Node(9))) + list2 = Node(1) + expected = Node(0, Node(0, Node(0, Node(1)))) + assert _equal(sum_lists(list1, list2), expected) + + def test_sums_lists_with_different_lengths(self): + """sums two lists with different lengths""" + list1 = Node(1, Node(2, Node(3, Node(4)))) + list2 = Node(5, Node(6)) + expected = Node(6, Node(8, Node(3, Node(4)))) + assert _equal(sum_lists(list1, list2), expected) diff --git a/technical-fundamentals/python/coding/problems/tests/lists/test_16_sum_lists_forward_order.py b/technical-fundamentals/python/coding/problems/tests/lists/test_16_sum_lists_forward_order.py new file mode 100644 index 00000000..83243f83 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/lists/test_16_sum_lists_forward_order.py @@ -0,0 +1,68 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("16_sum_lists_forward_order.py") +sum_lists_forward_order = _m.sum_lists_forward_order +Node = _m.Node + + +def _equal(a, b): + while a and b: + if a.value != b.value: + return False + a, b = a.next, b.next + return a is None and b is None + + +class TestSumListsForwardOrder: + def test_sums_one_element_each_without_carryover(self): + """Sums one element each without carryover""" + assert _equal(sum_lists_forward_order(Node(1), Node(2)), Node(3)) + + def test_sums_two_elements_each_without_carryover(self): + """Sums two elements each without carryover""" + l1 = Node(1, Node(3)) + l2 = Node(2, Node(3)) + expected = Node(3, Node(6)) + assert _equal(sum_lists_forward_order(l1, l2), expected) + + def test_sums_without_carryover(self): + """sums two non-empty lists without carryover""" + l1 = Node(1, Node(2, Node(3))) + l2 = Node(4, Node(5, Node(6))) + expected = Node(5, Node(7, Node(9))) + assert _equal(sum_lists_forward_order(l1, l2), expected) + + def test_sums_with_carryover(self): + """sums two non-empty lists with carryover""" + l1 = Node(9, Node(9, Node(9))) + l2 = Node(1) + expected = Node(1, Node(0, Node(0, Node(0)))) + assert _equal(sum_lists_forward_order(l1, l2), expected) + + def test_sums_different_lengths(self): + """sums two lists with different lengths""" + l1 = Node(1, Node(2, Node(3, Node(4)))) + l2 = Node(5, Node(6)) + expected = Node(1, Node(2, Node(9, Node(0)))) + assert _equal(sum_lists_forward_order(l1, l2), expected) + + def test_sums_two_empty_lists(self): + """sums two empty lists""" + assert sum_lists_forward_order(None, None) is None + + def test_sums_one_empty_and_one_non_empty(self): + """sums one empty list and one non-empty list""" + l1 = Node(1, Node(2, Node(3))) + result = sum_lists_forward_order(l1, None) + assert _equal(result, l1) diff --git a/technical-fundamentals/python/coding/problems/tests/lists/test_17_palindrome.py b/technical-fundamentals/python/coding/problems/tests/lists/test_17_palindrome.py new file mode 100644 index 00000000..13e44a30 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/lists/test_17_palindrome.py @@ -0,0 +1,40 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("17_palindrome.py") +is_palindrome = _m.is_palindrome +Node = _m.Node + + +class TestIsPalindrome: + def test_single_node_list_is_palindrome(self): + """single node list is palindrome""" + assert is_palindrome(Node(1)) == True + + def test_palindrome_list_with_odd_number_of_nodes(self): + """palindrome list with odd number of nodes""" + n1, n2, n3, n4, n5 = Node(1), Node(2), Node(3), Node(2), Node(1) + n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5 + assert is_palindrome(n1) == True + + def test_non_palindrome_list(self): + """non-palindrome list""" + n1, n2, n3, n4, n5 = Node(1), Node(2), Node(3), Node(4), Node(5) + n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5 + assert is_palindrome(n1) == False + + def test_palindrome_list_with_even_number_of_nodes(self): + """palindrome list with even number of nodes""" + n1, n2, n3, n4 = Node(1), Node(2), Node(2), Node(1) + n1.next, n2.next, n3.next = n2, n3, n4 + assert is_palindrome(n1) == True diff --git a/technical-fundamentals/python/coding/problems/tests/lists/test_18_intersection.py b/technical-fundamentals/python/coding/problems/tests/lists/test_18_intersection.py new file mode 100644 index 00000000..1b3184da --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/lists/test_18_intersection.py @@ -0,0 +1,42 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("18_intersection.py") +intersection = _m.intersection +Node = _m.Node + + +class TestIntersection: + def test_returns_none_if_lists_do_not_intersect(self): + """returns null if the lists do not intersect""" + list1 = Node(1, Node(2, Node(3, Node(4)))) + list2 = Node(5, Node(6, Node(7, Node(8)))) + assert intersection(list1, list2) is None + + def test_returns_intersection_node(self): + """returns intersection node when lists intersect""" + common = Node(7, Node(8, Node(9))) + list1 = Node(1, Node(2, Node(3, Node(4, common)))) + list2 = Node(5, Node(6, common)) + assert intersection(list1, list2) is common + + def test_returns_intersection_at_head(self): + """returns intersection node when lists intersect at the head""" + common = Node(1, Node(2, Node(3))) + assert intersection(common, common) is common + + def test_returns_intersection_at_end(self): + """returns intersection node when lists intersect at the end""" + list1 = Node(1, Node(2, Node(3, Node(4, Node(5, Node(6, Node(7))))))) + list2 = Node(0, list1) + assert intersection(list1, list2) is list1 diff --git a/technical-fundamentals/python/coding/problems/tests/lists/test_19_loop_detection.py b/technical-fundamentals/python/coding/problems/tests/lists/test_19_loop_detection.py new file mode 100644 index 00000000..abf6cb54 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/lists/test_19_loop_detection.py @@ -0,0 +1,41 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("19_loop_detection.py") +detect_loop = _m.detect_loop +Node = _m.Node + + +class TestLoopDetection: + def test_returns_none_for_single_node(self): + """returns null if the list has only one node""" + assert detect_loop(Node(1)) is None + + def test_returns_none_for_list_without_loop(self): + """returns null if the list does not have a loop""" + head = Node(1, Node(2, Node(3, Node(4, Node(5))))) + assert detect_loop(head) is None + + def test_returns_node_at_beginning_of_loop(self): + """returns the node at the beginning of the loop""" + loop_node = Node(31, Node(32)) + loop_node.next.next = loop_node + head = Node(1, Node(2, Node(3, Node(4, Node(5, Node(6, Node(7, Node(8, Node(9, loop_node))))))))) + assert detect_loop(head) is loop_node + + def test_returns_node_at_beginning_of_longer_loop(self): + """returns the node at the beginning of the loop (longer loop)""" + loop_node = Node(11, Node(12, Node(13))) + loop_node.next.next.next = loop_node + head = Node(1, Node(2, Node(3, Node(4, Node(5, Node(6, Node(7, Node(8, Node(9, Node(10, loop_node)))))))))) + assert detect_loop(head) is loop_node diff --git a/technical-fundamentals/python/coding/problems/tests/recursion/__init__.py b/technical-fundamentals/python/coding/problems/tests/recursion/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/technical-fundamentals/python/coding/problems/tests/recursion/test_41_triple_steps.py b/technical-fundamentals/python/coding/problems/tests/recursion/test_41_triple_steps.py new file mode 100644 index 00000000..ce0fa4c2 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/recursion/test_41_triple_steps.py @@ -0,0 +1,31 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("41_triple_steps.py") +triple_step = _m.triple_step + + +class TestTripleStep: + def test_returns_correct_count_for_valid_input(self): + """returns correct count for valid input""" + assert triple_step(0) == 0 + assert triple_step(1) == 1 + assert triple_step(2) == 2 + assert triple_step(3) == 4 + assert triple_step(4) == 7 + assert triple_step(5) == 13 + + def test_returns_0_for_negative_input(self): + """returns 0 for negative input""" + assert triple_step(-1) == 0 + assert triple_step(-10) == 0 diff --git a/technical-fundamentals/python/coding/problems/tests/recursion/test_42_robot_in_a_grid.py b/technical-fundamentals/python/coding/problems/tests/recursion/test_42_robot_in_a_grid.py new file mode 100644 index 00000000..6070e53e --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/recursion/test_42_robot_in_a_grid.py @@ -0,0 +1,46 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("42_robot_in_a_grid.py") +robot_in_a_grid = _m.robot_in_a_grid + + +class TestRobotInAGrid: + def test_returns_correct_path_for_3x3_grid(self): + """returns correct path for a 3x3 grid""" + grid = [ + [True, True, False], + [True, False, True], + [True, True, True], + ] + assert robot_in_a_grid(grid) == [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)] + + def test_returns_correct_path_for_4x4_grid(self): + """returns correct path for a 4x4 grid""" + grid = [ + [True, True, True, False], + [True, False, True, True], + [True, True, False, False], + [False, True, True, True], + ] + assert robot_in_a_grid(grid) == [(0, 0), (0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (3, 3)] + + def test_returns_false_for_no_path(self): + """returns false for no path""" + grid = [ + [True, False, True, False], + [False, False, True, True], + [True, True, True, False], + [True, True, True, True], + ] + assert not robot_in_a_grid(grid) diff --git a/technical-fundamentals/python/coding/problems/tests/recursion/test_43_magic_index.py b/technical-fundamentals/python/coding/problems/tests/recursion/test_43_magic_index.py new file mode 100644 index 00000000..b45db069 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/recursion/test_43_magic_index.py @@ -0,0 +1,30 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("43_magic_index.py") +find_magic_index_distinct = _m.find_magic_index_distinct +find_magic_index_non_distinct = _m.find_magic_index_non_distinct + + +class TestMagicIndexDistinct: + def test_returns_correct_magic_index(self): + """returns correct magic index for distinct input""" + assert find_magic_index_distinct([-2, -1, 0, 2, 4, 6, 8]) == 4 + assert not find_magic_index_distinct([-3, -2, -1, 4, 5, 7, 9]) + + +class TestMagicIndexNonDistinct: + def test_returns_correct_magic_index(self): + """returns correct magic index for non-distinct input""" + assert find_magic_index_non_distinct([-10, -5, 2, 2, 2, 2, 4, 7, 9, 12, 13]) == 2 + assert not find_magic_index_non_distinct([-10, -5, 0, 2, 5, 7, 9, 12, 13]) diff --git a/technical-fundamentals/python/coding/problems/tests/recursion/test_44_power_set.py b/technical-fundamentals/python/coding/problems/tests/recursion/test_44_power_set.py new file mode 100644 index 00000000..60c8a1a9 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/recursion/test_44_power_set.py @@ -0,0 +1,39 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("44_power_set.py") +power_set = _m.power_set + + +def _sorted_sets(sets): + return sorted([sorted(s) for s in sets]) + + +class TestPowerSet: + def test_returns_correct_power_set_for_3_elements(self): + """returns correct power set for a given set""" + expected = [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]] + assert _sorted_sets(power_set([1, 2, 3])) == _sorted_sets(expected) + + def test_returns_correct_power_set_for_empty_set(self): + """returns correct power set for 4""" + assert _sorted_sets(power_set([])) == [[]] + + def test_returns_correct_power_set_for_4_elements(self): + expected = [ + [], [1], [2], [3], [4], + [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4], + [1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4], + [1, 2, 3, 4], + ] + assert _sorted_sets(power_set([1, 2, 3, 4])) == _sorted_sets(expected) diff --git a/technical-fundamentals/python/coding/problems/tests/recursion/test_45_recursive_multiply.py b/technical-fundamentals/python/coding/problems/tests/recursion/test_45_recursive_multiply.py new file mode 100644 index 00000000..83da7ba5 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/recursion/test_45_recursive_multiply.py @@ -0,0 +1,25 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("45_recursive_multiply.py") +recursive_multiply = _m.recursive_multiply + + +class TestRecursiveMultiply: + def test_returns_correct_product(self): + """returns correct product for two positive integers""" + assert recursive_multiply(3, 4) == 12 + assert recursive_multiply(5, 7) == 35 + assert recursive_multiply(9, 2) == 18 + assert recursive_multiply(0, 10) == 0 + assert recursive_multiply(8, 0) == 0 diff --git a/technical-fundamentals/python/coding/problems/tests/recursion/test_46_towers_of_hanoi.py b/technical-fundamentals/python/coding/problems/tests/recursion/test_46_towers_of_hanoi.py new file mode 100644 index 00000000..069c08bf --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/recursion/test_46_towers_of_hanoi.py @@ -0,0 +1,23 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("46_towers_of_hanoi.py") +towers_of_hanoi = _m.towers_of_hanoi + + +class TestTowersOfHanoi: + def test_returns_correct_tower_configuration(self): + """returns correct tower configuration after moving disks""" + assert towers_of_hanoi(3) == ([], [], [3, 2, 1]) + assert towers_of_hanoi(4) == ([], [], [4, 3, 2, 1]) + assert towers_of_hanoi(5) == ([], [], [5, 4, 3, 2, 1]) diff --git a/technical-fundamentals/python/coding/problems/tests/recursion/test_47_permutations_with_dups.py b/technical-fundamentals/python/coding/problems/tests/recursion/test_47_permutations_with_dups.py new file mode 100644 index 00000000..634f6485 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/recursion/test_47_permutations_with_dups.py @@ -0,0 +1,37 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("47_permutations_with_dups.py") +permutations_without_dups = _m.permutations_without_dups +permutations_with_dups = _m.permutations_with_dups + + +class TestPermutationsWithoutDups: + def test_returns_correct_permutations(self): + """returns correct permutations for a string of unique characters""" + result = permutations_without_dups("abc") + expected = ["abc", "acb", "bac", "bca", "cab", "cba"] + assert all(p in result for p in expected) + + +class TestPermutationsWithDups: + def test_returns_correct_permutations_with_dups(self): + """returns correct permutations for a string with duplicate characters""" + result = permutations_with_dups("aab") + expected = ["aab", "aba", "baa"] + assert all(p in result for p in expected) + + def test_returns_correct_permutations_4_chars(self): + result = permutations_with_dups("aabb") + expected = ["aabb", "abab", "abba", "baab", "baba", "bbaa"] + assert all(p in result for p in expected) diff --git a/technical-fundamentals/python/coding/problems/tests/recursion/test_48_coin_change.py b/technical-fundamentals/python/coding/problems/tests/recursion/test_48_coin_change.py new file mode 100644 index 00000000..c5108e3c --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/recursion/test_48_coin_change.py @@ -0,0 +1,28 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("48_coin_change.py") +coin_change = _m.coin_change + + +class TestCoinChange: + def test_returns_0_if_coins_do_not_match(self): + """returns 0 if coins are invalid or do not match""" + assert coin_change(10, [15]) == 0 + assert coin_change(10, []) == 0 + assert coin_change(10, [7]) == 0 + + def test_returns_correct_counts(self): + """returns correct counts for various examples""" + assert coin_change(5, [1, 2, 5]) == 4 + assert coin_change(10, [10]) == 1 diff --git a/technical-fundamentals/python/coding/problems/tests/stacks/__init__.py b/technical-fundamentals/python/coding/problems/tests/stacks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/technical-fundamentals/python/coding/problems/tests/stacks/test_21_three_stacks.py b/technical-fundamentals/python/coding/problems/tests/stacks/test_21_three_stacks.py new file mode 100644 index 00000000..baff603f --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/stacks/test_21_three_stacks.py @@ -0,0 +1,66 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("21_three_stacks.py") +ThreeStacks = _m.ThreeStacks + + +class TestThreeStacks: + def test_push_and_pop_from_stack_0(self): + """push and pop elements from stack 1""" + s = ThreeStacks(9) + s.push(0, 1); s.push(0, 2); s.push(0, 3) + assert s.pop(0) == 3 + assert s.pop(0) == 2 + assert s.pop(0) == 1 + assert s.pop(0) is None + + def test_push_and_pop_from_stack_1(self): + """push and pop elements from stack 2""" + s = ThreeStacks(9) + s.push(1, 4); s.push(1, 5); s.push(1, 6) + assert s.pop(1) == 6 + assert s.pop(1) == 5 + assert s.pop(1) == 4 + assert s.pop(1) is None + + def test_push_and_pop_from_stack_2(self): + """push and pop elements from stack 3""" + s = ThreeStacks(9) + s.push(2, 7); s.push(2, 8); s.push(2, 9) + assert s.pop(2) == 9 + assert s.pop(2) == 8 + assert s.pop(2) == 7 + assert s.pop(2) is None + + def test_pop_from_empty_stack_returns_none(self): + """pop elements from empty stack""" + s = ThreeStacks(3) + assert s.pop(0) is None + assert s.pop(1) is None + assert s.pop(2) is None + + def test_peek_elements_from_stacks(self): + """peek elements from stacks""" + s = ThreeStacks(3) + s.push(0, 1); s.push(1, 2); s.push(2, 3) + assert s.peek(0) == 1 + assert s.peek(1) == 2 + assert s.peek(2) == 3 + + def test_peek_from_empty_stack_returns_none(self): + """peek elements from empty stack""" + s = ThreeStacks(3) + assert s.peek(0) is None + assert s.peek(1) is None + assert s.peek(2) is None diff --git a/technical-fundamentals/python/coding/problems/tests/stacks/test_22_stack_min.py b/technical-fundamentals/python/coding/problems/tests/stacks/test_22_stack_min.py new file mode 100644 index 00000000..c2934ab3 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/stacks/test_22_stack_min.py @@ -0,0 +1,50 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("22_stack_min.py") +StackMin = _m.StackMin + + +class TestStackMin: + def test_push_pop_and_min(self): + """push and pop elements from stack""" + s = StackMin() + s.push(5); s.push(2); s.push(8); s.push(1) + assert s.min() == 1 + assert s.pop() == 1 + assert s.min() == 2 + assert s.pop() == 8 + assert s.min() == 2 + assert s.pop() == 2 + assert s.min() == 5 + assert s.pop() == 5 + assert s.min() is None + + def test_min_returns_none_when_empty(self): + """min method returns undefined when stack is empty""" + s = StackMin() + assert s.min() is None + + def test_push_pop_mixed_with_min(self): + """push and pop mixed with min operations""" + s = StackMin() + s.push(3); assert s.min() == 3 + s.push(5); assert s.min() == 3 + s.push(2); assert s.min() == 2 + s.push(1); assert s.min() == 1 + assert s.pop() == 1; assert s.min() == 2 + assert s.pop() == 2; assert s.min() == 3 + s.push(0); assert s.min() == 0 + assert s.pop() == 0; assert s.min() == 3 + assert s.pop() == 5; assert s.min() == 3 + assert s.pop() == 3; assert s.min() is None diff --git a/technical-fundamentals/python/coding/problems/tests/stacks/test_23_stack_of_plates.py b/technical-fundamentals/python/coding/problems/tests/stacks/test_23_stack_of_plates.py new file mode 100644 index 00000000..a0da1284 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/stacks/test_23_stack_of_plates.py @@ -0,0 +1,54 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("23_stack_of_plates.py") +StackOfPlates = _m.StackOfPlates + + +class TestStackOfPlates: + def test_push_and_pop(self): + """push and pop elements from stack""" + s = StackOfPlates(3) + s.push(1); s.push(2); s.push(3) + assert s.pop() == 3 + assert s.pop() == 2 + assert s.pop() == 1 + assert s.pop() is None + s.push(4); s.push(5); s.push(6) + assert s.pop() == 6 + assert s.pop() == 5 + assert s.pop() == 4 + assert s.pop() is None + + def test_push_and_pop_across_multiple_stacks(self): + """push and pop elements from multiple stacks""" + s = StackOfPlates(2) + s.push(1); s.push(2); s.push(3); s.push(4); s.push(5) + assert s.pop() == 5 + assert s.pop() == 4 + assert s.pop() == 3 + assert s.pop() == 2 + assert s.pop() == 1 + assert s.pop() is None + + def test_pop_from_empty_returns_none(self): + """pop from empty stack returns undefined""" + s = StackOfPlates(2) + assert s.pop() is None + + def test_push_beyond_capacity_creates_new_stack(self): + """push beyond capacity creates new stack""" + s = StackOfPlates(2) + s.push(1); s.push(2); s.push(3); s.push(4) + assert s.pop() == 4 + assert s.pop() == 3 diff --git a/technical-fundamentals/python/coding/problems/tests/stacks/test_24_queue_via_stacks.py b/technical-fundamentals/python/coding/problems/tests/stacks/test_24_queue_via_stacks.py new file mode 100644 index 00000000..1c1f8899 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/stacks/test_24_queue_via_stacks.py @@ -0,0 +1,50 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("24_queue_via_stacks.py") +MyQueue = _m.MyQueue + + +class TestMyQueue: + def test_enqueue_and_dequeue(self): + """enqueue and dequeue elements from queue""" + q = MyQueue() + q.enqueue(1); q.enqueue(2); q.enqueue(3) + assert q.dequeue() == 1 + assert q.dequeue() == 2 + assert q.dequeue() == 3 + assert q.dequeue() is None + + def test_enqueue_dequeue_mixed_with_peek(self): + """enqueue and dequeue mixed with peek operations""" + q = MyQueue() + q.enqueue(1); assert q.peek() == 1 + q.enqueue(2); assert q.peek() == 1 + assert q.dequeue() == 1; assert q.peek() == 2 + q.enqueue(3); assert q.peek() == 2 + assert q.dequeue() == 2; assert q.peek() == 3 + assert q.dequeue() == 3; assert q.peek() is None + + def test_peek_from_empty_returns_none(self): + """peek from empty queue returns undefined""" + assert MyQueue().peek() is None + + def test_is_empty_returns_true_for_empty_queue(self): + """isEmpty returns true for empty queue""" + assert MyQueue().is_empty() == True + + def test_is_empty_returns_false_for_non_empty_queue(self): + """isEmpty returns false for non-empty queue""" + q = MyQueue() + q.enqueue(1) + assert q.is_empty() == False diff --git a/technical-fundamentals/python/coding/problems/tests/stacks/test_25_sort_stack.py b/technical-fundamentals/python/coding/problems/tests/stacks/test_25_sort_stack.py new file mode 100644 index 00000000..75bd081f --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/stacks/test_25_sort_stack.py @@ -0,0 +1,54 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("25_sort_stack.py") +SortStack = _m.SortStack + + +class TestSortStack: + def test_push_elements_in_sorted_order(self): + """push elements in sorted order""" + s = SortStack() + s.push(3); assert s.peek() == 3 + s.push(1); assert s.peek() == 1 + s.push(5); assert s.peek() == 1 + s.push(2); assert s.peek() == 1 + s.push(4); assert s.peek() == 1 + + def test_pop_elements_in_sorted_order(self): + """pop elements in sorted order""" + s = SortStack() + s.push(3); s.push(1); s.push(5); s.push(2); s.push(4) + assert s.pop() == 1 + assert s.pop() == 2 + assert s.pop() == 3 + assert s.pop() == 4 + assert s.pop() == 5 + assert s.pop() is None + + def test_peek_does_not_remove_element(self): + """peek returns the top element without removing it""" + s = SortStack() + s.push(3); s.push(1); s.push(5) + assert s.peek() == 1 + assert s.peek() == 1 + + def test_is_empty_returns_true_for_empty_stack(self): + """isEmpty returns true for empty stack""" + assert SortStack().is_empty() == True + + def test_is_empty_returns_false_for_non_empty(self): + """isEmpty returns false for non-empty stack""" + s = SortStack() + s.push(1) + assert s.is_empty() == False diff --git a/technical-fundamentals/python/coding/problems/tests/stacks/test_26_animal_shelter.py b/technical-fundamentals/python/coding/problems/tests/stacks/test_26_animal_shelter.py new file mode 100644 index 00000000..29ee17b6 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/stacks/test_26_animal_shelter.py @@ -0,0 +1,35 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("26_animal_shelter.py") +AnimalShelter = _m.AnimalShelter + + +class TestAnimalShelter: + def test_enqueue_and_dequeue(self): + """enqueue and dequeue elements from queue""" + shelter = AnimalShelter() + shelter.enqueue("dog"); shelter.enqueue("cat"); shelter.enqueue("dog") + assert shelter.dequeue_any().type == "dog" + assert shelter.dequeue_any().type == "cat" + shelter.enqueue("cat"); shelter.enqueue("dog") + assert shelter.dequeue_dog().type == "dog" + shelter.enqueue("dog") + assert shelter.dequeue_cat().type == "cat" + + def test_dequeue_returns_none_when_empty(self): + """dequeue methods return undefined when shelter is empty""" + shelter = AnimalShelter() + assert shelter.dequeue_any() is None + assert shelter.dequeue_dog() is None + assert shelter.dequeue_cat() is None diff --git a/technical-fundamentals/python/coding/problems/tests/strings/__init__.py b/technical-fundamentals/python/coding/problems/tests/strings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/__init__.cpython-314.pyc b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 00000000..6f970d58 Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/__init__.cpython-314.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_01_is_unique.cpython-314-pytest-9.0.3.pyc b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_01_is_unique.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 00000000..33dcdf0f Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_01_is_unique.cpython-314-pytest-9.0.3.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_02_check_permutations.cpython-314-pytest-9.0.3.pyc b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_02_check_permutations.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 00000000..eb1ac789 Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_02_check_permutations.cpython-314-pytest-9.0.3.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_03_urlify.cpython-314-pytest-9.0.3.pyc b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_03_urlify.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 00000000..9d024b63 Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_03_urlify.cpython-314-pytest-9.0.3.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_04_palindrome_permutation.cpython-314-pytest-9.0.3.pyc b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_04_palindrome_permutation.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 00000000..2e731137 Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_04_palindrome_permutation.cpython-314-pytest-9.0.3.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_05_one_away.cpython-314-pytest-9.0.3.pyc b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_05_one_away.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 00000000..1eb8aac6 Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_05_one_away.cpython-314-pytest-9.0.3.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_06_string_compression.cpython-314-pytest-9.0.3.pyc b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_06_string_compression.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 00000000..d6fa2c0b Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_06_string_compression.cpython-314-pytest-9.0.3.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_07_rotate_matrix.cpython-314-pytest-9.0.3.pyc b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_07_rotate_matrix.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 00000000..b3a57b01 Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_07_rotate_matrix.cpython-314-pytest-9.0.3.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_08_zero_matrix.cpython-314-pytest-9.0.3.pyc b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_08_zero_matrix.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 00000000..7ac483dd Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_08_zero_matrix.cpython-314-pytest-9.0.3.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_09_string_rotation.cpython-314-pytest-9.0.3.pyc b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_09_string_rotation.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 00000000..a4558568 Binary files /dev/null and b/technical-fundamentals/python/coding/problems/tests/strings/__pycache__/test_09_string_rotation.cpython-314-pytest-9.0.3.pyc differ diff --git a/technical-fundamentals/python/coding/problems/tests/strings/test_01_is_unique.py b/technical-fundamentals/python/coding/problems/tests/strings/test_01_is_unique.py new file mode 100644 index 00000000..11963154 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/strings/test_01_is_unique.py @@ -0,0 +1,52 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("01_is_unique.py") +is_unique = _m.is_unique + + +class TestIsUnique: + def test_returns_true_for_unique_characters(self): + """Returns true for unique characters""" + assert is_unique("abc") == True + assert is_unique("abcdefg") == True + assert is_unique("123456") == True + assert is_unique("!@#$%^") == True + + def test_returns_false_for_non_unique_characters(self): + """Returns false for non-unique characters""" + assert is_unique("aab") == False + assert is_unique("hello") == False + assert is_unique("testing") == False + assert is_unique("1234456") == False + assert is_unique("abccdef") == False + + def test_returns_true_for_empty_string(self): + """Returns true for empty string""" + assert is_unique("") == True + + def test_handles_whitespace_correctly(self): + """Handles whitespace correctly""" + assert is_unique("a b c") == False + assert is_unique("ab c") == True + + def test_handles_special_characters_correctly(self): + """Handles special characters correctly""" + assert is_unique("!@#$%^&*") == True + assert is_unique("!@#$%^&*!") == False + + def test_handles_mixed_case_correctly(self): + """Handles mixed case correctly""" + assert is_unique("aA") == True + assert is_unique("Aa") == True + assert is_unique("Hello") == False diff --git a/technical-fundamentals/python/coding/problems/tests/strings/test_02_check_permutations.py b/technical-fundamentals/python/coding/problems/tests/strings/test_02_check_permutations.py new file mode 100644 index 00000000..79a60bb9 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/strings/test_02_check_permutations.py @@ -0,0 +1,45 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("02_check_permutations.py") +check_permutations = _m.check_permutations + + +class TestCheckPermutations: + def test_returns_true_for_permutations_with_same_length_strings(self): + """Returns true for permutations with same length strings""" + assert check_permutations("abc", "cba") == True + + def test_returns_false_for_strings_with_different_lengths(self): + """Returns false for strings with different lengths""" + assert check_permutations("abc", "cbad") == False + + def test_returns_true_for_permutations_with_special_characters(self): + """Returns true for permutations with special characters""" + assert check_permutations("abc!", "!bac") == True + + def test_returns_false_for_non_permutations_with_special_characters(self): + """Returns false for non-permutations with special characters""" + assert check_permutations("abc!", "!bcd") == False + + def test_returns_true_for_empty_strings(self): + """Returns true for empty strings""" + assert check_permutations("", "") == True + + def test_returns_true_for_long_strings_with_same_characters(self): + """Returns true for long strings with same characters""" + assert check_permutations("a" * 1000, "a" * 1000) == True + + def test_returns_false_for_long_strings_with_different_characters(self): + """Returns false for long strings with different characters""" + assert check_permutations("a" * 1000, "b" * 1000) == False diff --git a/technical-fundamentals/python/coding/problems/tests/strings/test_03_urlify.py b/technical-fundamentals/python/coding/problems/tests/strings/test_03_urlify.py new file mode 100644 index 00000000..e1d8fd18 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/strings/test_03_urlify.py @@ -0,0 +1,45 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("03_urlify.py") +urlify = _m.urlify + + +class TestURLify: + def test_replaces_spaces_with_percent_20(self): + """Replaces spaces in a string with '%20'""" + assert urlify("ab c") == "ab%20c" + + def test_handles_leading_and_trailing_spaces(self): + """Handles leading and trailing spaces""" + assert urlify(" ab c ") == "%20%20ab%20c%20%20" + + def test_returns_empty_string_when_input_is_empty(self): + """Returns empty string when input is empty""" + assert urlify("") == "" + + def test_does_not_modify_string_without_spaces(self): + """Doesn't modify string without spaces""" + assert urlify("abc") == "abc" + + def test_handles_multiple_consecutive_spaces(self): + """Handles multiple consecutive spaces""" + assert urlify("a b c") == "a%20%20b%20%20%20c" + + def test_handles_special_characters(self): + """Handles special characters""" + assert urlify("a b!c") == "a%20b!c" + + def test_mr_john_smith(self): + """Mr 3ohn Smith""" + assert urlify("Mr 3ohn Smith") == "Mr%203ohn%20Smith" diff --git a/technical-fundamentals/python/coding/problems/tests/strings/test_04_palindrome_permutation.py b/technical-fundamentals/python/coding/problems/tests/strings/test_04_palindrome_permutation.py new file mode 100644 index 00000000..19fa29e8 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/strings/test_04_palindrome_permutation.py @@ -0,0 +1,57 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("04_palindrome_permutation.py") +palindrome_permutation = _m.palindrome_permutation + + +class TestPalindromePermutation: + def test_empty_string(self): + """Empty string""" + assert palindrome_permutation("") == True + + def test_single_character_string(self): + """Single character string""" + assert palindrome_permutation("a") == True + + def test_palindrome_with_odd_length(self): + """Palindrome with odd length""" + assert palindrome_permutation("taco cat") == True + + def test_palindrome_with_even_length(self): + """Palindrome with even length""" + assert palindrome_permutation("rdeder") == True + + def test_non_palindrome_with_odd_length(self): + """Non-palindrome with odd length""" + assert palindrome_permutation("hello") == False + + def test_non_palindrome_with_even_length(self): + """Non-palindrome with even length""" + assert palindrome_permutation("world") == False + + def test_string_with_mixed_case(self): + """String with mixed case""" + assert palindrome_permutation("RaceCar") == True + + def test_string_with_repeated_letters(self): + """String with repeated letters""" + assert palindrome_permutation("rrracecrrar") == True + + def test_string_with_non_alphanumeric_characters(self): + """String with non-alphanumeric characters""" + assert palindrome_permutation("12321") == True + + def test_string_with_no_possible_palindrome_permutation(self): + """String with no possible palindrome permutation""" + assert palindrome_permutation("abcdefg") == False diff --git a/technical-fundamentals/python/coding/problems/tests/strings/test_05_one_away.py b/technical-fundamentals/python/coding/problems/tests/strings/test_05_one_away.py new file mode 100644 index 00000000..e27e1b4c --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/strings/test_05_one_away.py @@ -0,0 +1,58 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("05_one_away.py") +is_one_away = _m.is_one_away + + +class TestOneAway: + def test_one_away_replace(self): + """One Away - Replace""" + assert is_one_away("pale", "bale") == True + assert is_one_away("bbaa", "bcca") == False + + def test_one_away_replace2(self): + """One Away - Replace""" + assert is_one_away("pale", "bale") == True + + def test_one_away_insert(self): + """One Away - Insert""" + assert is_one_away("pale", "ple") == True + + def test_one_away_remove(self): + """One Away - Remove""" + assert is_one_away("pale", "pales") == True + + def test_same_strings(self): + """Same Strings""" + assert is_one_away("abc", "abc") == True + + def test_more_than_one_edit_away(self): + """More Than One Edit Away""" + assert is_one_away("abcd", "efgh") == False + + def test_more_than_one_edit_away_2(self): + """More Than One Edit Away #2""" + assert is_one_away("palesa", "pale") == False + + def test_empty_strings(self): + """Empty Strings""" + assert is_one_away("", "") == True + + def test_one_character_difference(self): + """One Character Difference""" + assert is_one_away("a", "ab") == True + + def test_empty_and_non_empty_string(self): + """Empty and Non-Empty String""" + assert is_one_away("", "a") == True diff --git a/technical-fundamentals/python/coding/problems/tests/strings/test_06_string_compression.py b/technical-fundamentals/python/coding/problems/tests/strings/test_06_string_compression.py new file mode 100644 index 00000000..9653b6a7 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/strings/test_06_string_compression.py @@ -0,0 +1,41 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("06_string_compression.py") +string_compression = _m.string_compression + + +class TestStringCompression: + def test_compresses_string_with_repeated_characters(self): + """compresses string with repeated characters""" + assert string_compression("aabcccccaaa") == "a2b1c5a3" + + def test_returns_original_string_if_compression_does_not_reduce_length(self): + """returns original string if compression does not reduce length""" + assert string_compression("abcde") == "abcde" + + def test_returns_empty_string_for_empty_input(self): + """returns empty string for empty input""" + assert string_compression("") == "" + + def test_returns_single_character_for_single_character_string(self): + """returns single character for string with single character""" + assert string_compression("a") == "a" + + def test_compresses_uppercase_and_lowercase(self): + """compresses string with uppercase and lowercase letters""" + assert string_compression("AAAbbbCCCddd") == "A3b3C3d3" + + def test_returns_original_string_if_no_repeated_characters(self): + """returns original string if no repeated characters""" + assert string_compression("abcdef") == "abcdef" diff --git a/technical-fundamentals/python/coding/problems/tests/strings/test_07_rotate_matrix.py b/technical-fundamentals/python/coding/problems/tests/strings/test_07_rotate_matrix.py new file mode 100644 index 00000000..6b6d9c6d --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/strings/test_07_rotate_matrix.py @@ -0,0 +1,63 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("07_rotate_matrix.py") +rotate_matrix = _m.rotate_matrix + + +class TestRotateMatrix: + def test_rotates_2x2_matrix_clockwise(self): + """rotates 2x2 matrix clockwise""" + matrix = [[1, 2], [3, 4]] + rotate_matrix(matrix) + assert matrix == [[3, 1], [4, 2]] + + def test_rotates_3x3_matrix_clockwise(self): + """rotates 3x3 matrix clockwise""" + matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + rotate_matrix(matrix) + assert matrix == [[7, 4, 1], [8, 5, 2], [9, 6, 3]] + + def test_rotates_4x4_matrix_clockwise(self): + """rotates 4x4 matrix clockwise""" + matrix = [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16], + ] + rotate_matrix(matrix) + assert matrix == [ + [13, 9, 5, 1], + [14, 10, 6, 2], + [15, 11, 7, 3], + [16, 12, 8, 4], + ] + + def test_rotates_5x5_matrix_clockwise(self): + """rotates 5x5 matrix clockwise""" + matrix = [ + [1, 2, 3, 4, 5], + [6, 7, 8, 9, 10], + [11, 12, 13, 14, 15], + [16, 17, 18, 19, 20], + [21, 22, 23, 24, 25], + ] + rotate_matrix(matrix) + assert matrix == [ + [21, 16, 11, 6, 1], + [22, 17, 12, 7, 2], + [23, 18, 13, 8, 3], + [24, 19, 14, 9, 4], + [25, 20, 15, 10, 5], + ] diff --git a/technical-fundamentals/python/coding/problems/tests/strings/test_08_zero_matrix.py b/technical-fundamentals/python/coding/problems/tests/strings/test_08_zero_matrix.py new file mode 100644 index 00000000..62794143 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/strings/test_08_zero_matrix.py @@ -0,0 +1,61 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("08_zero_matrix.py") +zero_matrix = _m.zero_matrix + + +class TestZeroMatrix: + def test_zeroes_2x2_matrix(self): + """zeroes 2x2 matrix""" + matrix = [[0, 2], [3, 4]] + zero_matrix(matrix) + assert matrix == [[0, 0], [0, 4]] + + def test_zeroes_3x3_matrix(self): + """zeroes 3x3 matrix""" + matrix = [[1, 2, 3], [4, 5, 6], [7, 0, 9]] + zero_matrix(matrix) + assert matrix == [[1, 0, 3], [4, 0, 6], [0, 0, 0]] + + def test_zeroes_4x4_matrix(self): + """zeroes 4x4 matrix""" + matrix = [ + [1, 2, 3, 4], + [5, 6, 0, 8], + [9, 10, 11, 12], + [13, 14, 15, 16], + ] + zero_matrix(matrix) + assert matrix == [ + [1, 2, 0, 4], + [0, 0, 0, 0], + [9, 10, 0, 12], + [13, 14, 0, 16], + ] + + def test_2_zeroes_4x4_matrix(self): + """2 zeroes 4x4 matrix""" + matrix = [ + [0, 2, 3, 4], + [5, 6, 0, 8], + [9, 10, 11, 12], + [13, 14, 15, 16], + ] + zero_matrix(matrix) + assert matrix == [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 10, 0, 12], + [0, 14, 0, 16], + ] diff --git a/technical-fundamentals/python/coding/problems/tests/strings/test_09_string_rotation.py b/technical-fundamentals/python/coding/problems/tests/strings/test_09_string_rotation.py new file mode 100644 index 00000000..08546dc1 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/strings/test_09_string_rotation.py @@ -0,0 +1,29 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_utils = _load("utils/strings.py") + + +class TestStringRotation: + def setup_method(self): + _utils.reassign_is_substring() + _m = _load("09_string_rotation.py") + self.string_rotation = _m.string_rotation + + def test_rotates_a_string(self): + """rotates a string""" + assert self.string_rotation("Hello", "oHell") == True + + def test_rotates_another_string(self): + """rotates another string""" + assert self.string_rotation("waterbottle", "erbottlewat") == True diff --git a/technical-fundamentals/python/coding/problems/tests/trees/__init__.py b/technical-fundamentals/python/coding/problems/tests/trees/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/technical-fundamentals/python/coding/problems/tests/trees/test_30_trees.py b/technical-fundamentals/python/coding/problems/tests/trees/test_30_trees.py new file mode 100644 index 00000000..044e732e --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/trees/test_30_trees.py @@ -0,0 +1,40 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("30_trees.py") +Tree = _m.Tree +TreeNode = _m.TreeNode + + +class TestTrees: + def test_dfs_navigates_tree_in_order(self): + """dfs navigates the tree in order""" + root = TreeNode(1, + left=TreeNode(2, left=TreeNode(3), right=TreeNode(4)), + right=TreeNode(5, left=TreeNode(6, left=TreeNode(7)), right=TreeNode(8)), + ) + tree = Tree() + order = [] + tree.dfs(root, lambda node: order.append(node)) + assert [n.value for n in order] == [1, 2, 3, 4, 5, 6, 7, 8] + + def test_bfs_navigates_tree_in_order(self): + """bfs navigates the tree in order""" + root = TreeNode(1, + left=TreeNode(2, left=TreeNode(4), right=TreeNode(5)), + right=TreeNode(3, left=TreeNode(6, left=TreeNode(8)), right=TreeNode(7)), + ) + tree = Tree() + order = [] + tree.bfs(root, lambda node: order.append(node)) + assert [n.value for n in order] == [1, 2, 3, 4, 5, 6, 7, 8] diff --git a/technical-fundamentals/python/coding/problems/tests/trees/test_31_has_route_between_nodes.py b/technical-fundamentals/python/coding/problems/tests/trees/test_31_has_route_between_nodes.py new file mode 100644 index 00000000..22e7e6bf --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/trees/test_31_has_route_between_nodes.py @@ -0,0 +1,40 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("31_has_route_between_nodes.py") +has_route_between_nodes = _m.has_route_between_nodes +GraphNode = _m.GraphNode + + +class TestHasRouteBetweenNodes: + def test_has_route_between_connected_nodes(self): + """has route between connected nodes""" + n1, n2, n3, n4, n5, n6 = ( + GraphNode(1), GraphNode(2), GraphNode(3), + GraphNode(4), GraphNode(5), GraphNode(6), + ) + n1.neighbors = [n2, n5] + n2.neighbors = [n3] + n3.neighbors = [n4, n6] + n6.neighbors = [n3] + assert has_route_between_nodes(n1, n4) == True + assert has_route_between_nodes(n4, n1) == False + assert has_route_between_nodes(n2, n5) == False + assert has_route_between_nodes(n1, n6) == True + + def test_no_route_between_disconnected_nodes(self): + """no route between disconnected nodes""" + n1, n2, n3 = GraphNode(1), GraphNode(2), GraphNode(3) + assert has_route_between_nodes(n1, n2) == False + assert has_route_between_nodes(n2, n3) == False + assert has_route_between_nodes(n1, n3) == False diff --git a/technical-fundamentals/python/coding/problems/tests/trees/test_32_minimal_tree.py b/technical-fundamentals/python/coding/problems/tests/trees/test_32_minimal_tree.py new file mode 100644 index 00000000..77e9fa36 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/trees/test_32_minimal_tree.py @@ -0,0 +1,48 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("32_minimal_tree.py") +minimal_tree = _m.minimal_tree +TreeNode = _m.TreeNode + + +def _tree_equal(a, b): + if a is None and b is None: + return True + if a is None or b is None: + return False + return a.value == b.value and _tree_equal(a.left, b.left) and _tree_equal(a.right, b.right) + + +class TestMinimalTree: + def test_creates_minimal_bst_from_3_elements(self): + """creates minimal height BST from sorted array""" + expected = TreeNode(2, left=TreeNode(1), right=TreeNode(3)) + assert _tree_equal(minimal_tree([1, 2, 3]), expected) + + def test_creates_minimal_bst_from_5_elements(self): + """creates minimal height BST from sorted array 5 length""" + expected = TreeNode(3, left=TreeNode(2, left=TreeNode(1)), right=TreeNode(5, left=TreeNode(4))) + assert _tree_equal(minimal_tree([1, 2, 3, 4, 5]), expected) + + def test_creates_minimal_bst_from_7_elements(self): + """creates minimal height BST from sorted array 7 length""" + expected = TreeNode(4, + left=TreeNode(2, left=TreeNode(1), right=TreeNode(3)), + right=TreeNode(6, left=TreeNode(5), right=TreeNode(7)), + ) + assert _tree_equal(minimal_tree([1, 2, 3, 4, 5, 6, 7]), expected) + + def test_returns_none_for_empty_array(self): + """returns toBeUndefined for empty array""" + assert minimal_tree([]) is None diff --git a/technical-fundamentals/python/coding/problems/tests/trees/test_33_list_of_depths.py b/technical-fundamentals/python/coding/problems/tests/trees/test_33_list_of_depths.py new file mode 100644 index 00000000..fc180bc2 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/trees/test_33_list_of_depths.py @@ -0,0 +1,47 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("33_list_of_depths.py") +list_of_depths = _m.list_of_depths +TreeNode = _m.TreeNode +ListNode = _m.ListNode + + +def _list_values(node): + vals = [] + while node: + vals.append(node.value) + node = node.next + return vals + + +class TestListOfDepths: + def test_creates_linked_lists_at_each_depth(self): + """creates linked lists of nodes at each depth""" + root = TreeNode(1, + left=TreeNode(2, left=TreeNode(4, left=TreeNode(7)), right=TreeNode(5)), + right=TreeNode(3, right=TreeNode(6)), + ) + result = list_of_depths(root) + assert len(result) == 4 + assert _list_values(result[0]) == [1] + assert _list_values(result[1]) == [2, 3] + assert _list_values(result[2]) == [4, 5, 6] + assert _list_values(result[3]) == [7] + + def test_creates_linked_lists_for_single_node_tree(self): + """creates linked lists for single node tree""" + root = TreeNode(1) + result = list_of_depths(root) + assert len(result) == 1 + assert _list_values(result[0]) == [1] diff --git a/technical-fundamentals/python/coding/problems/tests/trees/test_34_check_balanced.py b/technical-fundamentals/python/coding/problems/tests/trees/test_34_check_balanced.py new file mode 100644 index 00000000..1062a2d7 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/trees/test_34_check_balanced.py @@ -0,0 +1,35 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("34_check_balanced.py") +check_balanced = _m.check_balanced +TreeNode = _m.TreeNode + + +class TestCheckBalanced: + def test_returns_true_for_balanced_tree(self): + """returns true for a balanced tree""" + root = TreeNode(1, + left=TreeNode(2, left=TreeNode(4), right=TreeNode(5)), + right=TreeNode(3, left=TreeNode(6), right=TreeNode(7)), + ) + assert check_balanced(root) == True + + def test_returns_false_for_unbalanced_tree(self): + """returns false for an unbalanced tree""" + root = TreeNode(1, left=TreeNode(2, left=TreeNode(3, left=TreeNode(4)))) + assert check_balanced(root) == False + + def test_returns_true_for_empty_tree(self): + """returns true for an empty tree""" + assert check_balanced(None) == True diff --git a/technical-fundamentals/python/coding/problems/tests/trees/test_35_validate_bst.py b/technical-fundamentals/python/coding/problems/tests/trees/test_35_validate_bst.py new file mode 100644 index 00000000..22593c24 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/trees/test_35_validate_bst.py @@ -0,0 +1,44 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("35_validate_bst.py") +validate_bst = _m.validate_bst +TreeNode = _m.TreeNode + + +class TestValidateBST: + def test_returns_true_for_valid_bst(self): + """returns true for a valid BST""" + root = TreeNode(2, left=TreeNode(1), right=TreeNode(3)) + assert validate_bst(root) == True + + def test_returns_false_for_invalid_bst(self): + """returns false for an invalid BST""" + root = TreeNode(1, left=TreeNode(2), right=TreeNode(3)) + assert validate_bst(root) == False + + def test_returns_false_for_invalid_bst_2(self): + """returns false for an invalid BST #2""" + root = TreeNode(3, + left=TreeNode(2, left=TreeNode(1), right=TreeNode(4)), + right=TreeNode(5), + ) + assert validate_bst(root) == False + + def test_returns_true_for_empty_tree(self): + """returns true for an empty tree""" + assert validate_bst(None) == True + + def test_returns_true_for_single_node(self): + """returns true for a single node tree""" + assert validate_bst(TreeNode(5)) == True diff --git a/technical-fundamentals/python/coding/problems/tests/trees/test_36_successor.py b/technical-fundamentals/python/coding/problems/tests/trees/test_36_successor.py new file mode 100644 index 00000000..6b2bc553 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/trees/test_36_successor.py @@ -0,0 +1,38 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("36_successor.py") +successor = _m.successor +TreeNode = _m.TreeNode + + +class TestSuccessor: + def test_returns_correct_in_order_successor(self): + """returns correct in-order successor""" + n2, n4, n6, n8 = TreeNode(2), TreeNode(4), TreeNode(6), TreeNode(8) + n3 = TreeNode(3, left=n2, right=n4) + n7 = TreeNode(7, left=n6, right=n8) + n5 = TreeNode(5, left=n3, right=n7) + n2.parent = n3; n4.parent = n3; n3.parent = n5 + n6.parent = n7; n8.parent = n7; n7.parent = n5 + assert successor(n2).value == 3 + assert successor(n3).value == 4 + assert successor(n4).value == 5 + assert successor(n5).value == 6 + assert successor(n6).value == 7 + assert successor(n7).value == 8 + assert successor(n8) is None + + def test_returns_none_for_node_without_successor(self): + """returns undefined for node without successor""" + assert successor(TreeNode(1)) is None diff --git a/technical-fundamentals/python/coding/problems/tests/trees/test_37_build_order.py b/technical-fundamentals/python/coding/problems/tests/trees/test_37_build_order.py new file mode 100644 index 00000000..a7bea51c --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/trees/test_37_build_order.py @@ -0,0 +1,39 @@ +import importlib.util +from pathlib import Path +import pytest + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("37_build_order.py") +build_order = _m.build_order + + +class TestBuildOrder: + def test_returns_correct_build_order(self): + """returns correct build order for valid input""" + projects = ["a", "b", "c", "d", "e", "f"] + deps = [["a", "d"], ["f", "b"], ["b", "d"], ["f", "a"], ["d", "c"]] + assert build_order(projects, deps) == ["e", "f", "a", "b", "d", "c"] + + def test_raises_error_for_no_valid_order(self): + """throws error for no valid order""" + projects = ["a", "b", "c", "d", "e"] + deps = [["a", "d"], ["f", "b"], ["b", "d"], ["f", "a"], ["d", "c"]] + with pytest.raises(Exception, match="No valid build order exists"): + build_order(projects, deps) + + def test_single_project(self): + """returns correct build order for single project""" + assert build_order(["a"], []) == ["a"] + + def test_empty_input(self): + """returns correct build order for empty input""" + assert build_order([], []) == [] diff --git a/technical-fundamentals/python/coding/problems/tests/trees/test_38_first_common_ancestor.py b/technical-fundamentals/python/coding/problems/tests/trees/test_38_first_common_ancestor.py new file mode 100644 index 00000000..e1ea94a6 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/trees/test_38_first_common_ancestor.py @@ -0,0 +1,28 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("38_first_common_ancestor.py") +first_common_ancestor = _m.first_common_ancestor +TreeNode = _m.TreeNode + + +class TestFirstCommonAncestor: + def test_returns_correct_common_ancestor(self): + """returns correct common ancestor for valid input""" + n4, n5, n6, n7 = TreeNode(4), TreeNode(5), TreeNode(6), TreeNode(7) + n2 = TreeNode(2, left=n4, right=n5) + n3 = TreeNode(3, left=n6, right=n7) + root = TreeNode(1, left=n2, right=n3) + assert first_common_ancestor(root, n2, n3) is root + assert first_common_ancestor(root, n4, n5) is n2 + assert first_common_ancestor(root, n4, n7) is root diff --git a/technical-fundamentals/python/coding/problems/tests/trees/test_39_bst_sequences.py b/technical-fundamentals/python/coding/problems/tests/trees/test_39_bst_sequences.py new file mode 100644 index 00000000..5c9209d8 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tests/trees/test_39_bst_sequences.py @@ -0,0 +1,36 @@ +import importlib.util +from pathlib import Path + +_PROBLEMS = Path(__file__).parents[2] + + +def _load(filename): + spec = importlib.util.spec_from_file_location(filename, _PROBLEMS / filename) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_m = _load("39_bst_sequences.py") +bst_sequences = _m.bst_sequences +TreeNode = _m.TreeNode + + +class TestBstSequences: + def test_returns_correct_sequences_for_3_nodes(self): + """returns correct sequences for valid input with 3 nodes""" + root = TreeNode(2, left=TreeNode(1), right=TreeNode(3)) + result = bst_sequences(root) + assert [2, 1, 3] in result + assert [2, 3, 1] in result + + def test_returns_correct_sequences_for_7_nodes(self): + """returns correct sequences for valid input""" + root = TreeNode(5, + left=TreeNode(3, left=TreeNode(2), right=TreeNode(4)), + right=TreeNode(7, left=TreeNode(6), right=TreeNode(8)), + ) + result = bst_sequences(root) + assert len(result) == 80 + assert [5, 3, 7, 2, 4, 6, 8] in result + assert [5, 7, 3, 2, 4, 6, 8] in result diff --git a/technical-fundamentals/python/coding/problems/tools/add_js_docstrings.py b/technical-fundamentals/python/coding/problems/tools/add_js_docstrings.py new file mode 100644 index 00000000..65d8d807 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tools/add_js_docstrings.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Auto-insert docstrings into Python tests based on corresponding JS test names.""" +import re +from pathlib import Path + +PY_ROOT = Path("/Users/mativs/Projects/100k/interview-ready/technical-fundamentals/python/coding/problems/tests") +JS_ROOT = Path("/Users/mativs/Projects/100k/interview-ready/technical-fundamentals/coding/problems/__tests__") + +def extract_js_test_names(js_file: Path) -> list: + if not js_file.exists(): + return [] + content = js_file.read_text(encoding='utf-8') + return re.findall(r'test\(\s*["\'](.*?)["\']', content) + +def process_file(py_file: Path): + rel = py_file.relative_to(PY_ROOT) + stem = py_file.stem + m = re.match(r'test_(\d+)_(.+)', stem) + if not m: + return False + + num = m.group(1) + js_dir = JS_ROOT / rel.parent + js_candidates = list(js_dir.glob(f"{num}_*.test.ts")) + if not js_candidates: + return False + + js_file = js_candidates[0] + js_names = extract_js_test_names(js_file) + + if not js_names: + return False + + lines = py_file.read_text(encoding='utf-8').splitlines(True) + + new_lines = [] + js_idx = 0 + + for line in lines: + new_lines.append(line) + m = re.match(r'(\s+)def\s+(test_\w+)\(self\):', line) + if m and js_idx < len(js_names): + indent = m.group(1) + js_name = js_names[js_idx] + new_lines.append(f'{indent} """{js_name}"""\n') + js_idx += 1 + + py_file.write_text("".join(new_lines), encoding='utf-8') + return True + +def main(): + py_files = sorted(PY_ROOT.glob("**/test_*.py")) + success = 0 + for py_file in py_files: + if process_file(py_file): + success += 1 + + print(f"Processed {success}/{len(py_files)} files") + +if __name__ == '__main__': + main() diff --git a/technical-fundamentals/python/coding/problems/tools/fix_docstrings.py b/technical-fundamentals/python/coding/problems/tools/fix_docstrings.py new file mode 100644 index 00000000..368c848c --- /dev/null +++ b/technical-fundamentals/python/coding/problems/tools/fix_docstrings.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Fix Python test docstrings to match JS test names exactly.""" +import re +from pathlib import Path + +PY_ROOT = Path("/Users/mativs/Projects/100k/interview-ready/technical-fundamentals/python/coding/problems/tests") +JS_ROOT = Path("/Users/mativs/Projects/100k/interview-ready/technical-fundamentals/coding/problems/__tests__") + +def extract_js_test_names(js_file: Path) -> list: + if not js_file.exists(): + return [] + content = js_file.read_text(encoding='utf-8') + return re.findall(r'test\(\s*["\'](.*?)["\']', content) + +def fix_file(py_file: Path): + # Find corresponding JS file + rel = py_file.relative_to(PY_ROOT) + stem = py_file.stem + m = re.match(r'test_(\d+)_(.+)', stem) + if not m: + return False + + num = m.group(1) + js_dir = JS_ROOT / rel.parent + js_candidates = list(js_dir.glob(f"{num}_*.test.ts")) + if not js_candidates: + return False + + js_file = js_candidates[0] + js_names = extract_js_test_names(js_file) + + if not js_names: + return False + + lines = py_file.read_text(encoding='utf-8').splitlines(True) + new_lines = [] + js_idx = 0 + i = 0 + + while i < len(lines): + line = lines[i] + # Check if this is a test method definition + method_match = re.match(r'(\s+)def\s+(test_\w+)\(self\):', line) + + if method_match and js_idx < len(js_names): + indent = method_match.group(1) + new_lines.append(line) + + # Skip any existing docstrings (triple-quoted strings right after def) + i += 1 + while i < len(lines) and (lines[i].strip().startswith('"""') or lines[i].strip() == ''): + if lines[i].strip().startswith('"""'): + # Skip the entire docstring + if lines[i].strip() == '"""': + i += 1 + while i < len(lines) and not lines[i].strip().endswith('"""'): + i += 1 + i += 1 + else: + # Single-line docstring + i += 1 + else: + i += 1 + + # Add the correct docstring + js_name = js_names[js_idx] + new_lines.append(f'{indent} """{js_name}"""\n') + js_idx += 1 + continue + else: + new_lines.append(line) + i += 1 + + py_file.write_text("".join(new_lines), encoding='utf-8') + return True + +def main(): + py_files = sorted(PY_ROOT.glob("**/test_*.py")) + success = 0 + for py_file in py_files: + if fix_file(py_file): + success += 1 + + print(f"Fixed {success}/{len(py_files)} files") + +if __name__ == '__main__': + main() diff --git a/technical-fundamentals/python/coding/problems/utils/__init__.py b/technical-fundamentals/python/coding/problems/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/technical-fundamentals/python/coding/problems/utils/__pycache__/__init__.cpython-314.pyc b/technical-fundamentals/python/coding/problems/utils/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 00000000..9860fd39 Binary files /dev/null and b/technical-fundamentals/python/coding/problems/utils/__pycache__/__init__.cpython-314.pyc differ diff --git a/technical-fundamentals/python/coding/problems/utils/__pycache__/strings.cpython-314.pyc b/technical-fundamentals/python/coding/problems/utils/__pycache__/strings.cpython-314.pyc new file mode 100644 index 00000000..a6d8c702 Binary files /dev/null and b/technical-fundamentals/python/coding/problems/utils/__pycache__/strings.cpython-314.pyc differ diff --git a/technical-fundamentals/python/coding/problems/utils/strings.py b/technical-fundamentals/python/coding/problems/utils/strings.py new file mode 100644 index 00000000..d8daa1a3 --- /dev/null +++ b/technical-fundamentals/python/coding/problems/utils/strings.py @@ -0,0 +1,27 @@ +""" +Utility for problem 09: String Rotation. +is_substring can only be called once per problem instance. +""" + +from typing import Callable + + +def _create_is_substring() -> Callable[[str, str], bool]: + called = False + + def is_substring(s1: str, s2: str) -> bool: + nonlocal called + if called: + raise RuntimeError("is_substring() can be used only once.") + called = True + return s2 in s1 + + return is_substring + + +is_substring: Callable[[str, str], bool] = _create_is_substring() + + +def reassign_is_substring() -> None: + global is_substring + is_substring = _create_is_substring() diff --git a/technical-fundamentals/python/conftest.py b/technical-fundamentals/python/conftest.py new file mode 100644 index 00000000..8fc48019 --- /dev/null +++ b/technical-fundamentals/python/conftest.py @@ -0,0 +1,50 @@ +import hashlib +from pathlib import Path + +PROBLEMS_DIR = Path(__file__).parent / "coding" / "problems" + + +_JS_TEST_PATH_MAP = { + "test_01_is_unique": "coding/problems/__tests__/strings/01_isUnique.test.ts", + "test_02_check_permutations": "coding/problems/__tests__/strings/02_checkPermutations.test.ts", + "test_03_urlify": "coding/problems/__tests__/strings/03_URLify.test.ts", + "test_04_palindrome_permutation": "coding/problems/__tests__/strings/04_palindromePermutation.test.ts", + "test_05_one_away": "coding/problems/__tests__/strings/05_oneAway.test.ts", + "test_06_string_compression": "coding/problems/__tests__/strings/06_stringCompression.test.ts", + "test_07_rotate_matrix": "coding/problems/__tests__/strings/07_rotateMatrix.test.ts", + "test_08_zero_matrix": "coding/problems/__tests__/strings/08_zeroMatrix.test.ts", + "test_09_string_rotation": "coding/problems/__tests__/strings/09_stringRotation.test.ts", +} + + +def hs(reports): + paths = set() + for report in reports: + parts = report.nodeid.split("::") + file_name = parts[0].split("/")[-1].replace(".py", "") + js_path = _JS_TEST_PATH_MAP.get(file_name) + if js_path: + paths.add(js_path) + + sorted_paths = sorted(paths) + string = "".join(sorted_paths) + return hashlib.md5(string.encode()).hexdigest() + + +def pytest_terminal_summary(terminalreporter, exitstatus, config): + passed = terminalreporter.stats.get("passed", []) + failed = terminalreporter.stats.get("failed", []) + has_failures = len(failed) > 0 + + if has_failures: + terminalreporter.write_line( + "\x1b[31m\x1b[1m❌ Some tests failed. To pass on Interview Ready, you need the password after all tests passed!\x1b[0m" + ) + else: + password = hs(passed) + terminalreporter.write_line( + "\x1b[32m\x1b[1m✨ All tests passed! Great job! 🎉\x1b[0m" + ) + terminalreporter.write_line( + f"\x1b[32m\x1b[1m✨ Use this password for your Interview Ready Submission: {password} \x1b[0m" + ) diff --git a/technical-fundamentals/python/pytest.ini b/technical-fundamentals/python/pytest.ini new file mode 100644 index 00000000..912d70a2 --- /dev/null +++ b/technical-fundamentals/python/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +testpaths = coding/problems/tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* diff --git a/technical-fundamentals/python/requirements.txt b/technical-fundamentals/python/requirements.txt new file mode 100644 index 00000000..e079f8a6 --- /dev/null +++ b/technical-fundamentals/python/requirements.txt @@ -0,0 +1 @@ +pytest