Shuffle string c#
Asked Answered
F

12

21

I want to know shuffle string

Example string

string word;

//I want to shuffle it
word = "hello"  

I would be able to get:

rand == "ohlel"
rand == "lleho"
etc.
Frequency answered 19/1, 2011 at 19:45 Comment(0)
S
26

This solution (in a form of extension method) is nice:

    public static string  Shuffle(this string str)
    {
        char[] array = str.ToCharArray();
        Random rng = new Random();
        int n = array.Length;
        while (n > 1)
        {
            n--;
            int k = rng.Next(n + 1);
            var value = array[k];
            array[k] = array[n];
            array[n] = value;
        }
        return new string(array);
    }
Shortbread answered 19/1, 2011 at 19:55 Comment(1)
Note to implementers - initializing random more than once in a process instance is not good practice. The line Random rng = new Random(); Should not be in the method but as a static variableKovrov
C
16

I accomplish this with this extension:

public static class Extensions{
    public static string Scramble(this string s){
        return new string(s.ToCharArray().OrderBy(x=>Guid.NewGuid()).ToArray());
    }
}
Craigie answered 20/2, 2013 at 18:0 Comment(3)
Seriously, I'd like to know what is wrong with this. It's pretty fast, a single line and easy to read and it works.Pheidippides
GUID creation feels like a pretty heavy weight operation to call for each iteration of the sort. Not tested but it feels slower than it could be.Manaker
For my purposes I'm generating a very temporary password for a new user. I am not doing it a thousand times. I'm doing it once a week. This solution looks perfect for my application. In my case I'm applying some additional rules to generate a password that will be in a specific order (random char from each of a set of groups, then random chars from all groups till length requirement met) among other things), and this will get me a much more random password with no tangible cost.Obtect
S
10

C#:

string str = "hello";

// The random number sequence
Random num = new Random();

// Create new string from the reordered char array
string rand = new string(str.ToCharArray().
                OrderBy(s => (num.Next(2) % 2) == 0).ToArray());
Standee answered 19/1, 2011 at 19:49 Comment(1)
This will not yield a good random result. According to MSDN OrderBy will use a stable sort and preserve the order for those characters that are given the same value. For the 'h' in "hello" to be put last, 'h' needs to roll 'false' and the rest 'true'. However the result will be "elloh", which is not very exciting. msdn.microsoft.com/en-us/library/bb534966.aspxBeach
M
5

Try Fisher-Yates Shuffle:

class Shuffle
{
    static System.Random rnd = new System.Random();

    static void Fisher_Yates(int[] array)
    {
        int arraysize = array.Length;
        int random;
        int temp;

        for (int i = 0; i < arraysize; i++)
        {
            random = i + (int)(rnd.NextDouble() * (arraysize - i));

            temp = array[random];
            array[random] = array[i];
            array[i] = temp;
        }
    }

    public static string StringMixer(string s)
    {
        string output = "";
        int arraysize = s.Length;
        int[] randomArray = new int[arraysize];

        for (int i = 0; i < arraysize; i++)
        {
            randomArray[i] = i;
        }

        Fisher_Yates(randomArray);

        for (int i = 0; i < arraysize; i++)
        {
            output += s[randomArray[i]];
        }

        return output;
    }
}

class Program
{
    static void Main()
    {
        string original = "Hello World!";

        string mixedOriginal = Shuffle.StringMixer(original);

        System.Console.WriteLine("The original string: {0}", original);
        System.Console.WriteLine("A mix of characters from the original string: {0}", mixedOriginal);

        System.Console.ReadKey();
    }
}
Meanwhile answered 1/9, 2015 at 14:23 Comment(1)
+1 for the idea of the algorithm, we have a much more short version at: rosettacode.org/wiki/Knuth_shuffle#C.23Shalondashalt
P
3

You're looking for something like the Fisher–Yates shuffle. There's actually a Python example on that page:

import random

def shuffle(x):
    for i in reversed(range(1, len(x))):
        # pick an element in x[:i+1] with which to exchange x[i]
        j = random.randrange(i+1)
        x[i], x[j] = x[j], x[i]

Edit: Since your question is tagged both ironpython and c#, there's also a Java example there which is very easily converted to C#.

Pasteurism answered 19/1, 2011 at 19:47 Comment(0)
P
2

One thing to be aware of is that the input string may contain things like surrogate pairs and combining diacritics. If these are a concern, you may want to try shuffling text elements:

using System;
using System.Globalization;
using System.Linq;
using System.Text;

public static class StringExtensions
{
    public static string ShuffleByTextElements(this string source, Random random)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (random == null) throw new ArgumentNullException(nameof(random));

        var info = new StringInfo(source);
        var indices = Enumerable.Range(0, info.LengthInTextElements).ToArray();

        // Fisher-Yates shuffle
        for (var i = indices.Length; i-- > 1;)
        {
            var j = random.Next(i + 1);
            if (i != j)
            {
                var temp = indices[i];
                indices[i] = indices[j];
                indices[j] = temp;
            }
        }

        var builder = new StringBuilder(source.Length);
        foreach (var index in indices)
        {
            builder.Append(info.SubstringByTextElements(index, 1));
        }

        return builder.ToString();
    }
}

Note that the above code will still not handle certain Unicode features such as bidirectional overrides correctly, nor will it handle the details of certain scripts where the letter form depends on where it is in the word. An example of this would be Greek lowercase sigma, which is encoded as U+03C3 GREEK SMALL LETTER SIGMA (σ), except at the end of the word where U+03C2 GREEK SMALL LETTER FINAL SIGMA (ς) is used instead.

Poyssick answered 5/2, 2019 at 18:10 Comment(0)
B
1

inspired from tsql' order by newid()

static string shuffle(string input)
{
    var q = from c in input.ToCharArray()
            orderby Guid.NewGuid()
            select c;
    string s = string.Empty;
    foreach (var r in q)
        s += r;
    return s;
}
Blockbuster answered 21/1, 2011 at 19:28 Comment(0)
G
1

The best way to shuffle a string or a list of strings is using this way. Here you will get no duplicates:

class CardsDeck
{
    public static Random r = new Random();

    private static List<string> cards = new List<string>{ "♣ King", "♣ Queen", "♣ Jack", " ♣", "♣ 7", "♣ 8", "♣ 9", "♣ 10",
                                                          "♦ King", "♦ Queen", "♦ Jack", " ♦", "♦ 7", "♦ 8", "♦ 9", "♦ 10",
                                                          "♥ King", "♥ Queen", "♥ Jack", " ♥", "♥ 7", "♥ 8", "♥ 9", "♥ 10",
                                                          "♠ King", "♠ Queen", "♠ Jack", " ♠", "♠ 7", "♠ 8", "♠ 9", "♠ 10" };
    public string ReceiveCards()
    {
        if (cards.Count > 0)
        {
            int index = r.Next(cards.Count);
            var card = cards[index];
            cards.RemoveAt(index);
            return card;
        }
        else
        {
            return "";
        }
    }
}
Garygarza answered 1/8, 2012 at 8:36 Comment(1)
No, that is not the best way. Although it avoids duplicates, it's horribly inefficient. The Fisher-Yates shuffle mentioned elsewhere is a much better way to do it.Tacky
H
0
class Program
{

    static void Main(string[] args)
    {
        string word = "hello";
        string temp = word;
        string result = string.Empty;
        Random rand = new Random();

        for (int a = 0; a < word.Length; a++)
        {
            //multiplied by a number to get a better result, it was less likely for the last index to be picked
            int temp1 = rand.Next(0, (temp.Length - 1) * 3);

            result += temp[temp1 % temp.Length];
            temp = temp.Remove(temp1 % temp.Length, 1);
        }
        Console.WriteLine(result);
    }
}
Hawking answered 20/1, 2011 at 19:32 Comment(0)
E
0

You could try some thing like this..

class Program
{
    static bool IsPositionfilled(int Position, List<int> WordPositions)
    {
        return WordPositions.Exists(a => a == Position);
    }

    public static string shufflestring(string word)
    {
        List<int> WordPositions = new List<int>();
        Random r = new Random();
        string shuffledstring = null;
        foreach (char c in word)
        {
            while (true)
            {

                int position = r.Next(word.Length);
                if (!IsPositionfilled(position, WordPositions))
                {
                    shuffledstring += word[position];
                    WordPositions.Add(position);
                    break;
                }
            }


        }
        return shuffledstring;
    }
    static void Main(string[] args)
    {

        string word = "Hel";
        Hashtable h = new Hashtable();
        for (int count = 0; count < 1000; count++)
        {
            Thread.Sleep(1);
            string shuffledstring = shufflestring(word);
            if (h.Contains(shuffledstring))
                h[shuffledstring] = ((int)h[shuffledstring]) + 1;
            else
                h.Add(shuffledstring,1);
        }

        Console.WriteLine(word);
        foreach (DictionaryEntry e in h)
        {
            Console.WriteLine(e.Key.ToString() + " , " + e.Value.ToString()); 
        }
    }
}
Escarole answered 21/1, 2011 at 18:14 Comment(0)
N
-1

I tried the old school way of doing it, this one works fine.

    static void Main()
    {        
        string input = "hello";
        string output = "";
        int ranIndex = 0;
        List<int> indexes = new List<int>();
        char[] split = input.ToCharArray();
        Random ran = new Random();

        for (int i = 0; i < input.Length; i++) 
        {
            ranIndex = ran.Next(0, input.Length);

            if (!indexes.Contains(ranIndex))
            {
                indexes.Add(ranIndex);
            }
            else 
            {
                i--;
            }
        }

        foreach (int value in indexes) 
        {
            output += split[value];
        }

            Console.WriteLine(output);
            Console.ReadLine();
    }
Namedropper answered 22/1, 2011 at 0:9 Comment(0)
A
-1

Fisher-Yates

static Random rand = new Random();
public static string ShuffleString(string s)
{
    if (string.IsNullOrEmpty(s))
        return s;
    char[] chars = s.ToCharArray();
    char c;
    int j;
    for(int i = chars.Length - 1; i > 0; i--)
    {
        j = rand.Next(i + 1);  // Next max is exclusive
        if (j == i)
            continue;
        c = chars[j];
        chars[j] = chars[i];
        chars[i] = c;
    }
    return chars.ToString();
}
Albric answered 17/1, 2017 at 13:11 Comment(2)
Works well except chars.ToString() always returns a literal "System.Char[]". The line return chars.ToString(); should be return new string(chars);Mot
This does not keep the original set, it overwrites chars not used.Yawn

© 2022 - 2024 — McMap. All rights reserved.