Compare commits

...

11 Commits

Author SHA1 Message Date
Hardik Pawar
7af8962b0b
Merge 156f8fe5fd into 9a572dec2b 2024-10-05 08:45:45 +05:30
ARNAV RAJ
9a572dec2b
feat: Implemented Matrix Exponentiation Method (#11747)
* feat: add Matrix Exponentiation method
docs: updated the header documentation and added new documentation for
the new function.

* feat: added new function matrix exponetiation method

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* feat: This function uses the tail-recursive form of the Euclidean algorithm to calculate

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* reduced the number of characters per line in the comments

* removed unwanted code

* feat: Implemented a new function to swaap numbers without dummy variable

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* removed previos code

* Done with the required changes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Done with the required changes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Done with the required changes

* Done with the required changes

* Done with the required changes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update maths/fibonacci.py

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Done with the required changes

* Done with the required changes

* Done with the required changes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
2024-10-04 09:29:39 -07:00
Hardik Pawar
156f8fe5fd Remove type hints for function docstrings 2024-10-01 15:26:34 +05:30
Hardik Pawar
26321da52f
Update backtracking/coloring.py
Co-authored-by: Christian Clauss <cclauss@me.com>
2024-10-01 08:22:36 +05:30
Hardik Pawar
7fafb02c4a
Update backtracking/coloring.py
Co-authored-by: Christian Clauss <cclauss@me.com>
2024-10-01 08:22:29 +05:30
Hardik Pawar
42c32e7c4e
Update backtracking/coloring.py
Co-authored-by: Christian Clauss <cclauss@me.com>
2024-10-01 08:22:15 +05:30
Hardik Pawar
9da28b0ccc
Update backtracking/coloring.py
Co-authored-by: Christian Clauss <cclauss@me.com>
2024-10-01 08:21:52 +05:30
Hardik Pawar
8fae9d55a8
Update backtracking/coloring.py
Co-authored-by: Christian Clauss <cclauss@me.com>
2024-10-01 08:21:05 +05:30
Hardik Pawar
d38b935106 Fix ruff errors 2024-09-30 20:41:53 +05:30
pre-commit-ci[bot]
92449bfb32 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2024-09-30 15:09:00 +00:00
Hardik Pawar
4de67b014c Improve comments, add doctests to coloring.py 2024-09-30 20:35:42 +05:30
2 changed files with 176 additions and 23 deletions

View File

@ -1,7 +1,7 @@
""" """
Graph Coloring also called "m coloring problem" Graph Coloring (also called the "m coloring problem") is the problem of
consists of coloring a given graph with at most m colors assigning at most 'm' colors to the vertices of a graph such that
such that no adjacent vertices are assigned the same color no two adjacent vertices share the same color.
Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring
""" """
@ -11,13 +11,31 @@ def valid_coloring(
neighbours: list[int], colored_vertices: list[int], color: int neighbours: list[int], colored_vertices: list[int], color: int
) -> bool: ) -> bool:
""" """
Check if a given vertex can be assigned the specified color
without violating the graph coloring constraints (i.e., no two adjacent vertices
have the same color).
Procedure:
For each neighbour check if the coloring constraint is satisfied For each neighbour check if the coloring constraint is satisfied
If any of the neighbours fail the constraint return False If any of the neighbours fail the constraint return False
If all neighbours validate the constraint return True If all neighbours validate the constraint return True
>>> neighbours = [0,1,0,1,0] Parameters:
>>> colored_vertices = [0, 2, 1, 2, 0] neighbours: The list representing which vertices
are adjacent to the current vertex.
1 indicates an edge between the current vertex
and the neighbour.
colored_vertices: List of current color assignments for all vertices
(-1 means uncolored).
color: The color we are trying to assign to the current vertex.
Returns:
True if the vertex can be safely colored with the given color,
otherwise False.
Examples:
>>> neighbours = [0, 1, 0, 1, 0]
>>> colored_vertices = [0, 2, 1, 2, 0]
>>> color = 1 >>> color = 1
>>> valid_coloring(neighbours, colored_vertices, color) >>> valid_coloring(neighbours, colored_vertices, color)
True True
@ -25,8 +43,14 @@ def valid_coloring(
>>> color = 2 >>> color = 2
>>> valid_coloring(neighbours, colored_vertices, color) >>> valid_coloring(neighbours, colored_vertices, color)
False False
>>> neighbors = [1, 0, 1, 0]
>>> colored_vertices = [-1, -1, -1, -1]
>>> color = 0
>>> valid_coloring(neighbors, colored_vertices, color)
True
""" """
# Does any neighbour not satisfy the constraints # Check if any adjacent vertex has already been colored with the same color
return not any( return not any(
neighbour == 1 and colored_vertices[i] == color neighbour == 1 and colored_vertices[i] == color
for i, neighbour in enumerate(neighbours) for i, neighbour in enumerate(neighbours)
@ -37,7 +61,7 @@ def util_color(
graph: list[list[int]], max_colors: int, colored_vertices: list[int], index: int graph: list[list[int]], max_colors: int, colored_vertices: list[int], index: int
) -> bool: ) -> bool:
""" """
Pseudo-Code Recursive function to try and color the graph using backtracking.
Base Case: Base Case:
1. Check if coloring is complete 1. Check if coloring is complete
@ -51,6 +75,20 @@ def util_color(
2.4. if current coloring leads to a solution return 2.4. if current coloring leads to a solution return
2.5. Uncolor given vertex 2.5. Uncolor given vertex
Parameters:
graph: Adjacency matrix representing the graph.
graph[i][j] is 1 if there is an edge
between vertex i and j.
max_colors: Maximum number of colors allowed (m in the m-coloring problem).
colored_vertices: Current color assignments for each vertex.
-1 indicates that the vertex has not been colored
yet.
index: The current vertex index being processed.
Returns:
True if the graph can be colored using at most max_colors, otherwise False.
Examples:
>>> graph = [[0, 1, 0, 0, 0], >>> graph = [[0, 1, 0, 0, 0],
... [1, 0, 1, 0, 1], ... [1, 0, 1, 0, 1],
... [0, 1, 0, 1, 0], ... [0, 1, 0, 1, 0],
@ -67,36 +105,47 @@ def util_color(
>>> util_color(graph, max_colors, colored_vertices, index) >>> util_color(graph, max_colors, colored_vertices, index)
False False
""" """
# Base Case: If all vertices have been assigned a color, we have a valid solution
# Base Case
if index == len(graph): if index == len(graph):
return True return True
# Recursive Step # Try each color for the current vertex
for i in range(max_colors): for color in range(max_colors):
if valid_coloring(graph[index], colored_vertices, i): # Check if it's valid to color the current vertex with 'color'
# Color current vertex if valid_coloring(graph[index], colored_vertices, color):
colored_vertices[index] = i colored_vertices[index] = color # Assign color
# Validate coloring # Recur to color the rest of the vertices
if util_color(graph, max_colors, colored_vertices, index + 1): if util_color(graph, max_colors, colored_vertices, index + 1):
return True return True
# Backtrack # Backtrack if no solution found with the current assignment
colored_vertices[index] = -1 colored_vertices[index] = -1
return False
return False # Return False if no valid coloring is possible
def color(graph: list[list[int]], max_colors: int) -> list[int]: def color(graph: list[list[int]], max_colors: int) -> list[int]:
""" """
Wrapper function to call subroutine called util_color Attempt to color the graph with at most max_colors colors such that no two adjacent
which will either return True or False. vertices have the same color.
If True is returned colored_vertices list is filled with correct colorings If it is possible, returns the list of color assignments;
otherwise, returns an empty list.
Parameters:
graph: Adjacency matrix representing the graph.
max_colors: Maximum number of colors allowed.
Returns:
List of color assignments if the graph can be colored using max_colors.
Each index in the list represents the color assigned
to the corresponding vertex.
If coloring is not possible, returns an empty list.
Examples:
>>> graph = [[0, 1, 0, 0, 0], >>> graph = [[0, 1, 0, 0, 0],
... [1, 0, 1, 0, 1], ... [1, 0, 1, 0, 1],
... [0, 1, 0, 1, 0], ... [0, 1, 0, 1, 0],
... [0, 1, 1, 0, 0], ... [0, 1, 1, 0, 0],
... [0, 1, 0, 0, 0]] ... [0, 1, 0, 0, 0]]
>>> max_colors = 3 >>> max_colors = 3
>>> color(graph, max_colors) >>> color(graph, max_colors)
[0, 1, 0, 2, 0] [0, 1, 0, 2, 0]
@ -104,10 +153,26 @@ def color(graph: list[list[int]], max_colors: int) -> list[int]:
>>> max_colors = 2 >>> max_colors = 2
>>> color(graph, max_colors) >>> color(graph, max_colors)
[] []
>>> graph = [[0, 1], [1, 0]] # Simple 2-node graph
>>> max_colors = 2
>>> color(graph, max_colors)
[0, 1]
>>> graph = [[0, 1, 1], [1, 0, 1], [1, 1, 0]] # Complete graph of 3 vertices
>>> max_colors = 2
>>> color(graph, max_colors)
[]
>>> max_colors = 3
>>> color(graph, max_colors)
[0, 1, 2]
""" """
# Initialize all vertices as uncolored (-1)
colored_vertices = [-1] * len(graph) colored_vertices = [-1] * len(graph)
# Use the utility function to try and color the graph starting from vertex 0
if util_color(graph, max_colors, colored_vertices, 0): if util_color(graph, max_colors, colored_vertices, 0):
return colored_vertices return colored_vertices # The successful color assignment
return [] return [] # No valid coloring is possible

View File

@ -7,6 +7,8 @@ the Binet's formula function because the Binet formula function uses floats
NOTE 2: the Binet's formula function is much more limited in the size of inputs NOTE 2: the Binet's formula function is much more limited in the size of inputs
that it can handle due to the size limitations of Python floats that it can handle due to the size limitations of Python floats
NOTE 3: the matrix function is the fastest and most memory efficient for large n
See benchmark numbers in __main__ for performance comparisons/ See benchmark numbers in __main__ for performance comparisons/
https://en.wikipedia.org/wiki/Fibonacci_number for more information https://en.wikipedia.org/wiki/Fibonacci_number for more information
@ -17,6 +19,9 @@ from collections.abc import Iterator
from math import sqrt from math import sqrt
from time import time from time import time
import numpy as np
from numpy import ndarray
def time_func(func, *args, **kwargs): def time_func(func, *args, **kwargs):
""" """
@ -230,6 +235,88 @@ def fib_binet(n: int) -> list[int]:
return [round(phi**i / sqrt_5) for i in range(n + 1)] return [round(phi**i / sqrt_5) for i in range(n + 1)]
def matrix_pow_np(m: ndarray, power: int) -> ndarray:
"""
Raises a matrix to the power of 'power' using binary exponentiation.
Args:
m: Matrix as a numpy array.
power: The power to which the matrix is to be raised.
Returns:
The matrix raised to the power.
Raises:
ValueError: If power is negative.
>>> m = np.array([[1, 1], [1, 0]], dtype=int)
>>> matrix_pow_np(m, 0) # Identity matrix when raised to the power of 0
array([[1, 0],
[0, 1]])
>>> matrix_pow_np(m, 1) # Same matrix when raised to the power of 1
array([[1, 1],
[1, 0]])
>>> matrix_pow_np(m, 5)
array([[8, 5],
[5, 3]])
>>> matrix_pow_np(m, -1)
Traceback (most recent call last):
...
ValueError: power is negative
"""
result = np.array([[1, 0], [0, 1]], dtype=int) # Identity Matrix
base = m
if power < 0: # Negative power is not allowed
raise ValueError("power is negative")
while power:
if power % 2 == 1:
result = np.dot(result, base)
base = np.dot(base, base)
power //= 2
return result
def fib_matrix_np(n: int) -> int:
"""
Calculates the n-th Fibonacci number using matrix exponentiation.
https://www.nayuki.io/page/fast-fibonacci-algorithms#:~:text=
Summary:%20The%20two%20fast%20Fibonacci%20algorithms%20are%20matrix
Args:
n: Fibonacci sequence index
Returns:
The n-th Fibonacci number.
Raises:
ValueError: If n is negative.
>>> fib_matrix_np(0)
0
>>> fib_matrix_np(1)
1
>>> fib_matrix_np(5)
5
>>> fib_matrix_np(10)
55
>>> fib_matrix_np(-1)
Traceback (most recent call last):
...
ValueError: n is negative
"""
if n < 0:
raise ValueError("n is negative")
if n == 0:
return 0
m = np.array([[1, 1], [1, 0]], dtype=int)
result = matrix_pow_np(m, n - 1)
return int(result[0, 0])
if __name__ == "__main__": if __name__ == "__main__":
from doctest import testmod from doctest import testmod
@ -242,3 +329,4 @@ if __name__ == "__main__":
time_func(fib_memoization, num) # 0.0100 ms time_func(fib_memoization, num) # 0.0100 ms
time_func(fib_recursive_cached, num) # 0.0153 ms time_func(fib_recursive_cached, num) # 0.0153 ms
time_func(fib_recursive, num) # 257.0910 ms time_func(fib_recursive, num) # 257.0910 ms
time_func(fib_matrix_np, num) # 0.0000 ms