6

So for a step size of 1, I want the array:

{1, 2, 3, 4}

To become:

{4, 1, 2, 3}

And for a step of size 2 the result will be:

{3, 4, 1, 2}

This is the code I'm using now:

private static int[] shiftArray(int[] array, int stepSize) {
  if (stepSize == 0)
     return array;

  int shiftStep = (stepSize > array.length ? stepSize % array.length : stepSize);

  int[] array2 = new int[array.length];
  boolean safe = false;
  for (int i = 0; i < array.length; i++) {
     if (safe) {
        array2[i] = array[i - shiftStep];
     }
     else {
        array2[i] = array[array.length - shiftStep + i];
        safe = (i+1) - shiftStep >= 0;
     }
  }
  return array2;
}

The code is working great, but is it possible to achieve this without creating a helper array (which is array2 in the code above)?

Thanks!

Prashant Kumar
  • 20,069
  • 14
  • 47
  • 63
mota
  • 5,275
  • 5
  • 34
  • 44
  • I assume you don't want to return a copy and you want to alter the original? – Peter Lawrey Mar 09 '12 at 14:13
  • 3
    You could use this method: http://stackoverflow.com/questions/4454072/how-to-perform-a-circular-shift-lefton-an-array-of-int – assylias Mar 09 '12 at 14:15
  • Take a look at this for rearranging array elements in-place : http://stackoverflow.com/questions/1683020/possible-to-rearrange-an-array-in-place-in-on . Optimize the algorithm to rearrange specifically, in your case - circular shift. – Kazekage Gaara Mar 09 '12 at 14:19
  • http://stackoverflow.com/a/4464054/181772 is faster than anything posted here. it does just N moves, no matter what the shift distance and requires only constant size temp storage. – andrew cooke Mar 09 '12 at 15:08
  • @andrewcooke I also thought it would be the fastest way to do the job, but my profiling shows that it's actually *way* slower than my method!! Jon's method is really *really* fast! I'll share the full results later. – mota Mar 09 '12 at 15:29
  • @andrew cooke: System.arraycopy() is pretty darn fast. Whereas the solution you linked uses the modulo operator (%) - which is awesomely *slow*. – Durandal Mar 09 '12 at 15:37
  • yes, sorry, i should have been clearer. i mean fast in the sense of operations count (elegance?), not measured speed. for example, it's not so great with caches because it jumps around and it can't take advantage of block moves etc in cpu – andrew cooke Mar 09 '12 at 15:52

6 Answers6

12

You can do it without creating as big an array:

// void return type as it shifts in-place
private static void shiftArray(int[] array, int stepSize) {
    // TODO: Cope with negative step sizes etc
    int[] tmp = new int[stepSize];
    System.arraycopy(array, array.length - stepSize, tmp, 0, stepSize);
    System.arraycopy(array, 0, array, stepSize, array.Length - stepSize);
    System.arraycopy(tmp, 0, array, 0, stepSize);
}

So for a 100,000 array and a step size of 10, it creates a 10-element array, copies the last 10 elements into it, copies the first 999,990 elements to be later, then copies from the temporary array back to the start of the array.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    The triple-reverse trick (mentioned in the questoin @assylias' linked to) can do it without any new array. – yshavit Mar 09 '12 at 14:20
  • 2
    for the record, he is creating a helper array which is supposed to be avoided by the OPs question. ;) Nice solution anyway. – stryba Mar 09 '12 at 14:22
  • 1
    @stryba: Hence the first sentence - there's a big difference between creating an array which is as big as the initial array, and an array which is as big as the step size. I'm going for a practical approach... – Jon Skeet Mar 09 '12 at 14:36
  • 1
    @yshavit: Sure, you *can* do it without any new arrays. I'm sure there are multiple approaches that would cope. However, I strongly suspect they'd be more complicated *and* slower. – Jon Skeet Mar 09 '12 at 14:38
  • @JonSkeet This one's not that complicated at all, and though it may be a bit slower, it wouldn't be by much. Reversing an array in-place is pretty darn fast. – yshavit Mar 09 '12 at 14:43
  • @JonSkeet: I know, and I apparently have to work on my irony skills – stryba Mar 09 '12 at 14:45
  • @yshavit: "Not that complicated at all"? I beg to disagree - reading the description it doesn't make immediate sense to me - whereas I hope my description of how my version works is instantly clear. (The code is pretty straightforward, too..) – Jon Skeet Mar 09 '12 at 14:48
  • Thanks Jon, your method is super fast, and it doesn't conflict with my requirements since the tmp array will be small compared to the input arrays (I'm dealing with huge arrays). – mota Mar 09 '12 at 15:32
  • @JonSkeet I agree the concept is a *bit* harder, but I don't think it's much harder, personally. Anyway, what happens when `stepSize == array.length-1`? The difference between copying the full array and copying only the step size depends on assumptions about both values, whereas the triple-reverse does not. – yshavit Mar 09 '12 at 15:36
  • @yshavit: I did leave a TODO for negative sizes etc. I still think that after coping with that, it would be conceptually simpler than the triple-reverse. It's just a case of: "is this smaller as a shift left or a shift right; adjust step size appropriately; perform shift left or shift right." Each of those steps is *really* simple. I still find the triple-reverse makes my head hurt. – Jon Skeet Mar 09 '12 at 15:38
  • @JonSkeet Alright then, what about shifting an array of 1m elements by 500k? Whether you go left or right, you're allocating a new array of 500k. Now, depending on the use case, that may not be significant -- but it's important to understand. – yshavit Mar 09 '12 at 16:13
  • @yshavit: Yes, indeed, that requires a new array of 500K. I wouldn't like to predict the performance of my version vs triple-reverse in that situation, to be honest. Fortunately, the OP has now said that "the tmp array will be small compared to the input arrays". – Jon Skeet Mar 09 '12 at 16:15
3

Use not the i++, but i += shiftSize and several loops (amount of them would be equal to gcd of array.length and shifSize).

Then you'll need only one int as buffer and execution time will be almost the same.

kirilloid
  • 14,011
  • 6
  • 38
  • 52
3

You could do it with a couple of loops, but its not easy. Using recursion is simpler in this case.

public static void main(String... args) {
    for (int i = 0; i < 12; i++) {
        int[] ints = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
        rotateLeft(ints, i);
        System.out.println(Arrays.toString(ints));
    }
}

public static void rotateLeft(int[] array, int num) {
    rotateLeft(array, num, 0);
}

private static void rotateLeft(int[] array, int num, int index) {
    if (index >= array.length) return;
    int tmp = array[(index + num) % array.length];
    rotateLeft(array, num, index + 1);
    array[index] = tmp;
}

prints

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1]
[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2]
[4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3]
[5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4]
[6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5]
[7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6]
[8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7]
[9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8]
[10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Ok you didn't create an array, but you used more space than the array would have – harold Mar 09 '12 at 14:52
  • @harold indeed. I believe using a temporary array is likely to be the fastest solution. i.e. what was posted originally. Jon's solution might be slightly faster again. – Peter Lawrey Mar 09 '12 at 14:56
2

Yes it's possible, you'd only need to temporary store one element additional to the array. Basically what you want to do is to:

  1. store last element in tmp var
  2. shift all elements to the right by one starting with the second to last element
  3. sotre tmp var as first element
  4. repeat from step 1 depending on your stepsize
stryba
  • 1,979
  • 13
  • 19
2

This is not tested ...

public void rotateByStep(int[] array, int step) {
    step = step % array.length;
    if (step == 0) {
        return;
    }
    int pos = step;
    int tmp = array[0];
    boolean inc = array.length % step == 0;
    for (int i = 0; i < array.length; i++) {
        int tmp2 = array[pos];
        array[pos] = tmp;
        tmp = tmp2;
        pos = (pos + step) % array.length;
        if (inc && pos < step) {
            array[pos] = tmp;
            pos++;
            tmp = array[pos];
        }
    }
}

The idea I'm trying to implement is as follows:

  • If step isn't a factor of length, then incrementing an index (pos) by step modulo length starting from zero will visit every array element once after length iterations.

  • If step is a factor of length, then index (incremented as above) will get back to its starting point after length / step iterations. But if you then increment by one, you can process the cycle starting at 1, and then at 2, and so on. After length iterations, we'll have visited every array element once.

The rest is just rippling the element values as we cycle through the element indexes ... with some adjustment when we increment to the next cycle.

The other complete solutions have the advantage that they are much easier to understand, but this one requires no extra heap storage (i.e. no temporary array), and does the job in array.length loop iterations.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
0

In n- 1 iterations

#include <stdio.h>

    int main(int argc, char **argv) {
        int k = 0, x = 0;
        int a[] = {-5,-4,-1,0,1,2,30,43,52,68,700,800,9999};
        int N = 0, R = 57; /*R = No of rotations*/
        int temp = 0, temp2  = 0, start = 0, iter = 0;

        x = 0;
        temp2 = a[x];

        N = sizeof(a) / sizeof(a[0]);
        for ( k = 0; k < N - 1; k++) {
            x = x + R;
            while ( x >= N ) {
                x = x - N;
            }
            temp = a[x];
            a[x] = temp2;
            temp2 = temp;
            if ( x == start ) {
                start = start + 1;
                x = x + 1;
                temp2 = a[x];
            }
            iter++;
        }
        a[start] = temp2;
        for ( k = 0; k < N; k++) {
            printf(" %d", a[k]);
        }
        printf("\n");
        printf("Done in %d iteration\n", iter);
        return 0;
    }
Alam
  • 1,536
  • 15
  • 26