Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 44 additions & 16 deletions digital_image_processing/change_brightness.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,54 @@
"""
Module for adjusting the brightness of images using PIL.
"""

from PIL import Image


def change_brightness(img: Image, level: float) -> Image:
"""
Change the brightness of a PIL Image to a given level.
"""
def change_brightness(img: Image.Image, level: float) -> Image.Image:
"""Change the brightness of a PIL Image by the given level.

def brightness(c: int) -> float:
"""
Fundamental Transformation/Operation that'll be performed on
every bit.
"""
return 128 + level + (c - 128)
Args:
img: The input PIL Image to modify.
level: Brightness adjustment level between -255.0 (darker) and
255.0 (brighter). 0 means no change.

Returns:
A new Image with adjusted brightness.

Raises:
ValueError: If level is outside the valid range [-255.0, 255.0].

Examples:
>>> from PIL import Image
>>> img = Image.new("RGB", (10, 10), (128, 128, 128))
>>> bright_img = change_brightness(img, 50)
>>> bright_img.getpixel((5, 5)) # doctest: +ELLIPSIS
(178, 178, 178)
"""
if not -255.0 <= level <= 255.0:
raise ValueError("level must be between -255.0 (black) and 255.0 (white)")
return img.point(brightness)

def adjust_pixel(c: int) -> int:
"""Adjust a single pixel channel value by the brightness level."""
return max(0, min(255, c + int(level)))

return img.point(adjust_pixel)


if __name__ == "__main__":
# Load image
with Image.open("image_data/lena.jpg") as img:
# Change brightness to 100
brigt_img = change_brightness(img, 100)
brigt_img.save("image_data/lena_brightness.png", format="png")
import sys

if len(sys.argv) != 4:
print("Usage: python change_brightness.py <input_image> <output_image> <level>")
print(" level: brightness adjustment from -255 to 255")
sys.exit(1)

input_path = sys.argv[1]
output_path = sys.argv[2]
level = float(sys.argv[3])

with Image.open(input_path) as img:
bright_img = change_brightness(img, level)
bright_img.save(output_path, format="png")
print(f"Brightness changed by {level} and saved to {output_path}")
68 changes: 51 additions & 17 deletions dynamic_programming/abbreviation.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,69 @@
"""
https://www.hackerrank.com/challenges/abbr/problem
You can perform the following operation on some string, :
String abbreviation problem solved using dynamic programming.

1. Capitalize zero or more of 's lowercase letters at some index i
(i.e., make them uppercase).
2. Delete all of the remaining lowercase letters in .
Given two strings 'a' and 'b', determine if 'a' can be transformed to 'b' by:
1. Capitalizing zero or more of 'a's lowercase letters at any index.
2. Deleting all remaining lowercase letters.

Reference: https://www.hackerrank.com/challenges/Abbr/problem

Example:
a=daBcd and b="ABC"
daBcd -> capitalize a and c(dABCd) -> remove d (ABC)
a = "daBcd" and b = "ABC"
"daBcd" -> capitalize 'a' and 'c' to get "dABCd" -> delete 'd' to get "ABC"
"""


def abbr(a: str, b: str) -> bool:
"""
>>> abbr("daBcd", "ABC")
def abbreviation(a: str, b: str) -> bool:
"""Check if string 'a' can be transformed to 'b'.

Uses dynamic programming where dp[i][j] indicates whether the first i
characters of 'a' can be transformed to the first j characters of 'b'.

Args:
a: The source string containing lowercase and uppercase letters.
b: The target string containing only uppercase letters.

Returns:
True if 'a' can be transformed to 'b', False otherwise.

Examples:
>>> abbreviation("daBcd", "ABC")
True
>>> abbreviation("dBcd", "ABC")
False
>>> abbreviation("ABC", "ABC")
True
>>> abbr("dBcd", "ABC")
>>> abbreviation("abc", "ABC")
False
>>> abbreviation("abCD", "ABCD")
True
"""
n = len(a)
m = len(b)
dp = [[False for _ in range(m + 1)] for _ in range(n + 1)]

# Early exit: if 'b' has lowercase letters, it can never match
if b != b.upper():
return False

# Early exit: if 'a' has more uppercase letters than the length of 'b'
a_upper_count = sum(1 for c in a if c.isupper())
if a_upper_count > m:
return False

dp = [[False] * (m + 1) for _ in range(n + 1)]
dp[0][0] = True

for i in range(n):
for j in range(m + 1):
if dp[i][j]:
if j < m and a[i].upper() == b[j]:
dp[i + 1][j + 1] = True
if a[i].islower():
dp[i + 1][j] = True
if not dp[i][j]:
continue

if j < m and a[i].upper() == b[j]:
dp[i + 1][j + 1] = True

if a[i].islower():
dp[i + 1][j] = True

return dp[n][m]


Expand Down
148 changes: 116 additions & 32 deletions graphs/g_topological_sort.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,131 @@
# Author: Phyllipe Bezerra (https://github.com/pmba)
"""
Graph topological sort implementation using Depth-First Search (DFS).

clothes = {
0: "underwear",
1: "pants",
2: "belt",
3: "suit",
4: "shoe",
5: "socks",
6: "shirt",
7: "tie",
8: "watch",
}
A topological sort or topological ordering of a directed graph is a linear
ordering of its vertices in which each vertex comes before all vertices
to which it has outgoing edges.

graph = [[1, 4], [2, 4], [3], [], [], [4], [2, 7], [3], []]
For example, the graph representing clothing dependencies:
- underwear -> pants -> belt -> suit
- shirt -> tie -> suit
- socks -> shoes

visited = [0 for x in range(len(graph))]
stack = []
Author: Phyllipe Bezerra (https://github.com/pmba)
"""

from collections.abc import Sequence

Check failure on line 16 in graphs/g_topological_sort.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (F401)

graphs/g_topological_sort.py:16:29: F401 `collections.abc.Sequence` imported but unused help: Remove unused import: `collections.abc.Sequence`

def print_stack(stack, clothes):
order = 1
while stack:
current_clothing = stack.pop()
print(order, clothes[current_clothing])
order += 1

class TopologicalSort:
"""Topological sort implementation using DFS."""

def depth_first_search(u, visited, graph):
visited[u] = 1
for v in graph[u]:
if not visited[v]:
depth_first_search(v, visited, graph)
def __init__(
self, graph: list[list[int]], labels: dict[int, str] | None = None
) -> None:
"""Initialize the topological sorter.

Args:
graph: Adjacency list representation where graph[u] contains
all vertices v such that there is an edge from u to v.
labels: Optional mapping from vertex indices to label strings
for pretty printing results.
"""
self.graph = graph
self.labels = labels or {}
self.n = len(graph)
self.visited: list[int] = [0] * self.n
self.stack: list[int] = []

def _depth_first_search(self, u: int) -> None:
"""Perform DFS from vertex u, adding to stack after exploring all neighbors."""
self.visited[u] = 1
for v in self.graph[u]:
if not self.visited[v]:
self._depth_first_search(v)
self.stack.append(u)

def sort(self) -> list[int]:
"""Compute and return the topological ordering of vertices.

Returns:
A list of vertices in topological order (each vertex appears before
all vertices reachable from it).
"""
self.stack = []
self.visited = [0] * self.n

for v in range(self.n):
if not self.visited[v]:
self._depth_first_search(v)

return self.stack[::-1]

def print_sorted(self, order: list[int]) -> None:
"""Print the vertices in topological order with their labels.

Args:
order: A list of vertices in topological order.
"""
for i, vertex in enumerate(order, 1):
label = self.labels.get(vertex, str(vertex))
print(f"{i}. {label}")

stack.append(u)

def topological_sort(graph: list[list[int]], visited: list[int]) -> list[int]:
"""Topological sort of a directed acyclic graph using DFS.

Args:
graph: Adjacency list representation of the graph.
visited: List to track visited vertices (modified in place).

Returns:
Vertices in topological order.

Examples:
>>> graph = [[1, 2], [3], [3], []]
>>> visited = [0] * len(graph)
>>> topological_sort(graph, visited)
[0, 2, 1, 3]
"""
stack = []

def dfs(u: int) -> None:
visited[u] = 1
for v in graph[u]:
if not visited[v]:
dfs(v)
stack.append(u)

def topological_sort(graph, visited):
for v in range(len(graph)):
if not visited[v]:
depth_first_search(v, visited, graph)
dfs(v)

return stack[::-1]


if __name__ == "__main__":
topological_sort(graph, visited)
print(stack)
print_stack(stack, clothes)
# Example: Clothing dependencies
clothes = {
0: "underwear",
1: "pants",
2: "belt",
3: "suit",
4: "shoe",
5: "socks",
6: "shirt",
7: "tie",
8: "watch",
}

graph = [[1, 4], [2, 4], [3], [], [], [4], [2, 7], [3], []]

# Using the class-based interface
sorter = TopologicalSort(graph, clothes)
order = sorter.sort()
sorter.print_sorted(order)

# Also demonstrate the functional interface
print("\n--- Using functional interface ---")
visited = [0] * len(graph)
result = topological_sort(graph, visited)
print(result)
33 changes: 20 additions & 13 deletions searches/sentinel_linear_search.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""
This is pure Python implementation of sentinel linear search algorithm
This is pure Python implementation of sentinel linear search algorithm.

The sentinel linear search improves on linear search by adding a sentinel
(target value) at the end of the list to avoid bounds checking on each iteration.

For doctests run following command:
python -m doctest -v sentinel_linear_search.py
Expand All @@ -10,26 +13,30 @@
python sentinel_linear_search.py
"""

from collections.abc import Sequence

Check failure on line 16 in searches/sentinel_linear_search.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (F401)

searches/sentinel_linear_search.py:16:29: F401 `collections.abc.Sequence` imported but unused help: Remove unused import: `collections.abc.Sequence`
from typing import Any


def sentinel_linear_search(sequence, target):
"""Pure implementation of sentinel linear search algorithm in Python
def sentinel_linear_search(sequence: list, target: Any) -> int | None:
"""Pure implementation of sentinel linear search algorithm in Python.

:param sequence: some sequence with comparable items
:param target: item value to search
:return: index of found item or None if item is not found
Args:
sequence: A list of comparable items. Note: this list will be modified
temporarily by appending the target value (restored before return).
target: The item value to search for.

Returns:
The index of the found item, or None if the item is not found.

Examples:
>>> sentinel_linear_search([0, 5, 7, 10, 15], 0)
0

>>> sentinel_linear_search([0, 5, 7, 10, 15], 15)
4

>>> sentinel_linear_search([0, 5, 7, 10, 15], 5)
1

>>> sentinel_linear_search([0, 5, 7, 10, 15], 6)

>>> sentinel_linear_search([0, 5, 7, 10, 15], 6) is None
True
"""
sequence.append(target)

Expand All @@ -39,7 +46,7 @@

sequence.pop()

if index == len(sequence):
if index >= len(sequence):
return None

return index
Expand All @@ -53,6 +60,6 @@
target = int(target_input)
result = sentinel_linear_search(sequence, target)
if result is not None:
print(f"{target} found at positions: {result}")
print(f"{target} found at position: {result}")
else:
print("Not found")
Loading
Loading