# ( Part of BE5B33PGE course, FEL CTU, 2024 )
#
#    MAXIMUM SUM OF A CONTIGUOUS SUBSEQUENCE
#
# This a notorious problem in programming because
# it is simple and it illustrates in a dramatic way
# the relative efficiency of a cubic, quadratic
# and linear solutions.
#
# The task is easy to formulate:
# Given a sequence of N numbers find a contiguous
# subsequence which sum of elements is maximum possible.
#
# When the sequence contains only non-negative numbers
# the solution to the problem is obviously the whole original sequence.
# When the sequence contains some negative values
# the solution is not obvious and a suitable algorithm
# has to be applied.


import random
import time
import sys

def printf(format, *args):
    sys.stdout.write(format % args)

# ----------------------------------------------------------------------------------
# Cubic, quadratic and linear solution of the problem
# Suppose the sequence is stored in a usual list [...]

# 3.
# Naive brute force solution
#   Cubic complexity
# Compute the sums of all countiguous subsequences,
# compare them and return the maximum one.

def maxSumCubic( aList ):
    ##  start value
    mmax = aList[0]
    ## search
    for i1 in range( 0, len(aList)-1 ):
        for i2 in range( i1, len(aList)):
            iSum = sum( aList[i1:i2+1] )
            if iSum > mmax:
                mmax = iSum
                besti1, besti2 = i1, i2+1
    return  besti1, besti2, mmax

# 2.
# Less naive  solution
#   Quadratic complexity
# For each possible start X of a countiguous subsequence
# set sum to 0 and then scan the sequence from X to the right, and
# updating the sum by each subsequent element.
# Return the maximum sum found.

def maxSumQuadratic( aList ):
    ##  start value
    mmax = aList[0]
    ## search
    for i1 in range( 0, len(aList)-1 ):
        iSum = 0
        for i2 in range( i1, len(aList)):
            iSum  += aList[i2]
            if iSum > mmax:
                mmax = iSum
                besti1, besti2 = i1, i2+1
    return  besti1, besti2, mmax


# 1.
# Fast (standard)  solution
#   Linear complexity
# Set the sum to 0. Set result to 0.
# Scan the sequence once, update the sum by the current element.
# If the sum is bigger than the result update the result.
# Whenever the sum becomes negative, reset it to zero.
# Return the result -- maximum sum found.

def maxSumLinear( aList ):
    ##  start value and best subsequence bounds:
    mmax = aList[0]; besti1 = 0; besti2 = 0
    ## search
    sum = 0; 
    starti = 0  # start of current subsequence
    for i1 in range( 0, len(aList) ):
        sum += aList[i1]
        if sum < 0:
            sum = 0
            starti = i1+1 # do not consider current index
        elif sum > mmax: # register best 
            mmax = sum
            besti1, besti2 = starti, i1+1
    return  besti1, besti2, mmax

# ----------------------------------------------------------------------------------
# Manage sum function calls, aggregate more calls into a single method

def runMaxSum( speed, aList ):
    t_start = time.time()
    if   speed == 1:
        besti, bestj, mmax = maxSumLinear( aList )
    elif speed == 2:
        besti, bestj, mmax = maxSumQuadratic( aList )
    elif speed == 3:
        besti, bestj, mmax = maxSumCubic( aList )
    t_end = time.time()
    allTime = t_end - t_start
    return besti, bestj, mmax, allTime

def runAllSpeedsOnList(  aList ):
    besti, bestj, mmax, runTimeLinear = runMaxSum( 1, aList )
    print( "Sol.:", aList[besti:bestj], " sum:", mmax )
    besti, bestj, mmax, runTimeQuadratic = runMaxSum( 2, aList )
    print( "Sol.:", aList[besti:bestj], " sum:", mmax )
    besti, bestj, mmax, runTimeCubic = runMaxSum( 3, aList )
    print( "Sol.:", aList[besti:bestj], " sum:", mmax )
    print("Sequence length =", len(aList))
    print("Times   linear, quadratic, cubic")
    printf(" %12.2f %6.2f    %6.2f\n\n", runTimeLinear, runTimeQuadratic, runTimeCubic )

def runLinQuadOnList(  aList ):
    besti, bestj, mmax, runTimeLinear = runMaxSum( 1, aList )
    besti, bestj, mmax, runTimeQuadratic = runMaxSum( 2, aList )
    print("Sequence length =", len(aList))
    print("Times   linear, quadratic ")
    printf(" %12.2f %6.2f \n\n", runTimeLinear, runTimeQuadratic )

def runLinearOnList(  aList ):
    besti, bestj, mmax, runTimeLinear = runMaxSum( 1, aList )
    print("Sequence length =", len(aList))
    print("Time   linear ")
    printf(" %12.2f \n\n", runTimeLinear )


# ----------------------------------------------------------------------------------
# Crerate example data and run maxSum functions on the data

random.seed( 2009 )      # use e.g. 2004 or 2009 for demo list15

list10 = [random.randint(-9, 9) for k in range(10) ]
list15 = [random.randint(-9, 9) for k in range(15) ]
list100 = [random.randint(-9, 9) for k in range(100) ]
list200 = [random.randint(-9, 9) for k in range(200) ]
list400 = [random.randint(-9, 9) for k in range(400) ]
list600 = [random.randint(-9, 9) for k in range(600) ]
list1000 = [random.randint(-9, 9) for k in range(1000) ]
list1050 = [random.randint(-9, 9) for k in range(1050) ]
list1100 = [random.randint(-9, 9) for k in range(1100) ]
list10000 = [random.randint(-9, 9) for k in range(10000) ]
list100000 = [random.randint(-9, 9) for k in range(100000) ]
#list1000000 = [random.randint(-9, 9) for k in range(1000000) ]
#list10000000 = [random.randint(-9, 9) for k in range(10000000) ]

print( "--- Maximum subsequence simple demo ---" )
print( list15 )
runAllSpeedsOnList( list15 )
print( "-------------------" )
exit()                                                             s

'''
print( list10 )
runAllSpeedsOnList( list10 )

print( list15 )
runAllSpeedsOnList( list15 )
'''


runAllSpeedsOnList( list100 )
runAllSpeedsOnList( list200 )
runAllSpeedsOnList( list400 )
runAllSpeedsOnList( list600 )
runAllSpeedsOnList( list1000 )
runAllSpeedsOnList( list1100 )
runLinQuadOnList( list10000 )
runLinearOnList( list100000 )


'''
n = 10
aa = []
aa.extend(range(0, 3 * n, 3))
print(aa)
'''
