19

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?

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
user339108
  • 12,613
  • 33
  • 81
  • 112
  • 3
    I doubt that there is anything simpler than something that is already available for you to use... (at least in this case) – npinti Jul 23 '10 at 08:55

17 Answers17

35

I dont know anything simpler. But you can use the Math.rand() functionality to generate a random number within the range of the character's length without replace and that would give you a shuffled output

public class Shuffle {
    public static void main(String[] args) {
        Shuffle s = new Shuffle();
        s.shuffle("hello");

    }
    public void shuffle(String input){
        List<Character> characters = new ArrayList<Character>();
        for(char c:input.toCharArray()){
            characters.add(c);
        }
        StringBuilder output = new StringBuilder(input.length());
        while(characters.size()!=0){
            int randPicker = (int)(Math.random()*characters.size());
            output.append(characters.remove(randPicker));
        }
        System.out.println(output.toString());
    }
}
/*
Sample outputs
hlleo
llheo
leohl
lleho
*/
bragboy
  • 34,892
  • 30
  • 114
  • 171
13

Not great performance, but quite readable in my opinion:

public static String shuffleString(String string)
{
  List<String> letters = Arrays.asList(string.split(""));
  Collections.shuffle(letters);
  String shuffled = "";
  for (String letter : letters) {
    shuffled += letter;
  }
  return shuffled;
}
Nick Bolton
  • 38,276
  • 70
  • 174
  • 242
10

How about this:

public static String shuffle(String text) {
    char[] characters = text.toCharArray();
    for (int i = 0; i < characters.length; i++) {
        int randomIndex = (int)(Math.random() * characters.length);
        char temp = characters[i];
        characters[i] = characters[randomIndex];
        characters[randomIndex] = temp;
    }
    return new String(characters);
}
cherouvim
  • 31,725
  • 15
  • 104
  • 153
  • This potentially leads to duplicates in random selection. To eliminate that chance will take checking that the picked index (or corresponding character) was not already picked, and randomly picking again if it was. – Alex Hall Mar 22 '20 at 19:56
6

What an annoying problem. I finally ended up with this:

import java.util.Collections;
import com.google.common.primitives.Chars;
import org.apache.commons.lang3.StringUtils;

String shuffle(String s) {
    List<Character> chars = Chars.asList(s.toCharArray());
    Collections.shuffle(chars);
    return StringUtils.join(chars.stream().toArray());
}

Yes, two libraries :)

Chris
  • 61
  • 1
  • 1
3

E.g.:

static String shuffle(String text){
    if (text.length()<=1)
        return text;

    int split=text.length()/2;

    String temp1=shuffle(text.substring(0,split));
    String temp2=shuffle(text.substring(split));

    if (Math.random() > 0.5) 
        return temp1 + temp2;
    else 
        return temp2 + temp1;
}    
amra
  • 16,125
  • 7
  • 50
  • 47
3
class ShuffleString
{

    public static String shuffle(String s)
    {

        String shuffledString = ""; 

        while (s.length() != 0)
        {
            int index = (int) Math.floor(Math.random() * s.length());
            char c = s.charAt(index);
            s = s.substring(0,index)+s.substring(index+1);
            shuffledString += c;
        }

        return shuffledString;

    }

}


public class foo{
    static public void main(String[] args)
    {

        String test = "hallo";
        test = ShuffleString.shuffle(test);
        System.out.println(test);
    }
}

Output: ahlol

user272043
  • 103
  • 6
2

Here's code that requires neither recursion, nor converting to a Collection.

public static String shuffle(String string) {
    StringBuilder sb = new StringBuilder(string.length());
    double rnd;
    for (char c: string.toCharArray()) {
        rnd = Math.random();
        if (rnd < 0.34)
            sb.append(c);
        else if (rnd < 0.67)
            sb.insert(sb.length() / 2, c);
        else
            sb.insert(0, c);
    }       
    return sb.toString();
}
Karl Giesing
  • 1,654
  • 3
  • 16
  • 24
2

Not sure why you wouldn't want to use shuffle, unless it's for school. ;)

And if you're concerned with performance, you definitely can't use any solution that concatenates strings with "+".

Here's the most compact solution I could come up with:

public static String shuffle(String string) {
    if (StringUtils.isBlank(string) {
        return string;
    }

    final List<Character> randomChars = new ArrayList<>();
    CollectionUtils.addAll(randomChars, ArrayUtils.toObject(string.toCharArray()));
    Collections.shuffle(randomChars);
    return StringUtils.join(randomChars, "");
}
mac
  • 2,672
  • 4
  • 31
  • 43
1

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();
}
c0der
  • 18,467
  • 6
  • 33
  • 65
1

In Kotlin, you can do the following.

val helloCharArray = "hello".toCharArray()
helloCharArray.shuffle()
val shuffledHello = helloCharArray.concatToString()
Big Pumpkin
  • 3,907
  • 1
  • 27
  • 18
0

You could iterate over all the characters, comparing each one with the next. Then if Math.rand() > 0.5 swap this character with the next, otherwise move on to the next character.

Tarski
  • 5,360
  • 4
  • 38
  • 47
0
        String shuffled;
        do {
            shuffled = Stream.of(text.split("")).sorted((o1, o2) -> ThreadLocalRandom.current().nextInt(3) - 1).collect(Collectors.joining());
        }while(shuffled.equals(text));
s13o
  • 116
  • 1
  • 5
  • 1
    Cool indeed but broken, because a `Comparator` has to return the same result for a given pair of strings otherwise you have a problem with the `sort(...)` method and might get a "Comparison method violates its general contract!" from the internal TimSort. – Christian Ullenboom Feb 23 '17 at 19:38
0

If you still want to restore the original String later on, try something like this:

public static class ShuffledString
{
    private List<Integer> indices;
    private String string;

    public ShuffledString(List<Integer> indices, String string)
    {
        this.indices = indices;
        this.string = string;
    }

    public List<Integer> getIndices()
    {
        return indices;
    }

    public String getRegularString()
    {
        StringBuilder stringBuilder = new StringBuilder();

        for (int stringIndex = 0; stringIndex < indices.size(); stringIndex++)
        {
            int characterIndex = indices.indexOf(stringIndex);
            stringBuilder.append(string.charAt(characterIndex));
        }

        return stringBuilder.toString();
    }
}

public static ShuffledString shuffle(String input)
{
    List<Integer> indices = new ArrayList<>();

    StringBuilder output = new StringBuilder(input.length());
    while (indices.size() < input.length())
    {
        int randomIndex;

        while (indices.contains(randomIndex = (int) (Math.random() * input.length())))
        {

        }

        indices.add(randomIndex);
        output.append(input.charAt(randomIndex));
    }

    return new ShuffledString(indices, output.toString());
}
BullyWiiPlaza
  • 17,329
  • 10
  • 113
  • 185
0

In Java 8+ this can be done using Collectors.shuffle(...) in three lines as follows:

  1. Convert the String into a List of Characters
  2. Shuffle the list
  3. Convert the shuffled List of Characters back into a String

Code:

public static String shuffle(final String str) {
    List<Character> chars = str.chars().mapToObj(e->(char)e).collect(Collectors.toList());
    Collections.shuffle(chars);
    return chars.stream().map(e->e.toString()).collect(Collectors.joining());
}

Demo:

Rextester demo

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
0

Using commons-lang3:

import org.apache.commons.lang3.ArrayUtils;

String shuffle(String text) {
    char[] chars = text.toCharArray();
    ArrayUtils.shuffle(chars);
    return String.valueOf(chars);
}
yurez
  • 2,826
  • 1
  • 28
  • 22
0

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
 }
Daniel Zin
  • 479
  • 2
  • 7
0

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);
}
dan1st
  • 12,568
  • 8
  • 34
  • 67