import random
import time
import sys

# --------------------------------------------------------------------------------
#  INVESTIGATION OF TUPLES OF VALUES IN A SINGLE LIST

# Surprisingly enough, investigation of item tuples in a plain list
# is faster when it is done by naive approach using only nested loops.
# It is slower when it is done using combinations() method
# in module <itertools>.
# This effect occurs because combinations() method
# physically generates all tuples and stores them in
# a separate list.
# When each possible tuple is investigated only once
# it is usually not worth to allocate memory for the tuple
# and store it there. With increasing m=number of stored tuples
# memory management takes more and more time.



def statistics():

    # show the number of specific tuples in lists of various lengths
    print( "Processing list with N items: ")

    for N in range( 100, 1200, 200 ):
        print ( "  N =", N, "   pairs to investigate:",  (N*(N-1)) // 2)
    print()

    for N in range( 100, 1200, 200 ):
        print ( "  N =", N, "   triples to investigate:",  (N*(N-1)*(N-2)) // 6 )
    print()

    for N in range( 100, 1200, 200 ):
        print ( "  N =", N, "   quadruples to investigate:",  (N*(N-1)*(N-2)*(N-3)) // 24 )
    print()

    for N in range( 10, 220, 30 ):
        print ( "  N =", N, "   quintuples to investigate:",  (N*(N-1)*(N-2)*(N-3)*(N-4)) // 120 )
    print()

statistics()
# exit()

# --------------------------------------------------------------------------------
#    F I N D    T U P L E S   W I T H   S P E C I F I C  P R O P E R T I E S

from itertools import combinations

def pairsWithSum( a, Sum ):
    if  len(a) < 16:
        print( "array:", a )
    num = 0
    t1 = time.time()
    for i in range( 0, len(a)-1 ):
        for j in range( i+1, len(a) ):
            if  a[i] + a[j] == Sum:
                num += 1
                if  len( a ) < 16:
                    print( "%d.  %d+%d = %d\n" %  (num, a[i], a[j],  Sum) )
    # print( a )
    t2 = time.time()
    print( "Found %d pairs with sum = %d.\n" % (num, Sum) )
    print( " now for native python method:" )
    t3 = time.time()
    print( 'Found',  len( [x for x in combinations( a, 2) if sum(x) == Sum] ) )
    t4 = time.time()
    print( "times: Loops versus library", t2-t1, t4-t3 )
    print( "-----------------" )



def triplesWithSum( a, Sum ):
    if  len(a) < 16:
        print( "array:", a )
    num = 0
    t1 = time.time()
    for i in range( 0, len(a)-2 ):
        for j in range( i+1, len(a)-1 ):
            for k in range( j+1, len(a) ):
                if a[i] + a[j] + a[k] == Sum:
                    num += 1
                    #print( "%d.  %d+%d+%d+%d = %d\n" %  (num, a[i], a[j], a[k], a[m], Sum) )
    print( "Found %d triples with sum = %d.\n" % (num, Sum) )
    t2 = time.time()
    print( " now for native python method:" )
    t3 = time.time()
    print( 'Found',  len( [x for x in combinations( a, 3) if sum(x) == Sum] ) )
    t4 = time.time()
    print( "times: Loops versus library", t2-t1, t4-t3 )
    print( "-----------------" )



def quadruplesWithSum( a, Sum ):
    if  len(a) < 16:
        print( "array:", a )
    num = 0
    t1 = time.time()
    for i in range( 0, len(a)-3 ):
        for j in range( i+1, len(a)-2 ):
            for k in range( j+1, len(a)-1 ):
                for m in range( k+1, len(a) ):
                    if  a[i] + a[j] + a[k] + a[m] == Sum:
                        num += 1
                        #print( "%d.  %d+%d+%d+%d = %d\n" %  (num, a[i], a[j], a[k], a[m], Sum) )
    print( "Found %d quadruples with sum = %d.\n" % (num, Sum) )
    t2 = time.time()
    print( " now for native python method:" )
    t3 = time.time()
    print( 'Found',  len( [x for x in combinations( a, 4) if sum(x) == Sum] ) )
    t4 = time.time()
    print( "times: Loops versus library", t2-t1, t4-t3 )
    print( "-----------------" )


def quintuplesWithSum( a, Sum ):
    if  len(a) < 16:
        print( "array:", a )
    num = 0
    t1 = time.time()
    for i in range( 0, len(a)-4 ):
        for j in range( i+1, len(a)-3 ):
            for k in range( j+1, len(a)-2 ):
                for m in range( k+1, len(a)-1 ):
                    for n in range( m+1, len(a) ):
                        if  a[i] + a[j] + a[k] + a[m] + a[n] == Sum:
                            num += 1
                            #print( "%d.  %d+%d+%d+%d+%d = %d\n" % (num, a[i], a[j], a[k], a[m], a[n], Sum) )
    print( "Found %d quintuples with sum = %d.\n" % (num, Sum) )
    t2 = time.time()
    print( " now for native python method:" )
    t3 = time.time()
    print( 'Found', len( [x for x in combinations( a, 5) if sum(x) == Sum] ) )
    t4 = time.time()
    print( "times: Loops versus library", t2-t1, t4-t3 )
    print( "-----------------" )




# --------------------------------------------------------------------------------
#    A R E A   F O R   E X P E R I M E N T S
# --------------------------------------------------------------------------------
# modify the size of arrays and check how long does it take to process the array
# - to check all tuples -
#  depending on the size of the array and the complexity of the task
# - number of nested loops

random.seed( 23239971 )
if 1:
    arrSize = 10000
    print( "arr size =", arrSize )
    array = random.choices( range(1, 20), k = arrSize )
    #array = [2,1,4,3,7,5,6,9,8]
    pairsWithSum( array, 21 )

if 1:
    arrSize = 800
    print( "arr size =", arrSize )
    array = random.choices( range(1, 20), k = arrSize )
    triplesWithSum( array, 31 )

if 1:
    arrSize = 100
    print( "arr size =", arrSize )
    array = random.choices( range(1, 20), k = arrSize )
    quadruplesWithSum( array, 41 )

if 1:
    arrSize = 100
    print( "arr size =", arrSize )
    array = random.choices( range(1, 20), k = arrSize )
    quintuplesWithSum( array, 91 )


#statistics()


