views:

154

answers:

3

How would I code a reversible shuffle algorithm in C# which uses a key to shuffle and can be reversed to the original state?

For instance, I have a string: "Hello world", how can I shuffle it so that later I could be able to reverse the shuffled string back to "Hello world".

+1  A: 

You may look at the following question and it's answers. Encrypt/Decrypt string in .NET

Albin Sunnanbo
+1  A: 

Here's a simple implementation of what you need (if I got it well):

public static class ShuffleExtensions
{
    public static int[] GetShuffleExchanges(int size, int key)
    {
        int[] exchanges = new int[size - 1];
        var rand = new Random(key);
        for (int i = size - 1; i > 0; i--)
        {
            int n = rand.Next(i + 1);
            exchanges[size - 1 - i] = n;
        }
        return exchanges;
    }

    public static string Shuffle(this string toShuffle, int key)
    {
        int size = toShuffle.Length;
        char[] chars = toShuffle.ToArray();
        var exchanges = GetShuffleExchanges(size, key);
        for (int i = size - 1; i > 0; i--)
        {
            int n = exchanges[size - 1 - i];
            char tmp = chars[i];
            chars[i] = chars[n];
            chars[n] = tmp;
        }
        return new string(chars);
    }

    public static string DeShuffle(this string shuffled, int key)
    {
        int size = shuffled.Length;
        char[] chars = shuffled.ToArray();
        var exchanges = GetShuffleExchanges(size, key);
        for (int i = 1; i < size; i++)
        {
            int n = exchanges[size - i - 1];
            char tmp = chars[i];
            chars[i] = chars[n];
            chars[n] = tmp;
        }
        return new string(chars);
    }
}

usage:

var originalString = "Hello world";
var shuffled = originalString.Shuffle(123);
var deShuffled = shuffled.DeShuffle(123);

// shuffled = "lelooH rwld";
// deShuffled = "Hello world";

The key must be an integer, if you need to use a string as password, just call GetHashCode() on it:

var shuffled = originalString.Shuffle(myStringKey.GetHashCode());

EDIT:

Now is exactly a Fisher–Yates shuffle algorithm implementation. Thanks to Jeff for the code

digEmAll
Errors are coming in return statements. Can't compile.Error 14 'string' does not contain a definition for 'Shuffle' and no extension method 'Shuffle' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) public static string Shuffle(this string toShuffle, int key){ return new string(toShuffle.Shuffle<char>(key));}public static string DeShuffle(this string toShuffle, int key){ return new string(toShuffle.DeShuffle<char>(key));}
Tush
It's really strange because it works for me...have you set the extensions methods in a static class ?
digEmAll
Yes, its working now. Thanks.
Tush
Ok, stimulated by Steve Jessop's comment about Sort complexity, I've rewritten the code. Now it's exactly Fisher-Yates with O(n) computational complexity in both Shuffling and Deshuffling.
digEmAll
+3  A: 

Look at Fisher-Yates shuffle for a way to permute the string based on a key. Feed the key as the seed into a PRNG, use that to generate the random numbers used by the shuffle.

Now, how to reverse the process? Fisher-Yates works by swapping certain pairs of elements. So to reverse the process you can feed the same key into the same PRNG, then run through the Fisher-Yates algorithm as if you were shuffling an array the size of your string. But actually you don't move anything, just record the indexes of the elements that would be swapped at each stage.

Once you've done this, run through your list of swaps in reverse, applying them to your shuffled string. The result is the original string.

So for example, suppose we've shuffled the string "hello" using the following swaps (I haven't used a PRNG here, I rolled dice, but the point about a PRNG is it gives you the same sequence of numbers given the same seed):

(4,0): "hello" -> "oellh"
(3,3): "oellh" -> "oellh"
(2,1): "oellh" -> "olelh"
(1,0): "olelh" -> "loelh"

So, the shuffled string is "loelh".

To deshuffle, I generate the same series of "random" numbers, 0, 3, 1, 0. Then apply the swaps in reverse order:

(1,0): "loelh" -> "olelh"
(2,1): "olelh" -> "oellh"
(3,3): "oellh" -> "oellh"
(4,0): "oellh" -> "hello"

Success!

The downside of this of course is that it uses a lot of memory for the deshuffle: an array of indexes as long as your original array of chars. So for truly huge arrays, you might want to choose a PRNG (or anyway a sequence-generation function) that can be stepped either forwards or backwards without having to store all the output. This rules out hash-based cryptographically secure PRNGs, but LFSRs are reversible.

Btw, why do you want to do this?

Steve Jessop
It is in practice what I wrote in my answer; what it changes is basically the random exchanges generation part :)
digEmAll
Yes fair point. I looked at your code and saw it was doing something fairly similar, but `GetShuffledIndexes` was tl;dr, and Sort is O(N log N) when we only really need O(N), so I thought I'd go for an English explanation :-)
Steve Jessop
Yes, sometimes I'm too verbose (obscure?) in my code :D
digEmAll
Just that I didn't have any incentive to know exactly what it does. It's going to shuffle the indexes 0 ... N-1. I expect the questioner cares how you do that, or someone maintaining your code, and it looks readable enough. But I don't and I'm not, so I didn't ;-)
Steve Jessop
Hi! I am making my research on Hash based Super Data-Compression which would require these kind of mathematically intense string functions.
Tush
Oh right, cool. So you try to find a key for which the resulting shuffled string compresses well, sort of like Burrows-Wheeler?
Steve Jessop
@Tush - This isn't compression this is shuffleing. Compression is a whole other question.
Wes
@Wes: if you don't see the relevance of shuffling to compression, I suggest you just let Tush and his research supervisor get on with it. I confess I don't really see how the *pseudo-random* shuffle I've described would help compression, but that's what I plan to do ;-)
Steve Jessop