unknown
unknown

Reputation:

What's the correct way to recurse with lists in Java?

When recursing with Lists in Java, I frequently end up allocating and copying lists many many times. For example, I want to generate a List<List<Integer>> of all possible Integer sequences where:

For example, [1,1,1,2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,7,7,7,8,8,8,9,9,9] is the largest sequence. I have a recursive method that does this:

static void recurse(List<List<Integer>> addto, List<Integer> prev, int n){
    if(n>=10)
        addto.add(prev);
    else{
        for(int i=0; i<=3; i++){
            List<Integer> newlist = new ArrayList<Integer>(prev);
            for(int k=0; k<i; k++){
                newlist.add(n);
            }
            recurse(addto, newlist, n+1);
        }
    }
}

What happens here is that I'm copying the entire prev list 3 times every recursion. I need to do this in order to concatenate stuff to my list and pass it on to the next iteration. This is very slow (2 seconds). A less elegent version using 10 nested loops ran much faster because it did not have to copy so many lists. What's the 'proper' way to do this?

By the way this is not homework, but related to one of the USACO problems.

Upvotes: 1

Views: 333

Answers (2)

Martin v. L&#246;wis
Martin v. L&#246;wis

Reputation: 127457

Rather than copying the list, you should modify the list in-place, and only copy it when you find a solution. After you get out of the recursion, remove the last three elements of the list.

static void recurse(List<List<Integer>> addto, List<Integer> list, int n){
        if(n>=10)
                addto.add(new ArrayList<Integer>(list));
        else{
                int pos = list.size();
                for(int i=0; i<=3; i++){
                        list.add(n);
                        recurse(addto, newlist, n+1);
                }
                for(int i=2; i>=0; i--){
                        list.remove(i);
                }
        }
}

If you want even more performance, try using an int[], rather than an ArrayList of Integer, as that will save you creation of the Integers. You could size the list array with 27 elements, and pass the index of the first free position in the recursion.

Upvotes: 0

Todd Owen
Todd Owen

Reputation: 16188

The slow-down may be due to the memory used internally by the ArrayLists being reallocated. By default, an ArrayList starts with a capacity of 10. When you add the 11th element, it has to expand that, but it only expands by 50%. Moreover, when you create an ArrayList with the copy constructor, the new list ends up with an even smaller capacity -- the actual number of elements in the source list plus 10% I think. (I'm guessing your 10-loop version of the algorithm used just a single "working" list, which it made a copy of just before adding to the List<List<Integer>>).

So you could try providing a capacity when creating the lists, and see if that speeds things up at all:

List<Integer> newlist = new ArrayList<Integer>(27);  // longest list size is 9 * 3
newlist.addAll(prev);

EDIT: By the way, you should be able to implement a non-recursive algorithm without 10 nested loops. Use a stack, similar to a depth-first tree search.

Upvotes: 1

Related Questions