In-place interleaving of the two halves of a string

后端 未结 6 1464
长发绾君心
长发绾君心 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 07:48

    Generically that problem is quite hard -- and it reduces to finding permutation cycles. The number and length of those varies quite a lot depending on the length.

    Cycles for in-place interleaving for 10 and 12 entry arrays

    The first and last cycles are always degenerate; the 10 entry array has 2 cycles of lengths 6 and 2 and the 12 entry array has a single cycle of length 10.

    Withing a cycle one does:

     for (i=j; next=get_next(i) != j; i=next) swap(i,next);
    

    Even though the function next can be implemented as some relatively easy formula of N, the problem is postponed to do book accounting of what indices have been swapped. In the left case of 10 entries, one should [quickly] find the starting positions of the cycles (they are e.g. 1 and 3).

    0 讨论(0)
  • 2021-02-20 07:58

    Alright, here's a rough draft. You say you don't just want an algorithm, but you are taking hints, so consider this algorithm a hint:

    Length is N.

    k = N/2 - 1.

    1) Start in the middle, and shift (by successive swapping of neighboring pair elements) the element at position N/2 k places to the left (1st time: '1' goes to position 1).

    2) --k. Is k==0? Quit.

    3) Shift (by swapping) the element at N/2 (1st time:'f' goes to position N-1) k places to the right.

    4) --k.

    Edit: The above algorithm is correct, as the code below shows. Actually proving that it's correct is waaay beyond my capabilities, fun little question though.

    #include <iostream>
    #include <algorithm>
    
    int main(void)
    {
        std::string s("abcdefghij1234567890");
        int N = s.size();
        int k = N/2 - 1;
        while (true)
        {
    
            for (int j=0; j<k; ++j)
            {
                int i = N/2 - j;
                std::swap(s[i], s[i-1]);
            }
    
            --k;
    
            if (k==0) break;
    
            for (int j=0; j<k; ++j)
            {
                int i = N/2 + j;
                std::swap(s[i], s[i+1]);
            }
    
            --k;
        }
    
       std::cout << s << std::endl;
    
       return 0;
    }
    
    0 讨论(0)
  • 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<i: j = next_index(start+j)
            return 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))
    
    0 讨论(0)
  • 2021-02-20 08:04

    You may be able to do it in O(N*log(N)) time:

    Want: abcdefgh12345678 -> a1b2c3d4e5f6g7h8
    
    a b c d e f g h
      1 2 3 4 5 6 7 8
    
      4 1-sized swaps:
    
    a 1 c 3 e 5 g 7
      b 2 d 4 f 6 h 8
    
    a1  c3  e5  g7
        b2  d4  f6  h8
    
      2 2-sized swaps:
    
    a1  b2  e5  f6
        c3  d4  g7  h8
    
    a1b2  e5f6
          c3d4  g7h8
    
      1 4-sized swap:
    
    a1b2  c3d4
          e5f6  g7h8
    
    a1b2c3d4
            e5f6g7h8
    

    Implementation in C:

    #include <stdio.h>
    #include <string.h>
    
    void swap(void* pa, void* pb, size_t sz)
    {
      char *p1 = pa, *p2 = pb;
      while (sz--)
      {
        char tmp = *p1;
        *p1++ = *p2;
        *p2++ = tmp;
      }
    }
    
    void interleave(char* s, size_t len)
    {
      size_t start, step, i, j;
    
      if (len <= 2)
        return;
    
      if (len & (len - 1))
        return; // only power of 2 lengths are supported
    
      for (start = 1, step = 2;
           step < len;
           start *= 2, step *= 2)
      {
        for (i = start, j = len / 2;
             i < len / 2;
             i += step, j += step)
        {
          swap(s + i,
               s + j,
               step / 2);
        }
      }
    }
    
    char testData[][64 + 1] =
    {
      { "Aa" },
      { "ABab" },
      { "ABCDabcd" },
      { "ABCDEFGHabcdefgh" },
      { "ABCDEFGHIJKLMNOPabcdefghijklmnop" },
      { "ABCDEFGHIJKLMNOPQRSTUVWXYZ0<({[/abcdefghijklmnopqrstuvwxyz1>)}]\\" },
    };
    
    int main(void)
    {
      unsigned i;
    
      for (i = 0; i < sizeof(testData) / sizeof(testData[0]); i++)
      {
        printf("%s -> ", testData[i]);
        interleave(testData[i], strlen(testData[i]));
        printf("%s\n", testData[i]);
      }
    
      return 0;
    }
    

    Output (ideone):

    Aa -> Aa
    ABab -> AaBb
    ABCDabcd -> AaBbCcDd
    ABCDEFGHabcdefgh -> AaBbCcDdEeFfGgHh
    ABCDEFGHIJKLMNOPabcdefghijklmnop -> AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPp
    ABCDEFGHIJKLMNOPQRSTUVWXYZ0<({[/abcdefghijklmnopqrstuvwxyz1>)}]\ -> AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz01<>(){}[]/\
    
    0 讨论(0)
  • 2021-02-20 08:06

    Ok lets start over. Here is what we are going to do:

    def interleave(string):
        i = (len(string)/2) - 1
        j = i+1
    
        while(i > 0):
            k = i
            while(k < j):
                tmp = string[k]
                string[k] = string[k+1]
                string[k+1] = tmp
                k+=2 #increment by 2 since were swapping every OTHER character
            i-=1 #move lower bound by one
            j+=1 #move upper bound by one
    

    Here is an example of what the program is going to do. We are going to use variables i,j,k. i and j will be the lower and upper bounds respectively, where k is going to be the index at which we swap.

    Example

    `abcd1234`
    
    i = 3 //got this from (length(string)/2) -1
    
    j = 4 //this is really i+1 to begin with
    
    k = 3 //k always starts off reset to whatever i is 
    
    swap d and 1
    increment k by 2 (k = 3 + 2 = 5), since k > j we stop swapping
    
    result `abc1d234` after the first swap
    
    i = 3 - 1 //decrement i
    j = 4 + 1 //increment j
    k= 2 //reset k to i
    
    swap c and 1, increment k (k = 2 + 2 = 4), we can swap again since k < j
    swap d and 2, increment k (k = 4 + 2 = 6), k > j so we stop
    //notice at EACH SWAP, the swap is occurring at index `k` and `k+1`
    
    result `ab1c2d34`
    
    i = 2 - 1
    j = 5 + 1
    k = 1
    
    swap b and 1, increment k (k = 1 + 2 = 3), k < j so continue
    swap c and 2, increment k (k = 3 + 2 = 5), k < j so continue
    swap d and 3, increment k (k = 5 + 2 = 7), k > j so were done
    
    result `a1b2c3d4`
    

    As for proving program correctness, see this link. It explains how to prove this is correct by means of a loop invariant.

    A rough proof would be the following:

    1. Initialization: Prior to the first iteration of the loop we can see that i is set to (length(string)/2) - 1. We can see that i <= length(string) before we enter the loop.
    2. Maintenance. After each iteration, i is decremented (i = i-1, i=i-2,...) and there must be a point at which i<length(string).
    3. Termination: Since i is a decreasing sequence of positive integers, the loop invariant i > 0 will eventually equate to false and the loop will exit.
    0 讨论(0)
  • 2021-02-20 08:06

    The solution is here J. Ellis and M. Markov. In-situ, stable merging by way of perfect shuffle. The Computer Journal. 43(1):40-53, (2000).

    Also see the various discussions here:

    1. https://cs.stackexchange.com/questions/332/in-place-algorithm-for-interleaving-an-array/400#400
    2. https://cstheory.stackexchange.com/questions/13943/linear-time-in-place-riffle-shuffle-algorithm.
    0 讨论(0)
提交回复
热议问题