In-place interleaving of the two halves of a string

后端 未结 6 1442
长发绾君心
长发绾君心 2021-02-20 07:30

Given a string of even size, say:

abcdef123456

How would I interleave the two halves, such that the same string would become

6条回答
  •  天命终不由人
    2021-02-20 08:03

    Here's an algorithm and working code. It is in place, O(N), and conceptually simple.

    1. Walk through the first half of the array, swapping items into place.
      • Items that started in the left half will be swapped to the right before we need them, so we use a trick to determine where they were swapped to.
    2. When we get to the midpoint, unscramble the unplaced left items that were swapped to the right.
      • A variation of the same trick is used to find the correct order for unscrambling.
    3. Repeat for the remaining half array.

    This goes through the array making no more than N+N/2 swaps, and requires no temporary storage.

    The trick is to find the index of the swapped items. Left items are swapped into a swap space vacated by the Right items as they are placed. The swap space grows by the following sequence:

    • Add an item to the end(into the space vacated by a Right Item)
    • Swap an item with the oldest existing (Left) item.

    Adding items 1..N in order gives:
    1 2 23 43 435 465 4657 ...
    The index changed at each step is:
    0 0 1 0 2 1 3 ...

    This sequence is exactly OEIS A025480, and can be calculated in O(1) amortized time:

    def next_index(n):
        while n&1: n=n>>1
        return n>>1
    

    Once we get to the midpoint after swapping N items, we need to unscramble. The swap space will contain N/2 items where the actual index of the item that should be at offset i is given by next_index(N/2+i). We can advance through the swaps space, putting items back in place. The only complication is that as we advance, we may eventually find a source index that is left of the target index, and therefore has already been swapped somewhere else. But we can find out where it is by doing the previous index look up again.

    def unscramble(start,i):
            j = next_index(start+i)
            while j

    Note that this only an indexing calculation, not data movement. In practice, the total number of calls to next_index is < 3N for all N.

    That's all we need for the complete implementation:

    def interleave(a, idx=0):
        if (len(a)<2): return
        midpt = len(a)//2 
    
        # the following line makes this an out-shuffle.
        # add a `not` to make an in-shuffle
        base = 1 if idx&1==0 else 0
    
        for i in range(base,midpt):
            j=next_index(i-base)
            swap(a,i,midpt+j)
    
        for i in range(larger_half(midpt)-1):
            j = unscramble( (midpt-base)//2, i);
            if (i!=j):
                swap(a, midpt+i, midpt+j)
    
        interleave(a[midpt:], idx+midpt)
    

    The tail-recursion at the end can easily be replaced by a loop. It's just less elegant with Python's array syntax. Also note that for this recursive version, the input must be a numpy array instead of a python list, because standard list slicing creates copies of the indexes that are not propagated back up.

    Here's a quick test to verify correctness. (8 perfect shuffles of a 52 card deck restore it to the original order).

    A = numpy.arange(52)
    B = A.copy()
    C =numpy.empty(52)
    
    for _ in range(8):
        #manual interleave
        C[0::2]=numpy.array(A[:26])
        C[1::2]=numpy.array(A[26:])
        #our interleave
        interleave(A)
        print(A)
        assert(numpy.array_equal(A,C))
    
    assert(numpy.array_equal(A, B))
    

提交回复
热议问题