"""
Python basics and search algorithms
===================================
This single file is a small, beginner-friendly reference for:
1. Basic Python, NumPy, and pandas
2. UCS, BFS, and DFS search
3. Greedy Best-First Search and A*
Run this file with:
python python_and_search_algorithms.py
The examples use fixed values so the file can run without waiting for input.
An input() example is included in basic_python_demo().
"""
from __future__ import annotations
from collections import deque
import heapq
from statistics import mean, median
def basic_python_demo( ) -> None :
"""Show common Python ideas with simple examples."""
print ( "\n " + "=" * 60 )
print ( "1. BASIC PYTHON" )
print ( "=" * 60 )
# -------------------- Data types --------------------
age = 20 # int: a whole number
height = 1.75 # float: a decimal number
name = "Amina" # str: text
is_student = True # bool: True or False
nothing = None # None: no value
print ( "\n Data types:" )
print ( age, type ( age) )
print ( height, type ( height) )
print ( name, type ( name) )
print ( is_student, type ( is_student) )
print ( nothing, type ( nothing) )
# Collections: list, tuple, set, and dictionary
scores = [ 80 , 95 , 72 , 88 ] # mutable ordered collection
coordinates = ( 10 , 20 ) # immutable ordered collection
unique_numbers = { 1 , 2 , 2 , 3 } # duplicate values disappear
student = { "name" : name, "age" : age} # key-value pairs
print ( "\n Collections:" )
print ( "List:" , scores)
print ( "Tuple:" , coordinates)
print ( "Set:" , unique_numbers)
print ( "Dictionary:" , student)
# -------------------- Output and input --------------------
print ( "\n Output:" )
print ( f"{name} has an average score of {mean(scores):.2f}." )
# This is the normal way to read text from a user:
# user_name = input("What is your name? ")
# print("Hello,", user_name)
#
# The line is commented out so this teaching file can run automatically.
sample_input = " python "
print ( "Input example after strip and upper:" , sample_input.strip ( ) .upper ( ) )
# -------------------- Conditions --------------------
print ( "\n Conditional statements:" )
if mean( scores) >= 80 :
print ( "The student is doing very well." )
elif mean( scores) >= 50 :
print ( "The student passed." )
else :
print ( "The student needs more practice." )
# -------------------- Loops and filtering --------------------
print ( "\n Loops and filtering:" )
for score in scores:
print ( "Score:" , score)
# A list comprehension creates a new list in one readable line.
passed_scores = [ score for score in scores if score >= 80 ]
print ( "Scores >= 80:" , passed_scores)
# -------------------- Functions --------------------
def square( number: float ) -> float :
"""Return the square of a number."""
return number * number
def average( values: list [ float ] ) -> float :
"""Return the arithmetic mean of a non-empty list."""
if not values:
raise ValueError ( "average() needs at least one value" )
return sum ( values) / len ( values)
print ( "\n Functions:" )
print ( "square(5) =" , square( 5 ) )
print ( "average(scores) =" , average( scores) )
print ( "statistics.mean(scores) =" , mean( scores) )
print ( "statistics.median(scores) =" , median( scores) )
# -------------------- Class, methods, and objects --------------------
class Student:
"""A simple class that stores a student's data."""
def __init__ ( self , student_name: str , student_scores: list [ float ] ) -> None :
self .name = student_name # attribute
self .scores = student_scores # attribute
def get_average( self ) -> float :
"""A method that calculates this student's average."""
return average( self .scores )
def describe( self ) -> str :
"""A method that returns a sentence about the object."""
return f"{self.name} has an average of {self.get_average():.2f}."
# student_one is an object created from the Student class.
student_one = Student( "Amina" , [ 80 , 95 , 72 , 88 ] )
print ( "\n Class and object:" )
print ( "Object attribute name:" , student_one.name )
print ( "Object attribute scores:" , student_one.scores )
print ( "Calling a method:" , student_one.describe ( ) )
# -------------------- NumPy and pandas --------------------
print ( "\n NumPy and pandas:" )
try :
import numpy as np
import pandas as pd
except ImportError :
print ( "NumPy/pandas are not installed in this Python environment." )
print ( "Install them with: python -m pip install numpy pandas" )
return
# NumPy arrays are useful for fast numerical operations.
numbers = np.array ( [ 10 , 20 , 30 , 40 ] )
print ( "NumPy array:" , numbers)
print ( "Array multiplied by 2:" , numbers * 2 )
print ( "NumPy mean:" , numbers.mean ( ) )
print ( "NumPy median:" , np.median ( numbers) )
# A DataFrame is a table with rows and named columns.
data = {
"name" : [ "Amina" , "Bilal" , "Chloe" , "David" ] ,
"age" : [ 20 , 22 , 19 , 21 ] ,
"score" : [ 88 , 75 , 95 , 60 ] ,
}
frame = pd.DataFrame ( data)
print ( "\n Complete DataFrame:" )
print ( frame)
# Select one column, one row, and one specific cell.
print ( "\n Only the score column:" )
print ( frame[ "score" ] )
print ( "\n First row with iloc:" )
print ( frame.iloc [ 0 ] )
print ( "\n Specific value (row 1, score column):" )
print ( frame.loc [ 1 , "score" ] )
# Conditions in a DataFrame return only matching rows.
high_scores = frame[ frame[ "score" ] >= 80 ]
adults_with_high_scores = frame[ ( frame[ "age" ] >= 20 ) & ( frame[ "score" ] >= 80 ) ]
print ( "\n Students with score >= 80:" )
print ( high_scores)
print ( "\n Students age >= 20 and score >= 80:" )
print ( adults_with_high_scores)
# A graph is represented as: node -> [(neighbor, cost), ...].
WeightedGraph = dict [ str , list [ tuple [ str , float ] ] ]
def ucs( graph: WeightedGraph, start: str , goal: str ) -> tuple [ list [ str ] , float ] :
"""Uniform Cost Search: find the least-cost path in a weighted graph."""
queue: list [ tuple [ float , str , list [ str ] ] ] = [ ( 0 , start, [ start] ) ]
best_cost: dict [ str , float ] = { start: 0 }
while queue:
cost, node, path = heapq .heappop ( queue)
if node == goal:
return path, cost
if cost > best_cost.get ( node, float ( "inf" ) ) :
continue
for neighbor, edge_cost in graph.get ( node, [ ] ) :
new_cost = cost + edge_cost
if new_cost < best_cost.get ( neighbor, float ( "inf" ) ) :
best_cost[ neighbor] = new_cost
heapq .heappush ( queue, ( new_cost, neighbor, path + [ neighbor] ) )
return [ ] , float ( "inf" )
def bfs( graph: WeightedGraph, start: str , goal: str ) -> list [ str ] :
"""Breadth-First Search: find a path with the fewest edges."""
queue = deque( [ ( start, [ start] ) ] )
visited = { start}
while queue:
node, path = queue.popleft ( )
if node == goal:
return path
for neighbor, _ in graph.get ( node, [ ] ) :
if neighbor not in visited:
visited.add ( neighbor)
queue.append ( ( neighbor, path + [ neighbor] ) )
return [ ]
def dfs( graph: WeightedGraph, start: str , goal: str ) -> list [ str ] :
"""Depth-First Search: explore one branch deeply before another."""
stack = [ ( start, [ start] ) ]
visited: set [ str ] = set ( )
while stack:
node, path = stack.pop ( )
if node in visited:
continue
visited.add ( node)
if node == goal:
return path
# Reverse keeps the example's left-to-right order predictable.
for neighbor, _ in reversed ( graph.get ( node, [ ] ) ) :
if neighbor not in visited:
stack.append ( ( neighbor, path + [ neighbor] ) )
return [ ]
def heuristic( node: str , goal: str ) -> float :
"""An example heuristic: estimated remaining cost.
A real heuristic uses problem knowledge, such as straight-line distance.
Here, the values are hand-written only to keep the example easy to read.
"""
estimates = {
"A" : 7 ,
"B" : 6 ,
"C" : 2 ,
"D" : 3 ,
"E" : 1 ,
"F" : 0 ,
}
return estimates.get ( node, 0 ) if node != goal else 0
def greedy_best_first(
graph: WeightedGraph, start: str , goal: str
) -> tuple [ list [ str ] , float ] :
"""Greedy Best-First Search: choose the smallest h(n) first."""
queue: list [ tuple [ float , float , str , list [ str ] ] ] = [
( heuristic( start, goal) , 0 , start, [ start] )
]
visited: set [ str ] = set ( )
while queue:
_, cost, node, path = heapq .heappop ( queue)
if node in visited:
continue
visited.add ( node)
if node == goal:
return path, cost
for neighbor, edge_cost in graph.get ( node, [ ] ) :
if neighbor not in visited:
heapq .heappush (
queue,
(
heuristic( neighbor, goal) ,
cost + edge_cost,
neighbor,
path + [ neighbor] ,
) ,
)
return [ ] , float ( "inf" )
def a_star( graph: WeightedGraph, start: str , goal: str ) -> tuple [ list [ str ] , float ] :
"""A*: choose the smallest f(n) = g(n) + h(n)."""
queue: list [ tuple [ float , float , str , list [ str ] ] ] = [
( heuristic( start, goal) , 0 , start, [ start] )
]
best_cost: dict [ str , float ] = { start: 0 }
while queue:
_, cost, node, path = heapq .heappop ( queue)
if node == goal:
return path, cost
if cost > best_cost.get ( node, float ( "inf" ) ) :
continue
for neighbor, edge_cost in graph.get ( node, [ ] ) :
new_cost = cost + edge_cost
if new_cost < best_cost.get ( neighbor, float ( "inf" ) ) :
best_cost[ neighbor] = new_cost
estimated_total = new_cost + heuristic( neighbor, goal)
heapq .heappush (
queue,
( estimated_total, new_cost, neighbor, path + [ neighbor] ) ,
)
return [ ] , float ( "inf" )
def search_algorithms_demo( ) -> None :
"""Run all search algorithms on the same small graph."""
print ( "\n " + "=" * 60 )
print ( "2. UCS, BFS, DFS, GREEDY BEST-FIRST, AND A*" )
print ( "=" * 60 )
graph: WeightedGraph = {
"A" : [ ( "B" , 2 ) , ( "C" , 5 ) ] ,
"B" : [ ( "D" , 4 ) , ( "E" , 10 ) ] ,
"C" : [ ( "E" , 1 ) ] ,
"D" : [ ( "F" , 3 ) ] ,
"E" : [ ( "F" , 2 ) ] ,
"F" : [ ] ,
}
print ( "\n Graph edges:" )
for node, edges in graph.items ( ) :
print ( f"{node} -> {edges}" )
path, cost = ucs( graph, "A" , "F" )
print ( "\n UCS:" , path, "cost =" , cost)
print ( "BFS:" , bfs( graph, "A" , "F" ) , "(fewest edges, ignores costs)" )
print ( "DFS:" , dfs( graph, "A" , "F" ) , "(first deep path found)" )
greedy_path, greedy_cost = greedy_best_first( graph, "A" , "F" )
a_star_path, a_star_cost = a_star( graph, "A" , "F" )
print ( "Greedy Best-First:" , greedy_path, "cost =" , greedy_cost)
print ( "A*:" , a_star_path, "cost =" , a_star_cost)
print ( "\n Comparison:" )
print ( "- UCS uses g(n), the cost already travelled. It finds an optimal path." )
print ( "- BFS uses depth. It is optimal only when every edge has equal cost." )
print ( "- DFS uses depth-first exploration. It may find a non-optimal path." )
print ( "- Greedy uses h(n), the estimated distance to the goal. It can be fast" )
print ( " but is not always optimal." )
print ( "- A* uses g(n) + h(n). With a good admissible heuristic, it is both" )
print ( " efficient and optimal." )
def main( ) -> None :
basic_python_demo( )
search_algorithms_demo( )
if __name__ == "__main__" :
main( )
IiIiClB5dGhvbiBiYXNpY3MgYW5kIHNlYXJjaCBhbGdvcml0aG1zCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpUaGlzIHNpbmdsZSBmaWxlIGlzIGEgc21hbGwsIGJlZ2lubmVyLWZyaWVuZGx5IHJlZmVyZW5jZSBmb3I6CjEuIEJhc2ljIFB5dGhvbiwgTnVtUHksIGFuZCBwYW5kYXMKMi4gVUNTLCBCRlMsIGFuZCBERlMgc2VhcmNoCjMuIEdyZWVkeSBCZXN0LUZpcnN0IFNlYXJjaCBhbmQgQSoKClJ1biB0aGlzIGZpbGUgd2l0aDoKICAgIHB5dGhvbiBweXRob25fYW5kX3NlYXJjaF9hbGdvcml0aG1zLnB5CgpUaGUgZXhhbXBsZXMgdXNlIGZpeGVkIHZhbHVlcyBzbyB0aGUgZmlsZSBjYW4gcnVuIHdpdGhvdXQgd2FpdGluZyBmb3IgaW5wdXQuCkFuIGlucHV0KCkgZXhhbXBsZSBpcyBpbmNsdWRlZCBpbiBiYXNpY19weXRob25fZGVtbygpLgoiIiIKCmZyb20gX19mdXR1cmVfXyBpbXBvcnQgYW5ub3RhdGlvbnMKCmZyb20gY29sbGVjdGlvbnMgaW1wb3J0IGRlcXVlCmltcG9ydCBoZWFwcQpmcm9tIHN0YXRpc3RpY3MgaW1wb3J0IG1lYW4sIG1lZGlhbgoKCmRlZiBiYXNpY19weXRob25fZGVtbygpIC0+IE5vbmU6CiAgICAiIiJTaG93IGNvbW1vbiBQeXRob24gaWRlYXMgd2l0aCBzaW1wbGUgZXhhbXBsZXMuIiIiCiAgICBwcmludCgiXG4iICsgIj0iICogNjApCiAgICBwcmludCgiMS4gQkFTSUMgUFlUSE9OIikKICAgIHByaW50KCI9IiAqIDYwKQoKICAgICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0gRGF0YSB0eXBlcyAtLS0tLS0tLS0tLS0tLS0tLS0tLQogICAgYWdlID0gMjAgICAgICAgICAgICAgICAgICAgICAgICMgaW50OiBhIHdob2xlIG51bWJlcgogICAgaGVpZ2h0ID0gMS43NSAgICAgICAgICAgICAgICAgICAjIGZsb2F0OiBhIGRlY2ltYWwgbnVtYmVyCiAgICBuYW1lID0gIkFtaW5hIiAgICAgICAgICAgICAgICAgIyBzdHI6IHRleHQKICAgIGlzX3N0dWRlbnQgPSBUcnVlICAgICAgICAgICAgICAjIGJvb2w6IFRydWUgb3IgRmFsc2UKICAgIG5vdGhpbmcgPSBOb25lICAgICAgICAgICAgICAgICAjIE5vbmU6IG5vIHZhbHVlCgogICAgcHJpbnQoIlxuRGF0YSB0eXBlczoiKQogICAgcHJpbnQoYWdlLCB0eXBlKGFnZSkpCiAgICBwcmludChoZWlnaHQsIHR5cGUoaGVpZ2h0KSkKICAgIHByaW50KG5hbWUsIHR5cGUobmFtZSkpCiAgICBwcmludChpc19zdHVkZW50LCB0eXBlKGlzX3N0dWRlbnQpKQogICAgcHJpbnQobm90aGluZywgdHlwZShub3RoaW5nKSkKCiAgICAjIENvbGxlY3Rpb25zOiBsaXN0LCB0dXBsZSwgc2V0LCBhbmQgZGljdGlvbmFyeQogICAgc2NvcmVzID0gWzgwLCA5NSwgNzIsIDg4XSAgICAgICAgICAgICAgICAgICMgbXV0YWJsZSBvcmRlcmVkIGNvbGxlY3Rpb24KICAgIGNvb3JkaW5hdGVzID0gKDEwLCAyMCkgICAgICAgICAgICAgICAgICAgICMgaW1tdXRhYmxlIG9yZGVyZWQgY29sbGVjdGlvbgogICAgdW5pcXVlX251bWJlcnMgPSB7MSwgMiwgMiwgM30gICAgICAgICAgICAgIyBkdXBsaWNhdGUgdmFsdWVzIGRpc2FwcGVhcgogICAgc3R1ZGVudCA9IHsibmFtZSI6IG5hbWUsICJhZ2UiOiBhZ2V9ICAgICAgIyBrZXktdmFsdWUgcGFpcnMKCiAgICBwcmludCgiXG5Db2xsZWN0aW9uczoiKQogICAgcHJpbnQoIkxpc3Q6Iiwgc2NvcmVzKQogICAgcHJpbnQoIlR1cGxlOiIsIGNvb3JkaW5hdGVzKQogICAgcHJpbnQoIlNldDoiLCB1bmlxdWVfbnVtYmVycykKICAgIHByaW50KCJEaWN0aW9uYXJ5OiIsIHN0dWRlbnQpCgogICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLSBPdXRwdXQgYW5kIGlucHV0IC0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgICBwcmludCgiXG5PdXRwdXQ6IikKICAgIHByaW50KGYie25hbWV9IGhhcyBhbiBhdmVyYWdlIHNjb3JlIG9mIHttZWFuKHNjb3Jlcyk6LjJmfS4iKQoKICAgICMgVGhpcyBpcyB0aGUgbm9ybWFsIHdheSB0byByZWFkIHRleHQgZnJvbSBhIHVzZXI6CiAgICAjIHVzZXJfbmFtZSA9IGlucHV0KCJXaGF0IGlzIHlvdXIgbmFtZT8gIikKICAgICMgcHJpbnQoIkhlbGxvLCIsIHVzZXJfbmFtZSkKICAgICMKICAgICMgVGhlIGxpbmUgaXMgY29tbWVudGVkIG91dCBzbyB0aGlzIHRlYWNoaW5nIGZpbGUgY2FuIHJ1biBhdXRvbWF0aWNhbGx5LgogICAgc2FtcGxlX2lucHV0ID0gIiAgcHl0aG9uICAiCiAgICBwcmludCgiSW5wdXQgZXhhbXBsZSBhZnRlciBzdHJpcCBhbmQgdXBwZXI6Iiwgc2FtcGxlX2lucHV0LnN0cmlwKCkudXBwZXIoKSkKCiAgICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tIENvbmRpdGlvbnMgLS0tLS0tLS0tLS0tLS0tLS0tLS0KICAgIHByaW50KCJcbkNvbmRpdGlvbmFsIHN0YXRlbWVudHM6IikKICAgIGlmIG1lYW4oc2NvcmVzKSA+PSA4MDoKICAgICAgICBwcmludCgiVGhlIHN0dWRlbnQgaXMgZG9pbmcgdmVyeSB3ZWxsLiIpCiAgICBlbGlmIG1lYW4oc2NvcmVzKSA+PSA1MDoKICAgICAgICBwcmludCgiVGhlIHN0dWRlbnQgcGFzc2VkLiIpCiAgICBlbHNlOgogICAgICAgIHByaW50KCJUaGUgc3R1ZGVudCBuZWVkcyBtb3JlIHByYWN0aWNlLiIpCgogICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLSBMb29wcyBhbmQgZmlsdGVyaW5nIC0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgICBwcmludCgiXG5Mb29wcyBhbmQgZmlsdGVyaW5nOiIpCiAgICBmb3Igc2NvcmUgaW4gc2NvcmVzOgogICAgICAgIHByaW50KCJTY29yZToiLCBzY29yZSkKCiAgICAjIEEgbGlzdCBjb21wcmVoZW5zaW9uIGNyZWF0ZXMgYSBuZXcgbGlzdCBpbiBvbmUgcmVhZGFibGUgbGluZS4KICAgIHBhc3NlZF9zY29yZXMgPSBbc2NvcmUgZm9yIHNjb3JlIGluIHNjb3JlcyBpZiBzY29yZSA+PSA4MF0KICAgIHByaW50KCJTY29yZXMgPj0gODA6IiwgcGFzc2VkX3Njb3JlcykKCiAgICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tIEZ1bmN0aW9ucyAtLS0tLS0tLS0tLS0tLS0tLS0tLQogICAgZGVmIHNxdWFyZShudW1iZXI6IGZsb2F0KSAtPiBmbG9hdDoKICAgICAgICAiIiJSZXR1cm4gdGhlIHNxdWFyZSBvZiBhIG51bWJlci4iIiIKICAgICAgICByZXR1cm4gbnVtYmVyICogbnVtYmVyCgogICAgZGVmIGF2ZXJhZ2UodmFsdWVzOiBsaXN0W2Zsb2F0XSkgLT4gZmxvYXQ6CiAgICAgICAgIiIiUmV0dXJuIHRoZSBhcml0aG1ldGljIG1lYW4gb2YgYSBub24tZW1wdHkgbGlzdC4iIiIKICAgICAgICBpZiBub3QgdmFsdWVzOgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCJhdmVyYWdlKCkgbmVlZHMgYXQgbGVhc3Qgb25lIHZhbHVlIikKICAgICAgICByZXR1cm4gc3VtKHZhbHVlcykgLyBsZW4odmFsdWVzKQoKICAgIHByaW50KCJcbkZ1bmN0aW9uczoiKQogICAgcHJpbnQoInNxdWFyZSg1KSA9Iiwgc3F1YXJlKDUpKQogICAgcHJpbnQoImF2ZXJhZ2Uoc2NvcmVzKSA9IiwgYXZlcmFnZShzY29yZXMpKQogICAgcHJpbnQoInN0YXRpc3RpY3MubWVhbihzY29yZXMpID0iLCBtZWFuKHNjb3JlcykpCiAgICBwcmludCgic3RhdGlzdGljcy5tZWRpYW4oc2NvcmVzKSA9IiwgbWVkaWFuKHNjb3JlcykpCgogICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLSBDbGFzcywgbWV0aG9kcywgYW5kIG9iamVjdHMgLS0tLS0tLS0tLS0tLS0tLS0tLS0KICAgIGNsYXNzIFN0dWRlbnQ6CiAgICAgICAgIiIiQSBzaW1wbGUgY2xhc3MgdGhhdCBzdG9yZXMgYSBzdHVkZW50J3MgZGF0YS4iIiIKCiAgICAgICAgZGVmIF9faW5pdF9fKHNlbGYsIHN0dWRlbnRfbmFtZTogc3RyLCBzdHVkZW50X3Njb3JlczogbGlzdFtmbG9hdF0pIC0+IE5vbmU6CiAgICAgICAgICAgIHNlbGYubmFtZSA9IHN0dWRlbnRfbmFtZSAgICAgICAjIGF0dHJpYnV0ZQogICAgICAgICAgICBzZWxmLnNjb3JlcyA9IHN0dWRlbnRfc2NvcmVzICAgIyBhdHRyaWJ1dGUKCiAgICAgICAgZGVmIGdldF9hdmVyYWdlKHNlbGYpIC0+IGZsb2F0OgogICAgICAgICAgICAiIiJBIG1ldGhvZCB0aGF0IGNhbGN1bGF0ZXMgdGhpcyBzdHVkZW50J3MgYXZlcmFnZS4iIiIKICAgICAgICAgICAgcmV0dXJuIGF2ZXJhZ2Uoc2VsZi5zY29yZXMpCgogICAgICAgIGRlZiBkZXNjcmliZShzZWxmKSAtPiBzdHI6CiAgICAgICAgICAgICIiIkEgbWV0aG9kIHRoYXQgcmV0dXJucyBhIHNlbnRlbmNlIGFib3V0IHRoZSBvYmplY3QuIiIiCiAgICAgICAgICAgIHJldHVybiBmIntzZWxmLm5hbWV9IGhhcyBhbiBhdmVyYWdlIG9mIHtzZWxmLmdldF9hdmVyYWdlKCk6LjJmfS4iCgogICAgIyBzdHVkZW50X29uZSBpcyBhbiBvYmplY3QgY3JlYXRlZCBmcm9tIHRoZSBTdHVkZW50IGNsYXNzLgogICAgc3R1ZGVudF9vbmUgPSBTdHVkZW50KCJBbWluYSIsIFs4MCwgOTUsIDcyLCA4OF0pCiAgICBwcmludCgiXG5DbGFzcyBhbmQgb2JqZWN0OiIpCiAgICBwcmludCgiT2JqZWN0IGF0dHJpYnV0ZSBuYW1lOiIsIHN0dWRlbnRfb25lLm5hbWUpCiAgICBwcmludCgiT2JqZWN0IGF0dHJpYnV0ZSBzY29yZXM6Iiwgc3R1ZGVudF9vbmUuc2NvcmVzKQogICAgcHJpbnQoIkNhbGxpbmcgYSBtZXRob2Q6Iiwgc3R1ZGVudF9vbmUuZGVzY3JpYmUoKSkKCiAgICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tIE51bVB5IGFuZCBwYW5kYXMgLS0tLS0tLS0tLS0tLS0tLS0tLS0KICAgIHByaW50KCJcbk51bVB5IGFuZCBwYW5kYXM6IikKICAgIHRyeToKICAgICAgICBpbXBvcnQgbnVtcHkgYXMgbnAKICAgICAgICBpbXBvcnQgcGFuZGFzIGFzIHBkCiAgICBleGNlcHQgSW1wb3J0RXJyb3I6CiAgICAgICAgcHJpbnQoIk51bVB5L3BhbmRhcyBhcmUgbm90IGluc3RhbGxlZCBpbiB0aGlzIFB5dGhvbiBlbnZpcm9ubWVudC4iKQogICAgICAgIHByaW50KCJJbnN0YWxsIHRoZW0gd2l0aDogcHl0aG9uIC1tIHBpcCBpbnN0YWxsIG51bXB5IHBhbmRhcyIpCiAgICAgICAgcmV0dXJuCgogICAgIyBOdW1QeSBhcnJheXMgYXJlIHVzZWZ1bCBmb3IgZmFzdCBudW1lcmljYWwgb3BlcmF0aW9ucy4KICAgIG51bWJlcnMgPSBucC5hcnJheShbMTAsIDIwLCAzMCwgNDBdKQogICAgcHJpbnQoIk51bVB5IGFycmF5OiIsIG51bWJlcnMpCiAgICBwcmludCgiQXJyYXkgbXVsdGlwbGllZCBieSAyOiIsIG51bWJlcnMgKiAyKQogICAgcHJpbnQoIk51bVB5IG1lYW46IiwgbnVtYmVycy5tZWFuKCkpCiAgICBwcmludCgiTnVtUHkgbWVkaWFuOiIsIG5wLm1lZGlhbihudW1iZXJzKSkKCiAgICAjIEEgRGF0YUZyYW1lIGlzIGEgdGFibGUgd2l0aCByb3dzIGFuZCBuYW1lZCBjb2x1bW5zLgogICAgZGF0YSA9IHsKICAgICAgICAibmFtZSI6IFsiQW1pbmEiLCAiQmlsYWwiLCAiQ2hsb2UiLCAiRGF2aWQiXSwKICAgICAgICAiYWdlIjogWzIwLCAyMiwgMTksIDIxXSwKICAgICAgICAic2NvcmUiOiBbODgsIDc1LCA5NSwgNjBdLAogICAgfQogICAgZnJhbWUgPSBwZC5EYXRhRnJhbWUoZGF0YSkKICAgIHByaW50KCJcbkNvbXBsZXRlIERhdGFGcmFtZToiKQogICAgcHJpbnQoZnJhbWUpCgogICAgIyBTZWxlY3Qgb25lIGNvbHVtbiwgb25lIHJvdywgYW5kIG9uZSBzcGVjaWZpYyBjZWxsLgogICAgcHJpbnQoIlxuT25seSB0aGUgc2NvcmUgY29sdW1uOiIpCiAgICBwcmludChmcmFtZVsic2NvcmUiXSkKICAgIHByaW50KCJcbkZpcnN0IHJvdyB3aXRoIGlsb2M6IikKICAgIHByaW50KGZyYW1lLmlsb2NbMF0pCiAgICBwcmludCgiXG5TcGVjaWZpYyB2YWx1ZSAocm93IDEsIHNjb3JlIGNvbHVtbik6IikKICAgIHByaW50KGZyYW1lLmxvY1sxLCAic2NvcmUiXSkKCiAgICAjIENvbmRpdGlvbnMgaW4gYSBEYXRhRnJhbWUgcmV0dXJuIG9ubHkgbWF0Y2hpbmcgcm93cy4KICAgIGhpZ2hfc2NvcmVzID0gZnJhbWVbZnJhbWVbInNjb3JlIl0gPj0gODBdCiAgICBhZHVsdHNfd2l0aF9oaWdoX3Njb3JlcyA9IGZyYW1lWyhmcmFtZVsiYWdlIl0gPj0gMjApICYgKGZyYW1lWyJzY29yZSJdID49IDgwKV0KICAgIHByaW50KCJcblN0dWRlbnRzIHdpdGggc2NvcmUgPj0gODA6IikKICAgIHByaW50KGhpZ2hfc2NvcmVzKQogICAgcHJpbnQoIlxuU3R1ZGVudHMgYWdlID49IDIwIGFuZCBzY29yZSA+PSA4MDoiKQogICAgcHJpbnQoYWR1bHRzX3dpdGhfaGlnaF9zY29yZXMpCgoKIyBBIGdyYXBoIGlzIHJlcHJlc2VudGVkIGFzOiBub2RlIC0+IFsobmVpZ2hib3IsIGNvc3QpLCAuLi5dLgpXZWlnaHRlZEdyYXBoID0gZGljdFtzdHIsIGxpc3RbdHVwbGVbc3RyLCBmbG9hdF1dXQoKCmRlZiB1Y3MoZ3JhcGg6IFdlaWdodGVkR3JhcGgsIHN0YXJ0OiBzdHIsIGdvYWw6IHN0cikgLT4gdHVwbGVbbGlzdFtzdHJdLCBmbG9hdF06CiAgICAiIiJVbmlmb3JtIENvc3QgU2VhcmNoOiBmaW5kIHRoZSBsZWFzdC1jb3N0IHBhdGggaW4gYSB3ZWlnaHRlZCBncmFwaC4iIiIKICAgIHF1ZXVlOiBsaXN0W3R1cGxlW2Zsb2F0LCBzdHIsIGxpc3Rbc3RyXV1dID0gWygwLCBzdGFydCwgW3N0YXJ0XSldCiAgICBiZXN0X2Nvc3Q6IGRpY3Rbc3RyLCBmbG9hdF0gPSB7c3RhcnQ6IDB9CgogICAgd2hpbGUgcXVldWU6CiAgICAgICAgY29zdCwgbm9kZSwgcGF0aCA9IGhlYXBxLmhlYXBwb3AocXVldWUpCiAgICAgICAgaWYgbm9kZSA9PSBnb2FsOgogICAgICAgICAgICByZXR1cm4gcGF0aCwgY29zdAogICAgICAgIGlmIGNvc3QgPiBiZXN0X2Nvc3QuZ2V0KG5vZGUsIGZsb2F0KCJpbmYiKSk6CiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgIGZvciBuZWlnaGJvciwgZWRnZV9jb3N0IGluIGdyYXBoLmdldChub2RlLCBbXSk6CiAgICAgICAgICAgIG5ld19jb3N0ID0gY29zdCArIGVkZ2VfY29zdAogICAgICAgICAgICBpZiBuZXdfY29zdCA8IGJlc3RfY29zdC5nZXQobmVpZ2hib3IsIGZsb2F0KCJpbmYiKSk6CiAgICAgICAgICAgICAgICBiZXN0X2Nvc3RbbmVpZ2hib3JdID0gbmV3X2Nvc3QKICAgICAgICAgICAgICAgIGhlYXBxLmhlYXBwdXNoKHF1ZXVlLCAobmV3X2Nvc3QsIG5laWdoYm9yLCBwYXRoICsgW25laWdoYm9yXSkpCgogICAgcmV0dXJuIFtdLCBmbG9hdCgiaW5mIikKCgpkZWYgYmZzKGdyYXBoOiBXZWlnaHRlZEdyYXBoLCBzdGFydDogc3RyLCBnb2FsOiBzdHIpIC0+IGxpc3Rbc3RyXToKICAgICIiIkJyZWFkdGgtRmlyc3QgU2VhcmNoOiBmaW5kIGEgcGF0aCB3aXRoIHRoZSBmZXdlc3QgZWRnZXMuIiIiCiAgICBxdWV1ZSA9IGRlcXVlKFsoc3RhcnQsIFtzdGFydF0pXSkKICAgIHZpc2l0ZWQgPSB7c3RhcnR9CgogICAgd2hpbGUgcXVldWU6CiAgICAgICAgbm9kZSwgcGF0aCA9IHF1ZXVlLnBvcGxlZnQoKQogICAgICAgIGlmIG5vZGUgPT0gZ29hbDoKICAgICAgICAgICAgcmV0dXJuIHBhdGgKCiAgICAgICAgZm9yIG5laWdoYm9yLCBfIGluIGdyYXBoLmdldChub2RlLCBbXSk6CiAgICAgICAgICAgIGlmIG5laWdoYm9yIG5vdCBpbiB2aXNpdGVkOgogICAgICAgICAgICAgICAgdmlzaXRlZC5hZGQobmVpZ2hib3IpCiAgICAgICAgICAgICAgICBxdWV1ZS5hcHBlbmQoKG5laWdoYm9yLCBwYXRoICsgW25laWdoYm9yXSkpCgogICAgcmV0dXJuIFtdCgoKZGVmIGRmcyhncmFwaDogV2VpZ2h0ZWRHcmFwaCwgc3RhcnQ6IHN0ciwgZ29hbDogc3RyKSAtPiBsaXN0W3N0cl06CiAgICAiIiJEZXB0aC1GaXJzdCBTZWFyY2g6IGV4cGxvcmUgb25lIGJyYW5jaCBkZWVwbHkgYmVmb3JlIGFub3RoZXIuIiIiCiAgICBzdGFjayA9IFsoc3RhcnQsIFtzdGFydF0pXQogICAgdmlzaXRlZDogc2V0W3N0cl0gPSBzZXQoKQoKICAgIHdoaWxlIHN0YWNrOgogICAgICAgIG5vZGUsIHBhdGggPSBzdGFjay5wb3AoKQogICAgICAgIGlmIG5vZGUgaW4gdmlzaXRlZDoKICAgICAgICAgICAgY29udGludWUKICAgICAgICB2aXNpdGVkLmFkZChub2RlKQoKICAgICAgICBpZiBub2RlID09IGdvYWw6CiAgICAgICAgICAgIHJldHVybiBwYXRoCgogICAgICAgICMgUmV2ZXJzZSBrZWVwcyB0aGUgZXhhbXBsZSdzIGxlZnQtdG8tcmlnaHQgb3JkZXIgcHJlZGljdGFibGUuCiAgICAgICAgZm9yIG5laWdoYm9yLCBfIGluIHJldmVyc2VkKGdyYXBoLmdldChub2RlLCBbXSkpOgogICAgICAgICAgICBpZiBuZWlnaGJvciBub3QgaW4gdmlzaXRlZDoKICAgICAgICAgICAgICAgIHN0YWNrLmFwcGVuZCgobmVpZ2hib3IsIHBhdGggKyBbbmVpZ2hib3JdKSkKCiAgICByZXR1cm4gW10KCgpkZWYgaGV1cmlzdGljKG5vZGU6IHN0ciwgZ29hbDogc3RyKSAtPiBmbG9hdDoKICAgICIiIkFuIGV4YW1wbGUgaGV1cmlzdGljOiBlc3RpbWF0ZWQgcmVtYWluaW5nIGNvc3QuCgogICAgQSByZWFsIGhldXJpc3RpYyB1c2VzIHByb2JsZW0ga25vd2xlZGdlLCBzdWNoIGFzIHN0cmFpZ2h0LWxpbmUgZGlzdGFuY2UuCiAgICBIZXJlLCB0aGUgdmFsdWVzIGFyZSBoYW5kLXdyaXR0ZW4gb25seSB0byBrZWVwIHRoZSBleGFtcGxlIGVhc3kgdG8gcmVhZC4KICAgICIiIgogICAgZXN0aW1hdGVzID0gewogICAgICAgICJBIjogNywKICAgICAgICAiQiI6IDYsCiAgICAgICAgIkMiOiAyLAogICAgICAgICJEIjogMywKICAgICAgICAiRSI6IDEsCiAgICAgICAgIkYiOiAwLAogICAgfQogICAgcmV0dXJuIGVzdGltYXRlcy5nZXQobm9kZSwgMCkgaWYgbm9kZSAhPSBnb2FsIGVsc2UgMAoKCmRlZiBncmVlZHlfYmVzdF9maXJzdCgKICAgIGdyYXBoOiBXZWlnaHRlZEdyYXBoLCBzdGFydDogc3RyLCBnb2FsOiBzdHIKKSAtPiB0dXBsZVtsaXN0W3N0cl0sIGZsb2F0XToKICAgICIiIkdyZWVkeSBCZXN0LUZpcnN0IFNlYXJjaDogY2hvb3NlIHRoZSBzbWFsbGVzdCBoKG4pIGZpcnN0LiIiIgogICAgcXVldWU6IGxpc3RbdHVwbGVbZmxvYXQsIGZsb2F0LCBzdHIsIGxpc3Rbc3RyXV1dID0gWwogICAgICAgIChoZXVyaXN0aWMoc3RhcnQsIGdvYWwpLCAwLCBzdGFydCwgW3N0YXJ0XSkKICAgIF0KICAgIHZpc2l0ZWQ6IHNldFtzdHJdID0gc2V0KCkKCiAgICB3aGlsZSBxdWV1ZToKICAgICAgICBfLCBjb3N0LCBub2RlLCBwYXRoID0gaGVhcHEuaGVhcHBvcChxdWV1ZSkKICAgICAgICBpZiBub2RlIGluIHZpc2l0ZWQ6CiAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgdmlzaXRlZC5hZGQobm9kZSkKICAgICAgICBpZiBub2RlID09IGdvYWw6CiAgICAgICAgICAgIHJldHVybiBwYXRoLCBjb3N0CgogICAgICAgIGZvciBuZWlnaGJvciwgZWRnZV9jb3N0IGluIGdyYXBoLmdldChub2RlLCBbXSk6CiAgICAgICAgICAgIGlmIG5laWdoYm9yIG5vdCBpbiB2aXNpdGVkOgogICAgICAgICAgICAgICAgaGVhcHEuaGVhcHB1c2goCiAgICAgICAgICAgICAgICAgICAgcXVldWUsCiAgICAgICAgICAgICAgICAgICAgKAogICAgICAgICAgICAgICAgICAgICAgICBoZXVyaXN0aWMobmVpZ2hib3IsIGdvYWwpLAogICAgICAgICAgICAgICAgICAgICAgICBjb3N0ICsgZWRnZV9jb3N0LAogICAgICAgICAgICAgICAgICAgICAgICBuZWlnaGJvciwKICAgICAgICAgICAgICAgICAgICAgICAgcGF0aCArIFtuZWlnaGJvcl0sCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICkKCiAgICByZXR1cm4gW10sIGZsb2F0KCJpbmYiKQoKCmRlZiBhX3N0YXIoZ3JhcGg6IFdlaWdodGVkR3JhcGgsIHN0YXJ0OiBzdHIsIGdvYWw6IHN0cikgLT4gdHVwbGVbbGlzdFtzdHJdLCBmbG9hdF06CiAgICAiIiJBKjogY2hvb3NlIHRoZSBzbWFsbGVzdCBmKG4pID0gZyhuKSArIGgobikuIiIiCiAgICBxdWV1ZTogbGlzdFt0dXBsZVtmbG9hdCwgZmxvYXQsIHN0ciwgbGlzdFtzdHJdXV0gPSBbCiAgICAgICAgKGhldXJpc3RpYyhzdGFydCwgZ29hbCksIDAsIHN0YXJ0LCBbc3RhcnRdKQogICAgXQogICAgYmVzdF9jb3N0OiBkaWN0W3N0ciwgZmxvYXRdID0ge3N0YXJ0OiAwfQoKICAgIHdoaWxlIHF1ZXVlOgogICAgICAgIF8sIGNvc3QsIG5vZGUsIHBhdGggPSBoZWFwcS5oZWFwcG9wKHF1ZXVlKQogICAgICAgIGlmIG5vZGUgPT0gZ29hbDoKICAgICAgICAgICAgcmV0dXJuIHBhdGgsIGNvc3QKICAgICAgICBpZiBjb3N0ID4gYmVzdF9jb3N0LmdldChub2RlLCBmbG9hdCgiaW5mIikpOgogICAgICAgICAgICBjb250aW51ZQoKICAgICAgICBmb3IgbmVpZ2hib3IsIGVkZ2VfY29zdCBpbiBncmFwaC5nZXQobm9kZSwgW10pOgogICAgICAgICAgICBuZXdfY29zdCA9IGNvc3QgKyBlZGdlX2Nvc3QKICAgICAgICAgICAgaWYgbmV3X2Nvc3QgPCBiZXN0X2Nvc3QuZ2V0KG5laWdoYm9yLCBmbG9hdCgiaW5mIikpOgogICAgICAgICAgICAgICAgYmVzdF9jb3N0W25laWdoYm9yXSA9IG5ld19jb3N0CiAgICAgICAgICAgICAgICBlc3RpbWF0ZWRfdG90YWwgPSBuZXdfY29zdCArIGhldXJpc3RpYyhuZWlnaGJvciwgZ29hbCkKICAgICAgICAgICAgICAgIGhlYXBxLmhlYXBwdXNoKAogICAgICAgICAgICAgICAgICAgIHF1ZXVlLAogICAgICAgICAgICAgICAgICAgIChlc3RpbWF0ZWRfdG90YWwsIG5ld19jb3N0LCBuZWlnaGJvciwgcGF0aCArIFtuZWlnaGJvcl0pLAogICAgICAgICAgICAgICAgKQoKICAgIHJldHVybiBbXSwgZmxvYXQoImluZiIpCgoKZGVmIHNlYXJjaF9hbGdvcml0aG1zX2RlbW8oKSAtPiBOb25lOgogICAgIiIiUnVuIGFsbCBzZWFyY2ggYWxnb3JpdGhtcyBvbiB0aGUgc2FtZSBzbWFsbCBncmFwaC4iIiIKICAgIHByaW50KCJcbiIgKyAiPSIgKiA2MCkKICAgIHByaW50KCIyLiBVQ1MsIEJGUywgREZTLCBHUkVFRFkgQkVTVC1GSVJTVCwgQU5EIEEqIikKICAgIHByaW50KCI9IiAqIDYwKQoKICAgIGdyYXBoOiBXZWlnaHRlZEdyYXBoID0gewogICAgICAgICJBIjogWygiQiIsIDIpLCAoIkMiLCA1KV0sCiAgICAgICAgIkIiOiBbKCJEIiwgNCksICgiRSIsIDEwKV0sCiAgICAgICAgIkMiOiBbKCJFIiwgMSldLAogICAgICAgICJEIjogWygiRiIsIDMpXSwKICAgICAgICAiRSI6IFsoIkYiLCAyKV0sCiAgICAgICAgIkYiOiBbXSwKICAgIH0KCiAgICBwcmludCgiXG5HcmFwaCBlZGdlczoiKQogICAgZm9yIG5vZGUsIGVkZ2VzIGluIGdyYXBoLml0ZW1zKCk6CiAgICAgICAgcHJpbnQoZiJ7bm9kZX0gLT4ge2VkZ2VzfSIpCgogICAgcGF0aCwgY29zdCA9IHVjcyhncmFwaCwgIkEiLCAiRiIpCiAgICBwcmludCgiXG5VQ1M6IiwgcGF0aCwgImNvc3QgPSIsIGNvc3QpCiAgICBwcmludCgiQkZTOiIsIGJmcyhncmFwaCwgIkEiLCAiRiIpLCAiKGZld2VzdCBlZGdlcywgaWdub3JlcyBjb3N0cykiKQogICAgcHJpbnQoIkRGUzoiLCBkZnMoZ3JhcGgsICJBIiwgIkYiKSwgIihmaXJzdCBkZWVwIHBhdGggZm91bmQpIikKCiAgICBncmVlZHlfcGF0aCwgZ3JlZWR5X2Nvc3QgPSBncmVlZHlfYmVzdF9maXJzdChncmFwaCwgIkEiLCAiRiIpCiAgICBhX3N0YXJfcGF0aCwgYV9zdGFyX2Nvc3QgPSBhX3N0YXIoZ3JhcGgsICJBIiwgIkYiKQogICAgcHJpbnQoIkdyZWVkeSBCZXN0LUZpcnN0OiIsIGdyZWVkeV9wYXRoLCAiY29zdCA9IiwgZ3JlZWR5X2Nvc3QpCiAgICBwcmludCgiQSo6IiwgYV9zdGFyX3BhdGgsICJjb3N0ID0iLCBhX3N0YXJfY29zdCkKCiAgICBwcmludCgiXG5Db21wYXJpc29uOiIpCiAgICBwcmludCgiLSBVQ1MgdXNlcyBnKG4pLCB0aGUgY29zdCBhbHJlYWR5IHRyYXZlbGxlZC4gSXQgZmluZHMgYW4gb3B0aW1hbCBwYXRoLiIpCiAgICBwcmludCgiLSBCRlMgdXNlcyBkZXB0aC4gSXQgaXMgb3B0aW1hbCBvbmx5IHdoZW4gZXZlcnkgZWRnZSBoYXMgZXF1YWwgY29zdC4iKQogICAgcHJpbnQoIi0gREZTIHVzZXMgZGVwdGgtZmlyc3QgZXhwbG9yYXRpb24uIEl0IG1heSBmaW5kIGEgbm9uLW9wdGltYWwgcGF0aC4iKQogICAgcHJpbnQoIi0gR3JlZWR5IHVzZXMgaChuKSwgdGhlIGVzdGltYXRlZCBkaXN0YW5jZSB0byB0aGUgZ29hbC4gSXQgY2FuIGJlIGZhc3QiKQogICAgcHJpbnQoIiAgYnV0IGlzIG5vdCBhbHdheXMgb3B0aW1hbC4iKQogICAgcHJpbnQoIi0gQSogdXNlcyBnKG4pICsgaChuKS4gV2l0aCBhIGdvb2QgYWRtaXNzaWJsZSBoZXVyaXN0aWMsIGl0IGlzIGJvdGgiKQogICAgcHJpbnQoIiAgZWZmaWNpZW50IGFuZCBvcHRpbWFsLiIpCgoKZGVmIG1haW4oKSAtPiBOb25lOgogICAgYmFzaWNfcHl0aG9uX2RlbW8oKQogICAgc2VhcmNoX2FsZ29yaXRobXNfZGVtbygpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIG1haW4oKQo=