



from timeit import timeit

_lowercase     							=		{
    '''MALAYALAM''': True,
    '''String''': False,
    '''rotor''': True,
    '''level''': True,
    '''A''': True,
    '''BB''': True,
    '''ABC''': False,
    '''amanaplanacanalpanama''': True,  # "a man a plan a canal panama"
}
# Ensure our test data is valid
assert all((key == key[::-1]) is value for key, value in test_data.items())






def      _A				(UpperCamelCase		:      str )  ->bool:



			'''simple docstring'''







			lowerCamelCase__ :      int								=		0
			lowerCamelCase__ :      Dict								=		len(UpperCamelCase ) - 1
			while start_i < end_i:
						if s[start_i] == s[end_i]:
									start_i += 1
									end_i -= 1
						else:
									return False
			return True






def      _A				(UpperCamelCase		:      str )  ->bool:



			'''simple docstring'''







			lowerCamelCase__ :      Union[str, Any]								=		len(UpperCamelCase ) // 2
			lowerCamelCase__ :      List[str]								=		len(UpperCamelCase )

			# We need to traverse till half of the length of string
			# as we can get access of the i'th last element from
			# i'th index.
			# eg: [0,1,2,3,4,5] => 4th index can be accessed
			# with the help of 1st index (i==n-i-1)
			# where n is length of string
			return all(s[i] == s[n - i - 1] for i in range(UpperCamelCase ) )






def      _A				(UpperCamelCase		:      str )  ->bool:



			'''simple docstring'''







			if len(UpperCamelCase ) <= 2:
						return True
			if s[0] == s[len(UpperCamelCase ) - 1]:
						return is_palindrome_recursive(s[1:-1] )
			else:
						return False






def      _A				(UpperCamelCase		:      str )  ->bool:



			'''simple docstring'''







			return s == s[::-1]






def      _A				(UpperCamelCase		:      str )  ->None:



			'''simple docstring'''







			lowerCamelCase__ :      Union[str, Any]								=		f"all({name}(key) is value for key, value in test_data.items())"
			lowerCamelCase__ :      Union[str, Any]								=		f"from __main__ import test_data, {name}"
			lowerCamelCase__ :      Union[str, Any]								=		500000
			lowerCamelCase__ :      Tuple								=		timeit(stmt=UpperCamelCase      ,			setup=UpperCamelCase      ,			number=UpperCamelCase )
			print(f"{name:<35} finished {number:,} runs in {result:.5f} seconds" )


if __name__ == "__main__":
				for key, value in test_data.items():
								assert is_palindrome(key) is is_palindrome_recursive(key)
								assert is_palindrome(key) is is_palindrome_slice(key)
								print(F'''{key:21} {value}''')
				print('''a man a plan a canal panama''')

				# finished 500,000 runs in 0.46793 seconds
				benchmark_function('''is_palindrome_slice''')
				# finished 500,000 runs in 0.85234 seconds
				benchmark_function('''is_palindrome''')
				# finished 500,000 runs in 1.32028 seconds
				benchmark_function('''is_palindrome_recursive''')
				# finished 500,000 runs in 2.08679 seconds
				benchmark_function('''is_palindrome_traversal''')








# Author: OMKAR PATHAK, Nwachukwu Chidiebere

# Use a Python dictionary to construct the graph.
from __future__ import annotations

from pprint import pformat
from typing import Generic, TypeVar

_lowercase     							=						TypeVar('''T''')
class 				__A					( Generic[T]      ):





			def __init__(self							,    __magic_name__ = True				):
						lowerCamelCase__ :      dict[T, list[T]]								=		{}  # dictionary of lists
						lowerCamelCase__ :      List[str]								=		directed





			def        _snake_case    (self							,    __magic_name__							,    __magic_name__				):

						if not self.directed:  # For undirected graphs
									# if both source vertex and destination vertex are both present in the
									# adjacency list, add destination vertex to source vertex list of adjacent
									# vertices and add source vertex to destination vertex list of adjacent
									# vertices.
									if source_vertex in self.adj_list and destination_vertex in self.adj_list:
												self.adj_list[source_vertex].append(__magic_name__				)
												self.adj_list[destination_vertex].append(__magic_name__				)
									# if only source vertex is present in adjacency list, add destination vertex
									# to source vertex list of adjacent vertices, then create a new vertex with
									# destination vertex as key and assign a list containing the source vertex
									# as it's first adjacent vertex.
									elif source_vertex in self.adj_list:
												self.adj_list[source_vertex].append(__magic_name__				)
												lowerCamelCase__ :      int								=		[source_vertex]
									# if only destination vertex is present in adjacency list, add source vertex
									# to destination vertex list of adjacent vertices, then create a new vertex
									# with source vertex as key and assign a list containing the source vertex
									# as it's first adjacent vertex.
									elif destination_vertex in self.adj_list:
												self.adj_list[destination_vertex].append(__magic_name__				)
												lowerCamelCase__ :      Dict								=		[destination_vertex]
									# if both source vertex and destination vertex are not present in adjacency
									# list, create a new vertex with source vertex as key and assign a list
									# containing the destination vertex as it's first adjacent vertex also
									# create a new vertex with destination vertex as key and assign a list
									# containing the source vertex as it's first adjacent vertex.
									else:
												lowerCamelCase__ :      Any								=		[destination_vertex]
												lowerCamelCase__ :      Optional[Any]								=		[source_vertex]
						else:  # For directed graphs
									# if both source vertex and destination vertex are present in adjacency
									# list, add destination vertex to source vertex list of adjacent vertices.
									if source_vertex in self.adj_list and destination_vertex in self.adj_list:
												self.adj_list[source_vertex].append(__magic_name__				)
									# if only source vertex is present in adjacency list, add destination
									# vertex to source vertex list of adjacent vertices and create a new vertex
									# with destination vertex as key, which has no adjacent vertex
									elif source_vertex in self.adj_list:
												self.adj_list[source_vertex].append(__magic_name__				)
												lowerCamelCase__ :      Optional[Any]								=		[]
									# if only destination vertex is present in adjacency list, create a new
									# vertex with source vertex as key and assign a list containing destination
									# vertex as first adjacent vertex
									elif destination_vertex in self.adj_list:
												lowerCamelCase__ :      Tuple								=		[destination_vertex]
									# if both source vertex and destination vertex are not present in adjacency
									# list, create a new vertex with source vertex as key and a list containing
									# destination vertex as it's first adjacent vertex. Then create a new vertex
									# with destination vertex as key, which has no adjacent vertex
									else:
												lowerCamelCase__ :      Optional[int]								=		[destination_vertex]
												lowerCamelCase__ :      List[Any]								=		[]

						return self





			def __repr__(self				):
						return pformat(self.adj_list				)



