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.

## Considerations

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)

1. Imperfectly divide the list (deck) in half with a set variance
2. 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

## Implementation

``````@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
``````