import random
import time

# --------------------------------------------------
# Function creates a list filled with randomly chosen values
printlim = 20
def generateList( listSize, maxval  ):
    random.seed(12321)
    t1 = time.time()
    aList = [random.randint(0, maxval) for x in range(listSize)]
    t2 = time.time()
    #print( "time generating", t2-t1  )
    if listSize < printlim:  print(aList)
    return aList

# to be filled later
numQueries, lowQueryValue, hiQueryValue = 0, 0, 0 

# ----------------------------------------------------------------
#   SEARCH A LIST
# ----------------------------------------------------------------

# Demonstrates repeated search for various values in a list.
#   numQueries   --  number of searches (queries) for a value
#   minQuery, maxQuery -- range of queries
# Returns the number of hits (found), number of misses (not found)
# and the time spent on the whole process.

def searchUNsorted_by_index( aList ):
    countHits, countMisses = 0, 0
    t1 = time.time()
    for i in range( numQueries ):
        query = random.randint( lowQueryValue, hiQueryValue )
        try:
            index = aList.index( query )
            countHits += 1
        except ValueError:
            countMisses += 1
    t2 = time.time()
    return countHits, countMisses, t2 - t1, "search by index function     "

def searchUNsorted_by_in( aList ):
    countHits, countMisses = 0, 0
    t1 = time.time()
    for i in range( numQueries ):
        query = random.randint( lowQueryValue, hiQueryValue )
        if query in aList:
            countHits += 1
        else:
            countMisses += 1
    t2 = time.time()
    return countHits, countMisses, t2 - t1, "search by in keyword         "
  
def searchAny_by_set( aList ):
    countHits, countMisses = 0, 0
    t1 = time.time()
    setOfValues = set ( aList )
    for i in range( numQueries ):
        query = random.randint( lowQueryValue, hiQueryValue )
        if query in setOfValues:
            countHits += 1
        else:
            countMisses += 1
    t2 = time.time()
    return countHits, countMisses, t2 - t1, "search by set data structure " 
  
  
# ----------------------------------------------------------------
#    SEARCH SORTED LIST WITH     BINARY SEARCH
# ----------------------------------------------------------------

#  --------------------------------------------------
# Binary search function
# It is widely used for its efficiency.
def binarySearch( aList, value ):
    low = 0; high = len(aList)-1
    while low < high:    # while segment length > 1
        mid = (low + high) // 2
        if aList[mid] < value: low   = mid+1
        else:                  high  = mid
    if aList[low] == value: return low  # found or
    return -1 # not found


# search in an ordered list -- *binary search*
def searchSorted_by_binary( aList ):
    countHits, countMisses = 0, 0
    t1 = time.time()
    for i in range( numQueries ):
        query = random.randint( lowQueryValue, hiQueryValue )
        if binarySearch( aList, query ) > -1 :
            countHits += 1
        else:
            countMisses += 1
    t2 = time.time()
    return countHits, countMisses, t2 - t1, "Binary Search                "


def evaluate( searchMethod, aList ):
    hitsN, missesN, methodTime, methodName = searchMethod( aList )

    print( "Method:", methodName, "    execution time: ",  methodTime  ) 

# ----------------------------------------------------------------------------------------------
#                              M  A  I  N
# ----------------------------------------------------------------------------------------------

aList = [2,4,6,8,10,12,14,16,18,20]
aList = generateList( 100000,  30 )
numQueries, lowQueryValue, hiQueryValue = 10000, 5, 15

print( "N Queries =", numQueries,  "list size ",   len(aList),   "   Range = [", lowQueryValue, hiQueryValue, "]" )

# process and evaluate UNSORTED list
evaluate( searchUNsorted_by_index, aList )
evaluate( searchUNsorted_by_in   , aList )
evaluate( searchAny_by_set,        aList )

print()

# process and evaluate SORTED list
aList.sort()
evaluate( searchUNsorted_by_index, aList )
evaluate( searchUNsorted_by_in   , aList )
evaluate( searchAny_by_set,        aList )
evaluate( searchSorted_by_binary,  aList )


