2

I have a List containing elements that are accessed according to their indexes. In this list I need to be able to "rotate" groups of 4 elements according to their index. For example in the list

[a, b, c, d, e ,f , g, h, i, j, k, l]

I want to rotate c, f, i, l in order to get

[a, b, l, d, e ,c , g, h, f, j, k, i]

How will you implement this ?

Manuel Selva
  • 18,554
  • 22
  • 89
  • 134

2 Answers2

6

A straightforward solution

If you only need to 1-rotate elements at 4 indices of a List, you can just write a straightforward generic method like this:

static <T> void rotate4(List<T> list, int i0, int i1, int i2, int i3) {
    T item = list.get(i3);
    item = list.set(i0, item);
    item = list.set(i1, item);
    item = list.set(i2, item);
    item = list.set(i3, item);
}

This will cyclically rotate 4 elements of any List<T>. Remember that List.set returns the element that previously was at that index, so you could write the entire method in one-line if you want:

    // one-liner version
    list.set(i3, list.set(i2, list.set(i1, list.set(i0, list.get(i3)))));

With this helper method, you'll have:

    List<Character> list = Arrays.asList(
        'a','b','c','d','e','f','g','h','i','j','k','l'
    );

    System.out.println(list);
    // [a, b, c, d, e, f, g, h, i, j, k, l]
    //        *        *        *        *

    rotate4(list, 2, 5, 8, 11);

    System.out.println(list);       
    // [a, b, l, d, e, c, g, h, f, j, k, i]
    //        *        *        *        *

A more general solution

IF you need a way to rotate an arbitrary number of elements for an arbitrary distance, then you can create a live view of another List, and then you can Collections.rotate that view.

IF the elements are consecutive, for example, you'd just use subList:

    List<Character> list = Arrays.asList(
        'a','b','c','d','e','f','g','h','i','j','k','l'
    );

    System.out.println(list);
    // [a, b, c, d, e, f, g, h, i, j, k, l]
    //     *  *  *  *  *

    System.out.println(list.subList(1, 6));
    // [b, c, d, e, f]

    Collections.rotate(list.subList(1, 6), -2);
    System.out.println(list);
    // [a, d, e, f, b, c, g, h, i, j, k, l]
    //     *  *  *  *  *

Since the elements aren't consecutive, you can't use subList, but you can write e.g. PeriodicalLiveViewList class. You want to be able to write something like this:

    System.out.println(PeriodicalLiveViewList.of(list, 3, 2));
    // [c, f, i, l]

    Collections.rotate(PeriodicalLiveViewList.of(list, 3, 2), 1);

Basically you create another List whose elements are every 3rd element of another List, starting at index 2, as a live view.

If you are using Guava, there is ForwardingList that you can built on. You can implement the decorator pattern for this from scratch too if necessary.

Related questions

Community
  • 1
  • 1
polygenelubricants
  • 376,812
  • 128
  • 561
  • 623
  • 1
    Cleanest way? Then the cleanest way to compare two numbers is running quicksort on them :) Seriously, this is so anti-clean, I can't think of less clean solution for this 5-lines-of-code task. – Nikita Rybak Aug 28 '10 at 01:22
  • 1
    _If you put a bounty on this I'd write this PeriodicalLiveViewList in 2 days at most._ Please say you're kidding and whole OOP idea here is a big joke :) – Nikita Rybak Aug 28 '10 at 01:23
  • @polygenelubricants Well, I must say the idea with nested 'set' calls is quite impressive! (although not very 'clean' too) Anyway, +1 – Nikita Rybak Aug 28 '10 at 01:56
  • Execellent OOP style solution. +1 @Nikita Is Java your favourite language? – whiskeysierra Aug 28 '10 at 17:00
  • @Willi Yes, because it's clean and does not complicate things. – Nikita Rybak Aug 28 '10 at 18:33
  • 1
    @Nikita: `subList` is a very powerful and useful mechanism. The documentation prescribes the idiom to remove a range of elements by `theList.subList(from, to).clear()`, for example. `PeriodicalLiveViewList` is just a variation where the view is not a contiguous block but periodical/cyclical (i.e. every N-th element). As I mentioned previously, I've actually have had many usecases for such a view. You are right that for this particular problem, it's may be an overengineered solution, but if provided, this is a very valuable tool for many list manipulation scenarios. – polygenelubricants Aug 28 '10 at 20:02
2

Here's what I came up with. It's a rather simple brute-force solution, but it's extensible.

import java.util.Arrays;
import java.util.List;

public class ArrayRotator {

    public static void main(String... args) {
        List<String> target = Arrays.asList("a", "b", "c", "d", "e", "f", "g",
                "h", "i", "j", "k", "l");
        System.out.println(target);
        target = rotate(target, 3);
        System.out.println(target);
    }

    private static List<String> rotate(List<String> aList, int anOffset) {
        String[] result = new String[aList.size()];
        for (int i = 0; i < aList.size(); i++) {
            if (0 == (i + 1) % anOffset) {
                if (aList.size() > (i + anOffset)) {
                    result[i + anOffset ] = aList.get(i);
                } else {
                    result[anOffset - 1] = aList.get(i);
                }
            } else {
                result[i] = aList.get(i);
            }
        }
        return Arrays.asList(result);
    }
}

And here's the output I got:

[a, b, c, d, e, f, g, h, i, j, k, l]
[a, b, l, d, e, c, g, h, f, j, k, i]

I didn't think about the Collections functionality in this case, as it seemed to be a bit more complex than that. I'm not sure which algorithm would be more efficient on large lists. Maybe you could combine the functionality as below:

import java.util.List;

public class ListRotator {

    public static void main(String... args) {
        List<String> target = Arrays.asList("a", "b", "c", "d", "e", "f", "g",
                "h", "i", "j", "k", "l");
        System.out.println(target);
        rotate(target, 3);
        System.out.println(target);
    }

    private static void rotate(List<String> aList, int anOffset) {
        for (int i = anOffset-1; i < aList.size()-anOffset; i += anOffset) {
            Collections.rotate(aList.subList(i, i+anOffset), -1);
        }
        Collections.rotate(aList.subList(anOffset-1, aList.size()), 1);
    }

}

It generates the same output as above.

mlschechter
  • 994
  • 5
  • 8