2

I recently made a method in Java to get the permutation of a string but it's throwing this when the string is too long: java.lang.OutOfMemoryError: Java heap space I'm sure that the method is efficient so I need advice on how to spread the calculations to avoid the error. I'm running it using the console in Eclipse.

public static ArrayList<String> permutation(String s) {
        ArrayList<String> res = new ArrayList<String>();
        if (s.length() == 1) {
            res.add(s);
        } else if (s.length() > 1) {
            int lastIndex = s.length() - 1;
            String last = s.substring(lastIndex);
            String rest = s.substring(0, lastIndex);
            res = merge(permutation(rest), last);
        }
        return res;
    }
    public static int factorial(int n) {
        int fact = 1;
        for (int i = 1; i <= n; i++) {
            fact *= i;
        }
        return fact;
    }
    public static ArrayList<String> merge(ArrayList<String> list, String c) {
        ArrayList<String> res = new ArrayList<String>();
        for (String s : list) {
            for (int i = 0; i <= s.length(); ++i) {
                String ps = new StringBuffer(s).insert(i, c).toString();
                res.add(ps);
            }
        }
        return res;
    }
Robin De Baets
  • 389
  • 1
  • 3
  • 12
  • Can you show us what your algorithm looks like so that we know how to spread it out? – Brian J Feb 25 '15 at 21:10
  • Factorials grow *really* fast. But it doesn't look like you're calling that method anywhere. Should you be? – Brian J Feb 25 '15 at 21:15
  • Yeah, factorials grow pretty damn fast. If you're trying to calculate 1000! or something of that nature, you're probably gonna have a bad time. The answer below should help to increase heap size, and possibly get you a bit further. – Ricky Mutschlechner Feb 25 '15 at 21:16
  • I just posted the method, I'm calling it in another part of the code. I'll take the heap size advice. – Robin De Baets Feb 25 '15 at 21:18
  • @RobinDeBaets memoization (i.e. http://stackoverflow.com/questions/1988804/what-is-memoization-and-how-can-i-use-it-in-python) might help you out, too. I'm not sure if it will, but might be worth a shot. – Ricky Mutschlechner Feb 25 '15 at 21:20
  • @RobinDeBaets please make sure you accept the answer by clicking the tick box – adrCoder Feb 25 '15 at 21:36
  • As you can generate a lot of permutations, growing exponentially with each character you need to process each permutation as you generate it, and avoid creating such a large list. – Peter Lawrey Feb 26 '15 at 08:14

4 Answers4

1

You can increase the heap space as presented here:

http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

using

java -Xms<initial heap size> -Xmx<maximum heap size> 

on the command line. By default, the values are 32m and 128m.

adrCoder
  • 3,145
  • 4
  • 31
  • 56
  • Thanks, anyway how I could improve it in the code ? Can't it spread the calculations over some longer period ? – Robin De Baets Feb 25 '15 at 21:12
  • For specific solution ideas for computing factorials see here: http://stackoverflow.com/questions/22751969/calculate-large-factorials-using-nodes-in-java –  Feb 25 '15 at 21:19
1

First off, you need to make sure you understand what causes an OutOfMemoryError (OOME). You make couple of references to spreading the calculations out over time. I may misunderstand what you mean by that but as stated, that won't make any difference in this situation.

OOMEs are the result of 1. the JVM not having a free contiguous block of space large enough to store a new object after releasing all garbage and compacting the heap. Time isn't really a factor in this situation. If the memory is reachable, it can't be collected regardless of how long it's been around.

So there are a couple of reasons you might have an issue here. One is that you are trying to create a very large object (which strings allow you to do) and there's no free block of heap large enough to accommodate it. The other reason would be that you've filled up the heap with objects that aren't collectable because you are still referencing them.

In this case, I think the issue is that you are putting all of the strings into an arraylist so they can't be collected before the end of the execution of the method. It's worth noting that substr actually creates a new string that refernces the original string's character array and therefore won't allow it to be collected. This can be an issue if you want to pull a small bit of text out of a large String and discard the rest. I don't think it's an issue here but good to know all the same.

The most efficient way to reduce memory is to create your own Collection class and generate each permutation as the iterator is called. This would eliminate the need to store all the permutations in memory at the same time. If I get a minute, I will post some example code.

Edit: I worked up an example without changing the fundamental structure of your algorithm. It should run in a normal JVM for larger inputs than you'll ever want to wait to finish. Essentially by putting all the results into an Arraylist, your memory consumption grows a factorial rate with respect to your input string length. By eliminating the storage of the results, the below approach has memory usage that grows linearly with respect to the input string length.

This is not perfect and there are some problems that I am leaving to the reader to solve. Hint: what happens if you create a Permutor with an empty String?

As someone mentioned StringBuffer is not the best choice here but you'd probably do even better using substring and concat something like so:

current.substring(0, position).concat(last).concat(current.substring(position));

Example:

public class Example
{
    public static void main(String... args) {
        Permutor p = new Permutor("abcdefghijklmnopqrstuvwxyz");

        System.out.println(p.size());

        int i = 0;

        for (String s : p) {
          System.out.println(i++ + ": " + s);
        }
    }


    public static int factorial(int n) {
        int fact = 1;
        for (int i = 1; i <= n; i++) {
            fact *= i;
        }
        return fact;
    }

    public static class Permutor extends AbstractCollection<String>
    {
        private String characters;

        public Permutor(String s)
        {
            characters = s;
        }

        @Override
        public Iterator<String> iterator()
        {
            if (characters.length() == 1) {
                return Collections.singleton(characters).iterator();
            } else {
                return new PermutingIterator(characters);
            }
        }

        @Override
        public int size()
        {
            return factorial(characters.length());
        }
    }

    private static class PermutingIterator implements Iterator<String>
    {
        private final char last;
        private final Iterator<String> inner;

        private String current;
        private int position;

        PermutingIterator(String s)
        {
            int lastIndex = s.length() - 1;
            this.inner = new Permutor(s.substring(0, lastIndex)).iterator();
            this.last = s.charAt(lastIndex);
        }

        @Override
        public boolean hasNext()
        {
            return inner.hasNext() || (current != null && position <= current.length());
        }

        @Override
        public String next()
        {
          while(true) {
              if (current != null && position <= current.length()) {
                  return new StringBuffer(current).insert(position++, last).toString();
              } else if (inner.hasNext()) {
                  position = 0;
                  current = inner.next();
              } else {
                  throw new IllegalStateException("no more permutations available");
              }
          }
        }

        @Override
        public void remove()
        {
          throw new UnsupportedOperationException();
        }
    }
}
James Watson
  • 464
  • 2
  • 11
0

I'm not an algorithm specialist or something, but here is an algorithm I came up with ontop of my head. (Proboably allready exists in some form)
I also don't know if its more efficient.

public static List<String> permutations(String s) {
    List<String> permutations = new ArrayList<String>();
    if (s.length() == 1) {
        permutations.add(s);
        return permutations;
    }
    for (int i = 0; i < s.length(); i++) {
        char starChar = s.charAt(i);
        String rest = new StringBuilder(s).deleteCharAt(i).toString();
        for (String string : permutations(rest)) {
            permutations.add(starChar + string);
        }
    }
    return permutations;
}

It has been able to permutate a String of length 10 so far.
As I write this my computer is still trying to permutate a String of length 11 for something like 10 minutes, and still no OutOfMemoryError.

I also noticed you are using a StringBuffer in your code. The methods of StringBuffer are synchronised and thus slower. You should use a StringBuilder instead. Appart from the method synchronisation the classes are allmost identical.

Edit:
OutOfMemoryError while trying to permutate a String of length 11.
So the max is 10 I guess (at least on my machine).

Edit 2:
Another thing you could do is to save some of the progress of the caculation on the hard drive. But that would be some seriouis work!

Quijx
  • 366
  • 1
  • 10
  • Depending on what you want with the results, you could just write out your permutation to the file and never look at it again. That would be the simple version of saving progress to the hard drive. – Brian J Feb 26 '15 at 15:28
0

What you can do is return only one permutation per call, instead of an entire List. This way you could keep calling that method to get the next permutation as needed.

class Permutation {

    private String str;
    private int first;
    private int swap;
    private BigInteger count;
    private BigInteger numPermutations;

    public Permutation(String str) {
        this.str = str;
        this.first = 0;
        this.swap = 1;
        this.count = BigInteger.ZERO;
        this.numPermutations = factorial(str.length());
    }

    public String next() {
        if (swap >= str.length()) {
            swap = 1;
            first = 0;
        }

        char[] array = str.toCharArray();
        char tmp = array[first];
        array[first] = array[swap];
        array[swap] = tmp;

        swap++;
        first++;
        count = count.add(BigInteger.ONE);

        str = String.valueOf(array);
        return str;
    }

    public boolean hasNext() {
        return count.compareTo(numPermutations) < 0;
    }

    private static BigInteger factorial(int n) {
        BigInteger fact = BigInteger.ONE;
        for (int i = 1; i <= n; i++) {
            fact = fact.multiply(BigInteger.valueOf(i));
        }
        return fact;
    }
}

Here's an example of it's use.

Permutation perm = new Permutation("abcde");
while (perm.hasNext()) {
    System.out.println(perm.next());
}
yate
  • 794
  • 4
  • 7