3

I would like to shuffle the key value pairs in this dictionary so that the outcome has no original key value pairs. Starting dictionary:

my_dict = {'A':'a',
           'K':'k',
           'P':'p',
           'Z':'z'}

Example of unwanted outcome:

my_dict_shuffled = {'Z':'a',
                    'K':'k', <-- Original key value pair
                    'A':'p',
                    'P':'z'}

Example of wanted outcome:

my_dict_shuffled = {'Z':'a',
                    'A':'k',
                    'K':'p',
                    'P':'z'}

I have tried while loops and for loops with no luck. Please help! Thanks in advance.

  • Maybe there's an elegant way, but have you tried a brute force approach? Also, not what you're looking for, but one non-random way is to just shift all the values relative to the keys. BTW welcome to Stack Overflow! Check out the [tour]. – wjandrea Nov 15 '19 at 02:01
  • Could you post your attempts? You can [edit] the question. – wjandrea Nov 15 '19 at 02:03
  • 1
    Shuffle once. Split the array into good set and bad (unchanged) set. For each one in the bad set, randomly pick one in the good set and swap them. – jf328 Nov 15 '19 at 02:04
  • @jf328 Is there a way to ensure you don't pick the same element from the good set as the one you're replacing in the bad set? – wjandrea Nov 15 '19 at 02:06
  • @wjandrea, it's by problem definition. union(keys in good set) = union(values in good set) = U. union(keys in bad set) = union(values in bad set) = V. Then U and V are disjoint. Of course we treat all values as different. – jf328 Nov 15 '19 at 02:12
  • will the dict be huge? I have an idea about a solution, but it would not be very performant as I had to create / modify lists or set for each iteration. – gelonida Nov 15 '19 at 02:16
  • 1
    Possible duplicate: https://stackoverflow.com/q/7279895/1568919 – jf328 Nov 15 '19 at 02:22

3 Answers3

3

Here's a fool-proof algorithm I learned from a Numberphile video :)

import itertools
import random

my_dict = {'A': 'a',
           'K': 'k',
           'P': 'p',
           'Z': 'z'}

# Shuffle the keys and values.
my_dict_items = list(my_dict.items())
random.shuffle(my_dict_items)
shuffled_keys, shuffled_values = zip(*my_dict_items)

# Offset the shuffled values by one.
shuffled_values = itertools.cycle(shuffled_values)
next(shuffled_values, None)  # Offset the values by one.

# Guaranteed to have each value paired to a random different key!
my_random_dict = dict(zip(shuffled_keys, shuffled_values))

Disclaimer (thanks for mentioning, @jf328): this will not generate all possible permutations! It will only generate permutations with exactly one "cycle". Put simply, the algorithm will never give you the following outcome:

   {'A': 'k',
    'K': 'a',
    'P': 'z',
    'Z': 'p'}

However, I imagine you can extend this solution by building a random list of sub-cycles:

(2, 2, 3) => concat(zip(*items[0:2]), zip(*items[2:4]), zip(*items[4:7]))
Brian Rodriguez
  • 4,250
  • 1
  • 16
  • 37
  • 2
    The minor issue is that it only generates cycles, not complete random. But should be fine in most cases. – jf328 Nov 15 '19 at 02:36
  • 1
    This was my Christmas secret santa algo – jf328 Nov 15 '19 at 02:41
  • Besides the typo in `shuflfed_values`, there is a problem because you're using `zip` with one argument - this gives a "too many values to unpack" error. – kaya3 Nov 15 '19 at 02:41
1

A shuffle which doesn't leave any element in the same place is called a derangement. Essentially, there are two parts to this problem: first to generate a derangement of the keys, and then to build the new dictionary.

We can randomly generate a derangement by shuffling until we get one; on average it should only take 2-3 tries even for large dictionaries, but this is a Las Vegas algorithm in the sense that there's a tiny probability it could take a much longer time to run than expected. The upside is that this trivially guarantees that all derangements are equally likely.

from random import shuffle

def derangement(keys):
    if len(keys) == 1:
        raise ValueError('No derangement is possible')

    new_keys = list(keys)

    while any(x == y for x, y in zip(keys, new_keys)):
        shuffle(new_keys)

    return new_keys

def shuffle_dict(d):
    return { x: d[y] for x, y in zip(d, derangement(d)) }

Usage:

>>> shuffle_dict({ 'a': 1, 'b': 2, 'c': 3 })
{'a': 2, 'b': 3, 'c': 1}
kaya3
  • 47,440
  • 4
  • 68
  • 97
0

theonewhocodes, does this work, if you don't have a right answer, can you update your question with a second use case?

my_dict = {'A':'a',
           'K':'k',
           'P':'p',
           'Z':'z'}

while True:
   new_dict = dict(zip(list(my_dict.keys()), random.sample(list(my_dict.values()),len(my_dict))))
   if new_dict.items() & my_dict.items():
      continue
   else:
      break

print(my_dict)
print(new_dict)
oppressionslayer
  • 6,942
  • 2
  • 7
  • 24