How to shuffle characters in a string without using Collections.shuffle(...)?

Viewed 79339

How do I shuffle the characters in a string (e.g. hello could be ehlol or lleoh or ...). I don't want to use the Collections.shuffle(...) method, is there anything simpler?

17 Answers

Without external libraries, for those who do not mind using Collections.shuffle():

static String shuffle(String string){

    List<Character> list = string.chars().mapToObj(c -> new Character((char) c))
                                         .collect(Collectors.toList());
    Collections.shuffle(list);
    StringBuilder sb = new StringBuilder();
    list.forEach(c -> sb.append(c));

    return sb.toString();
}

In Kotlin, you can do the following.

val helloCharArray = "hello".toCharArray()
helloCharArray.shuffle()
val shuffledHello = helloCharArray.concatToString()

Using commons-lang3:

import org.apache.commons.lang3.ArrayUtils;

String shuffle(String text) {
    char[] chars = text.toCharArray();
    ArrayUtils.shuffle(chars);
    return String.valueOf(chars);
}

One more short implementation using Stream API:

 String shuffle(String str) {       
    return new Random().ints(0, str.length()).distinct().limit(str.length()) // shuffle indexes
            .mapToObj(i->""+str.charAt(i)).collect(Collectors.joining());    // collect letters
 }

Some Strings may contain symbols consisting of multiple characters.

In order to get around this, you can convert the String to code points, shuffle the array and convert them back to a String.

public static String shuffle(String toShuffle){
    int[] codePoints = toShuffle.codePoints().toArray();
    shuffle(codePoints);
    return new String(codePoints, 0, codePoints.length);
}

Shuffling the array could be done like this or using any other method:

//code from https://stackoverflow.com/a/1520212/10871900
private static void shuffle(int[] ar)
  {
    // If running on Java 6 or older, use `new Random()` on RHS here
    Random rnd = ThreadLocalRandom.current();
    for (int i = ar.length - 1; i > 0; i--)
    {
      int index = rnd.nextInt(i + 1);
      // Simple swap
      int a = ar[index];
      ar[index] = ar[i];
      ar[i] = a;
    }
  }

As a less-performant alternative, you could use wrapper objects and shuffle the stream:

public static String shuffle(String toShuffle){
    int[] codePoints = toShuffle
        .codePoints()
        .boxed()
        .sorted((o1, o2) -> ThreadLocalRandom.current().nextInt(-1, 2) //https://stackoverflow.com/a/55352119/10871900 
        .mapToInt(i->i)
        .toArray();
    return new String(codePoints, 0, codePoints.length);
}
Related