-1

I have a series of functions that end up giving a list, with the first item containing a number, derived from the dictionaries, and the second and third items are dictionaries.

These dictionaries have been previously randomly generated.

The function I am using generates a given number of these dictionaries, trying to get the highest number possible as the first item. (It's designed to optimise dice rolls).

This all works fine, and I can print the value of the highest first item from all iterations. However, when I try and print the two dictionaries associated with this first number (bearing in mind they're all in a list together), it just seemingly randomly generates the two other dictionaries.

def repeat(type, times):
    best = 0
    for i in range(0, times):
        x = rollForCharacter(type)
        if x[0] > best:
            print("BEST:", x)
            best = x[0]

    print("The highest average success is", best)

    return best

This works great. The last thing shown is:

BEST: (3.58, [{'strength': 4, 'intelligence': 1, 'charisma': 1, 'stamina': 4, 'willpower': 2, 'dexterity': 2, 'wits': 5, 'luck': 2}, {'agility': 1, 'brawl': 2, 'investigation': 3, 'larceny': 0, 'melee': 1, 'survival': 0, 'alchemy': 3, 'archery': 0, 'crafting': 0, 'drive': 1, 'magic': 0, 'medicine': 0, 'commercial': 0, 'esteem': 5, 'instruction': 2, 'intimidation': 2, 'persuasion': 0, 'seduction': 0}]) The highest average success is 3.58

But if I try something to store the list which gave this number:

def repeat(type, times):
    best = 0
    bestChar = []
    for i in range(0, times):
        x = rollForCharacter(type)
        if x[0] > best:
            print("BEST:", x)
            best = x[0]
            bestChar = x

    print("The highest average success is", best)
    print("Therefore the best character is", bestChar)

    return best, bestChar

I get this as the last result, which is fine:

BEST: (4.15, [{'strength': 2, 'intelligence': 3, 'charisma': 4, 'stamina': 4, 'willpower': 1, 'dexterity': 2, 'wits': 4, 'luck': 1}, {'agility': 1, 'brawl': 0, 'investigation': 5, 'larceny': 0, 'melee': 0, 'survival': 0, 'alchemy': 7, 'archery': 0, 'crafting': 0, 'drive': 0, 'magic': 0, 'medicine': 0, 'commercial': 1, 'esteem': 0, 'instruction': 3, 'intimidation': 0, 'persuasion': 0, 'seduction': 0}]) The highest average success is 4.15

but the last line is

Therefore the best character is (4.15, [{'strength': 1, 'intelligence': 3, 'charisma': 4, 'stamina': 4, 'willpower': 1, 'dexterity': 2, 'wits': 2, 'luck': 3}, {'agility': 1, 'brawl': 0, 'investigation': 1, 'larceny': 4, 'melee': 2, 'survival': 0, 'alchemy': 2, 'archery': 4, 'crafting': 0, 'drive': 0, 'magic': 0, 'medicine': 0, 'commercial': 1, 'esteem': 0, 'instruction': 0, 'intimidation': 2, 'persuasion': 1, 'seduction': 0}])

As you can see this doesn't match with what I want, and what is printed literally right above it.

Through a little bit of checking, I realised what it gives out as the "Best Character" is just the last one generated, which is not the best, just the most recent. However, it isn't that simple, because the first element IS the highest result that was recorded, just not from the character in the rest of the list. This is really confusing because it means the list is somehow being edited but at no point can I see where that would happen.

Am I doing something stupid whereby the character is randomly generated every time? I wouldn't think so since x[0] gives the correct result and is stored fine, so what changes when it's the whole list?

From the function rollForCharacter() it returns rollResult, character which is just the number and then the two dictionaries.

I would greatly appreciate it if anyone could figure out and explain where I'm going wrong and why it can print the correct answer to the console yet not store it correctly a line below!

EDIT:

Dictionary 1 Code:

attributes = {}


def assignRow(row, p): # p is the number of points you have to assign to each row
    rowValues = {}
    for i in range(0, len(row)-1):
        val = randint(0, p)
        rowValues[row[i]] = val + 1
        p -= val
    rowValues[row[-1]] = p + 1
    return attributes.update(rowValues)


def getPoints():
    points = [7, 5, 3]
    shuffle(points)
    row1 = ['strength', 'intelligence', 'charisma']
    row2 = ['stamina', 'willpower']
    row3 = ['dexterity', 'wits', 'luck']
    for i in range(0, len(points)):
        row = eval("row" + str(i+1))
        assignRow(row, points[i])

Dictionary 2 Code:

skills = {}


def assignRow(row, p):  # p is the number of points you have to assign to each row
    rowValues = {}
    for i in range(0, len(row) - 1):
        val = randint(0, p)
        rowValues[row[i]] = val
        p -= val
    rowValues[row[-1]] = p
    return skills.update(rowValues)


def getPoints():
    points = [11, 7, 4]
    shuffle(points)
    row1 = ['agility', 'brawl', 'investigation', 'larceny', 'melee', 'survival']
    row2 = ['alchemy', 'archery', 'crafting', 'drive', 'magic', 'medicine']
    row3 = ['commercial', 'esteem', 'instruction', 'intimidation', 'persuasion', 'seduction']
    for i in range(0, len(points)):
        row = eval("row" + str(i + 1))
        assignRow(row, points[i])
Freddie R
  • 377
  • 5
  • 15

1 Answers1

1

It does look like the dictionary is being re-generated, which could easily happen if the function rollForCharacter returns either a generator or alternatively is overwriting a global variable which is being overwritten by a subsequent cycle of the loop.

A simple-but-hacky way to solve the problem would be to take a deep copy of the dictionary at the time of storing, so that you're sure you're keeping the values at that point:

def repeat(type, times):
    best = 0
    bestChar = []
    for i in range(0, times):
        x = rollForCharacter(type)
        if x[0] > best:
            print("BEST:", x)
            best = x[0]
            # Create a brand new tuple, containing a copy of the current dict
            bestChar = (x[0], x[1].copy())

The correct answer would be however to pass a unique dictionary variable that is not affected by later code.

See this SO answer with a bit more context about how passing a reference to a dictionary can be risky as it's still a mutable object.

Phil Sheard
  • 2,102
  • 1
  • 17
  • 38
  • Thank you for the link, that really helped. It turns out I need deepcopy(), but that works great thank you! – Freddie R Jul 28 '17 at 09:00