Imperfect Human List (Card) Shuffle in Python
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)
- 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 
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