1

Alright, I wrote the following code to find the union of any number of probabilities (wonderful article on the topic here: https://www.thoughtco.com/probability-union-of-three-sets-more-3126263):

#Finds all Intersections
def intersection_finder1(poss, intersection_number):
    #Make Lists of descending possibilities intersection_number times, Works for 2, not 3 
    sub_posses = []
    use = poss
    sub_posses.append(use)
    for i in range(intersection_number - 1):
        #print use[(i+1):], intersection_number
        sub_posses.append(use[(i+1):])
    return sub_posses

def sub_poss_modifier(sub_posses):
    for x in range(len(sub_posses)):
        del sub_posses[x][0]
    return sub_posses

def intersection_finder2(sub_posses, intersection_number, ongoing_sum=0):
    #Iterate over lists for list_item_number_1
    #Increment lists by one 
    #Repeat for last_list length times, 
    #Commented out below are debugging things 
    multi = 1 
    for y in range(len(sub_posses[-1])):
        for i in range(len(sub_posses)):    
            multi = multi * sub_posses[i][y]
            #print multi, sub_posses[i][y], intersection_number
        #print "-----------RESTART------------"
        ongoing_sum += multi
        multi = 1
    if len(sub_posses[-1]) > 1:
        new_lt = sub_poss_modifier(sub_posses)
        return intersection_finder2(new_lt, intersection_number, ongoing_sum)
    return ongoing_sum

def combiner(poss):
    #Sums Possbilities
    total = sum(poss) 
    ongoing_total = total 
    #Adds/Subtracts Combinations 
    for i in range(2, len(poss) + 1):
        #Somehow, poss is changing. I have no idea how. 
        #Say poss is [1/2.0, 1/2.0, 1/2.0]. If you put that in, the function works. If you put poss in, it doesn't 
        print poss
        sub_poss = intersection_finder1(poss, i)
        aors = intersection_finder2(sub_poss, i)
        #print aors, i 
        if i % 2 == 0:
            ongoing_total -= aors
        else:
            ongoing_total += aors
    #Returns total Possbility 
    return ongoing_total

print combiner([1/2.0, 1/2.0, 1/2.0])

It works but only if I make a specific change in which the value of the variable poss is inserted in place of itself. For Example (the only change is in the 9th line of combiner):

#Finds all Intersections
def intersection_finder1(poss, intersection_number):
    #Make Lists of descending possibilities intersection_number times, Works for 2, not 3 
    sub_posses = []
    use = poss
    sub_posses.append(use)
    for i in range(intersection_number - 1):
        #print use[(i+1):], intersection_number
        sub_posses.append(use[(i+1):])
    return sub_posses

def sub_poss_modifier(sub_posses):
    for x in range(len(sub_posses)):
        del sub_posses[x][0]
    return sub_posses

def intersection_finder2(sub_posses, intersection_number, ongoing_sum=0):
    #Iterate over lists for list_item_number_1
    #Increment lists by one 
    #Repeat for last_list length times, 
    #Commented out below are debugging things 
    multi = 1 
    for y in range(len(sub_posses[-1])):
        for i in range(len(sub_posses)):    
            multi = multi * sub_posses[i][y]
            #print multi, sub_posses[i][y], intersection_number
        #print "-----------RESTART------------"
        ongoing_sum += multi
        multi = 1
    if len(sub_posses[-1]) > 1:
        new_lt = sub_poss_modifier(sub_posses)
        return intersection_finder2(new_lt, intersection_number, ongoing_sum)
    return ongoing_sum

def combiner(poss):
    #Sums Possbilities
    total = sum(poss) 
    ongoing_total = total 
    #Adds/Subtracts Combinations 
    for i in range(2, len(poss) + 1):
        #Somehow, poss is changing. I have no idea how. 
        #Say poss is [1/2.0, 1/2.0, 1/2.0]. If you put that in, the function works. If you put poss in, it doesn't 
        #print poss
        sub_poss = intersection_finder1([1/2.0, 1/2.0, 1/2.0], i)
        aors = intersection_finder2(sub_poss, i)
        #print aors, i 
        if i % 2 == 0:
            ongoing_total -= aors
        else:
            ongoing_total += aors
    #Returns total Possbility 
    return ongoing_total

print combiner([1/2.0, 1/2.0, 1/2.0])

By doing a little debugging, I found that the variable poss changes throughout each iteration of the for loop -- thus yielding an incorrect answer. Furthermore, it only changes in code block #1; in code block #2, poss stays the same. So far, I haven't been able to find any instance where I redefine or alter poss in any function. Also, even if I did alter poss somewhere, the only difference between code block #1 and #2 is the list in the ninth line of the function combiner. Yet, block #2 yields the correct answer, while block #1 doesn't.

The Terminal Output from block #1 (printing poss): [0.5, 0.5, 0.5] [0.5, 0.5] 0.75

The Terminal Output from block #2 (printing poss): [0.5, 0.5, 0.5] [0.5, 0.5, 0.5] 0.875

So far, to prevent poss from changing, while maintaining some level of general use, I've tried to redefine it and rename it. What can I do to stop poss from changing, while making it calculate different probabilities as simple as changing a variable?

By the way, I fairly new to programming, so any advice to make my code better or myself a better programmer as a whole would be greatly appreciated.

Owen Winzeler
  • 43
  • 1
  • 5

2 Answers2

0

"So far, I haven't been able to find any instance where I redefine or alter poss in any function."

You alter the objects referred to by your function parameter posse quite clearly. You essentially pass the result of intersection_finder1 to intersection_finder2 (apt names...) In intersection_finder1, you append posse to sub_posses. In other words, in the partitions of the set, you use the object itself to represent the improper subset.

def intersection_finder1(poss, intersection_number):
    #Make Lists of descending possibilities intersection_number times, Works for 2, not 3 
    sub_posses = []
    use = poss # NOT MAKING A COPY

This says the object referred to by posse is now also referred to by use

    sub_posses.append(use) # now sub_posses includes posse
    for i in range(intersection_number - 1):
        #print use[(i+1):], intersection_number
        sub_posses.append(use[(i+1):])
    return sub_posses

Now, in intersection_finder2 you call sub_poss_modifier on sub_posses:

def sub_poss_modifier(sub_posses):
    for x in range(len(sub_posses)):
        del sub_posses[x][0]
    return sub_posses

Where you clearly modify sub_posses[0], which will be posse

The solution? Make copies. You an use the built-in list method posse.copy(), or the well-understood python copy-idiom, poss[:].

def intersection_finder1(poss, intersection_number):
    #Make Lists of descending possibilities intersection_number times, Works for 2, not 3 
    sub_posses = []
    use = poss.copy()

And everywhere else where you don't want to share objects, but want a list of the same values...

Note

To get a better grip of how Python variables and values work, I suggest reading all of Ned Batchelder's Facts and myths about Python names and values.

Also you should know that these operations produce shallow copies. If you have more complicated data-structures than a list of immutable objects, say, a list of lists, or a list of dicts, you may need a deep-copy instead. You should know the difference.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
0

When you are passing a list, it is passed as an object, not as a copy, and any assignment will refer to the object as reference.

You are appending poss to sub_posses in intersection_finder1(). This appends poss through its reference, not a copy, even if assigned to use.

In sub_poss_modifier() you delete some elements of sub_posses. This actually deletes the elements of poss too.

Solution is to make a copy when appending to sub_posses in intersection_finder1():

sub_posses.append(use[:])
Martin
  • 59
  • 4