Update: Looking for a chance to dive into C , I decided to incorporate this concept (including multple shuffle types) into a Python C Extension Module
I attend a local Python Users Group and had a fun task recently. With a group of others we implemented a function that will shuffle a list imperfectly, attempting to simulate the imperfect nature of a human shuffling a deck of cards.
Predictable behaviours that make a human shuffle imperfect
- Once a deck of cards is divided to each hand, the amount of cards in each hand is not (likely) equal.
- As each thumb releases from it’s half of the deck, sometimes more than one card drops from a single hand at a time.
- As the deck in each hand gets more and more thin, the probability of larger chucks of cards dropping from each hand increases.
Our approach (algorithm)
- Imperfectly divide the list (deck) in half with a set variance
- As long as either hand still has cards 1. (Re-)Evaluate the currently selected hand ( allowing for it to potentially remain the same ) 2. Drop the card from the current hand 3. append remaining cards from hand that still has cards
@staticmethod def humanoid_shuffle(items, num_shuffles=6): # how many times items can be pulled # from the same list consecutively MAX_STREAK = 10 # divide list roughly in half num_items = len(items) margin_of_error = random.randint(0, int(.1 * num_items)) end_range = int(num_items / 2 + margin_of_error) # list up to 0 - end_range first_half = items[:end_range] # list after end_range - len(items) second_half = items[end_range:] split_lists = (first_half, second_half) mixed =  streak = current_list_index = 0 # while both lists still contain items while first_half and second_half: # calc the percentage of remaining total items remaining = (1 - float(len(mixed)) / num_items) # if we happen to generate a random value # less than the remaining percentage # which will be continually be decreasing #(along with the probability) # or # if MAX_STREAK is exceeded if random.random() < remaining or streak > MAX_STREAK: # switch which list is being used to pull items from current_list_index = 1 ^ current_list_index # reset streak counter streak = 0 # pop the selected list onto the new (shuffled) list mixed.append(split_lists[current_list_index].pop()) # increment streak of how many consecutive # times a list has remained selected streak += 1 # add any remaining items mixed.extend(first_half) mixed.extend(second_half) num_shuffles -= 1 # if we still have shuffles to do if num_shuffles: # rinse and repeat mixed = humanoid_shuffle(mixed, num_shuffles) # finally return fully shuffled list return mixed