-1

I'd like a nice one line way of generating a random permutation of a Java String.

Here is an example using Java 8 Streams, of the direction I'm looking for.

In this example I am Using "abcd" as an example input, which could produce permutations like dabc, dbac, etc.

I've reduced generating the String permutation to three lines, but I have a feeling it could be shorter.

public static void main(String[] args) {

    List<Character> charList = "abcd".chars().mapToObj(i -> (char) i).collect(Collectors.toList());
    Collections.shuffle(charList);
    String string =  charList.stream().map(String::valueOf).collect(Collectors.joining()); 
    System.out.println(string);
}

Any way to make this code shorter / simpler would be appreciated.

EDIT:

OK, I have come up with what I think is an efficient one line solution, but it is not very readable, so I will probably break it down to a few lines.

I am including it here just for reference. If someone can simplify it, that would be a welcome answer as well.

String str = "ABCDE";        
String str2 = str.chars().mapToObj(e->(char)e).collect(Collectors.toMap(key -> new Random().nextInt(), value -> value)).values().stream().map(String::valueOf).collect(Collectors.joining());
System.out.println(str2);
Gonen I
  • 5,576
  • 1
  • 29
  • 60
  • 3
    Why would you like it to be shorter? So that noone understands it? Because it **is** quite hard to follow. Please explain to us why exactly you need shorter code, because else, you'll just get lots of answers that go: *Yeah, short is not the best, here look at this code snippet, which does the same but is more understandable*. And that is probably not something you'd expect as a real answer, though it might be – Lino Jun 11 '19 at 22:08
  • Ideally I would like it short AND simple :) – Gonen I Jun 11 '19 at 22:16

5 Answers5

2

I don't believe your stream approach can be made significantly simpler. But in my opinion, it's always easier to write simple routines than try to adapt other methods. An in place shuffle is easy to write and significantly less overhead than the stream approach. Once written, I just tuck them away in a library.

  public static String shuffle(String str) {
      char[] result = str.toCharArray();

      int n = result.length;
      while (n > 0) {
         int v = ThreadLocalRandom.current().nextInt(n);
         char temp = result[v];
         result[v] = result[n - 1];
         result[n - 1] = temp;
         n--;
      }
      return new String(result);
   }
WJS
  • 36,363
  • 4
  • 24
  • 39
  • And if you don't like the shuffle logic in the middle, get it for free from e.g. [Apache Commons Lang](https://stackoverflow.com/a/56552415/5221149). – Andreas Jun 11 '19 at 22:29
2

To add one more aproach using random and streams:

    String str = "ABCDE";        
    Random rand = new Random();
    String perm= rand.ints( 0, str.length())
                     .distinct()
                     .limit(str.length())
                     .mapToObj(i->String.valueOf(str.charAt(i))).collect(Collectors.joining());
    System.out.println(perm);
Eritrean
  • 15,851
  • 3
  • 22
  • 28
1

Not particularly shorter but the intention is a bit clearer.

Random random = new Random();
String chars = "abcd";
StringBuilder sb = new StringBuilder();

Stream.generate(() -> random.nextInt(chars.length()))
             .distinct()
             .limit(chars.length())
             .map(chars::charAt)
             .forEach(sb::append);

System.out.println(sb.toString());

Keep in mind this is not the most performant solution, because the random number generator will generate duplicates which will be dropped by distinct. For a larger string you might want to look at in place shuffling of a list instead.

jbx
  • 21,365
  • 18
  • 90
  • 144
  • I can't get your code to compile on Eclipse. I get the following error for the stream statement. `Cannot invoke distinct() on primitive type char` – WJS Jun 11 '19 at 22:31
  • Fixed it to also work if the input characters are not unique. – jbx Jun 11 '19 at 22:35
  • @Andreas, yes it is just an alternative that is a bit simpler to read. On such small strings performance is not an issue. If you wanted to randomise large strings you would go for in place shuffling, but it is not what the OP asked for. – jbx Jun 11 '19 at 22:37
  • OP asked for permutation of strings, without any defined limit or expectation on the length of said string. `"abcd"` is just an *example*, and should as such not be taken as being representative of the actual strings to process. – Andreas Jun 11 '19 at 22:39
  • Op didn't say he wanted it to be the fastest, he said he wanted a shorter simpler solution. – jbx Jun 11 '19 at 22:42
  • @WJS yes, typed it on my phone and forgot it. Fixed. – jbx Jun 11 '19 at 22:44
  • @jbx You're doing this from you phone!!!? I have a hard enough time doing this from my laptop. – WJS Jun 11 '19 at 22:45
1

From comment:

Ideally I would like it short AND simple

Use Apache Commons Lang class ArrayUtils and its shuffle(char[] array) method.

public static String permute(String s) {
    char[] buf = s.toCharArray();
    ArrayUtils.shuffle(buf);
    return new String(buf);
}

That is simple and very easy to understand.

Andreas
  • 154,647
  • 11
  • 152
  • 247
0

What you could do here is use shuffler inside collector.

You can find example of collector for shuffling list in https://stackoverflow.com/a/36391959/7862302

So in your case it could look like:

String string = "abcd".chars()
                      .mapToObj(i -> (char) i)
                      .collect(toShuffledList())
                      .stream()
                      .map(String::valueOf)
                      .collect(Collectors.joining()); 

You could even introduce collector for shuffling to String:

public static <T> Collector<CharSequence, ?, String> shuffleToString() {
    return Collectors.collectingAndThen(
        Collectors.toCollection(ArrayList::new),
        list -> {
            Collections.shuffle(list);
            return list.stream().collect(Collectors.joining());
        });
}

With this collector it would look like:

    String string = "abcd".chars()
                          .mapToObj(i -> (char) i)
                          .map(String::valueOf)
                          .collect(shuffleToString());
    System.out.println(string);
Szpara
  • 21
  • 3