Question

Good day,

First of all, I've seen many posts on this site that deal with similar problems, yet none of them offered a working solution. I'd highly appreciate if you'd take the time to at look my question before referring to another post.

I've been spending a few hours trying to figure out a simple way to replace two items in dictionary in a way that would take the items from two randomly selected pair of keys and switch between the two. such as:

dictionary1 = {'a':'A', 'b':'B', 'c':'C'}
dictionary2 = {'a':'B', 'b':'A', 'c':'C'}

Now, I understand that dictionaries don't have indexes, which makes the process a bit tricky. So I've built this function:

def get_neighbor (dictionary1 ):
    dictionary1 = dictionary1 
    def keys_with_value(self, value):
        return [k for k, v in list(self.items()) if v == value]
    copy = {}
    copy = dictionary1 
    letter1 = random.choice(list(copy))
    letter2 = random.choice(list(copy))

    key1 =  (keys_with_value(copy,letter1))[0]
    key2 =  (keys_with_value(copy,letter2))[0]
    copy[key1] = letter2
    copy[key2] = letter1
    return copy

As you can see, the function takes dictionary1 and creates a copy. Then the function reassigns the keys and values of 2 randomly chosen letters.

Yet, when I'm trying to look into the function it appears that the end result effects BOTH the original (dictionary1) and the copy (dictionary2).

It seems as if both dictionaries are stored in the same place. But that's impossible, right?

I would highly appreciate any help/suggestion you guys (and girls) might provide!

Was it helpful?

Solution

It seems as if both dictionaries are stored in the same place. But that's impossible, right?

Actually, that's exactly what's happening. Your line:

copy = dictionary1

makes copy another name for the object already named dictionary1. It does not create a new dictionary containing the contents of dictionary1.

Python variables are names for objects. Setting one variable equal to another assigns the name to the object, it does not copy the object. There are various ways to do that - for one way to do a shallow copy, you can use:

copy.update(dictionary1)

Or, as Red Alert noted in the comments above, copy = dictionary1.copy(). That's better than declaring a new empty dict and using update.

Or, for more safety, use the copy.deepcopy function. With the example you've shown here there would be no difference, but if your dictionary had lists or other mutable objects as values the shallow copy would copy references to the objects rather than new objects.

Some notes on mutable vs. immutable objects follow, based on the comments thread:

It is common for implementations of Python (including the only one I can talk about with accuracy, the common C implementation) to reuse objects for some immutable objects. For example, the following code in C Python will result in two references to the same object:

x = 7
y = 7

Because 7 is immutable, it is safe to do this. You can subsequently rebind x to a different object, eg:

x = 9

This does not affect the shared object for 7, and y will not change. This behavior is not special to immutable objects - x = 9 rebinds the name in the same way that x = [1, 2] would. In neither case would y be affected. What is special is the behavior of mutable objects - code like x = [1, 2]; x.append(3) changes the underlying object. Correct Python implementations will not reuse objects for mutable classes.

In general, you will not have to care about this implementation detail. Although it is true that x = 7; y = 7; x is y will return True in the C implementation of Python, code should not rely on this. Instead, it is best to write your code as if it were entirely possible that x and y would have different objects - you can't mutate those objects, so it should never matter. Instead, use the comparison functions like == - those work regardless of the implementation detail. And in fact numeric equality can easily be true in cases where object identity is not, such as x = 7; y = 7.0; x == y.

All you actually have to worry about is whether you have other names for your mutable objects, such as:

x = [1, 2]
y = x
y.append(3)

Python variables are always references to objects, but in some cases you can work with the objects as if they were values. They aren't, but when you're doing things that are safe to do on shared objects (such as everything you can do to an integer) you don't have to care.

The one fly in this ointment is None. None is a singleton object - None is in fact a reference to a single immutable instance of NoneType. But because comparison logic can be written incorrectly, the correct way to check for None values is in fact to rely on that singleton nature and use x is None instead of x == None and run the risk that x's equality check returns a false positive. I haven't checked the implementation details to see if it's even theoretically possible to create a second object of NoneType, but in any case nothing does.

You should make sure that when you are mutating an object, you either intend your mutations to affect all references to that object or that you make a copy first. Because it is impossible to mutate an immutable type like an int, you don't have to worry about this with ints, tuples, frozensets, or other built-in immutable types.

OTHER TIPS

You may want to use random.sample instead of random.choice. When you use random.sample you specify how many items you want and are guaranteed that you will not get the same letter twice. There is no such guarantee if you are using random.choice twice. Of course, if you don't mind it occasionally switching a value with itself (i.e nothing happening), then you do not need to change anything.

Here's how you could use random.sample:

letter1, letter2 = random.sample(copy, 2)

Also, when you are determining the keys to swap

key1 =  (keys_with_value(copy,letter1))[0]
key2 =  (keys_with_value(copy,letter2))[0]

you are looking for values that match the keys letter1 and letter2. If you want letter1 and letter2 to hold values from dictionary1, you should use

letter1, letter2 = random.sample(copy.values(), 2)

Looking at your code some more, it seems to me that your keys_with_values function is entirely unnecessary. Simply finding two random keys and switching their values can be done like this:

key1, key2 = random.sample(copy, 2)
copy[key1], copy[key2] = copy[key2], copy[key1]

However, if you really did intend to for letter1 and letter2 to be keys, then you are switching the values of two keys that have values that are also keys. In that case, you can use

key1, key2 = random.sample([k for k, v in copy.items() if v in copy], 2)
copy[key1], copy[key2] = copy[key2], copy[key1]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top