Reversible shuffle algorithm using a key
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?
reverse deterministic shuffle - derive key
Shuffling is just creating a random permutation of a given sequence. The typical way to do that is something like the Fisher-Yates Shuffle that you pointed out. The problem is that the shuffle program generates multiple random numbers based on a seed, and unless you implement the random number generator there's no easy way to reverse the sequence of random numbers.
There is another way to do it. What if you could generate the nth permutation of a sequence directly? That is, given the string "Fast", you define the first few permutations as:
0 Fast
1 Fats
2 Fsat
3 Fsta
... etc. for all 24 permutations
You want a random permutation of those four characters. Select a random number from 0 to 23 and then call a function to generate that permutation.
If you know the key, you can call a different function, again passing that key, to have it reverse the permutation back to the original.
In the fourth article in his series on permutations, Eric Lippert showed how to generate the nth permutation without having to generate all of the permutations that come before it. He doesn't show how to reverse the process, but doing so shouldn't be difficult if you understand how the generator works. It's well worth the time to study the entire series of articles.
If you don't know what the key (i.e. the random number used) is, then deriving the sequence of swaps required to get to the original order is expensive.
Edit
Upon reflection, it just might be possible to derive the key if you're given the original sequence and the transformed sequence. Since you know how far each symbol has moved, you should be able to derive the key. Consider the possible permutations of two letters:
0. ab 1. ba
Now, assign the letter b
the value of 0, and the letter a
the value of 1. What permutation number is ba
? Find a
in the string, swap to the left until it gets to the proper position, and multiply the number of swaps by one.
That's too easy. Consider the next one:
0. abc 1. acb 2. bac
3. cab 4. bca 5. cba
a
is now 2, b
is 1, and c
is 0. Given cab
:
swap a left one space. 1x2 = 2. Result is `acb`
swap b left one space. 1x1 = 1. Result is `abc`
So cab
is permutation #3.
This does assume that your permutation generator numbers the permutations in the same way. It's also not a terribly efficient way of doing things. Worst case will require n(n-1)/2
swaps. You can optimize the swaps by moving things in an array, but it's still an O(n^2) algorithm. Where n
is the length of the sequence. Not terrible for 100 or maybe even 1,000 items. Pretty bad after that, though.
Arraylist Shuffle algorithm using a key
private class myItem
{
public Int32 ID { get; set; }
public Int32 Rand { get; set; }
public String Value { get; set; }
}
private void Main(object sender, EventArgs e)
{
string[] array = new string[] { "alpha", "beta", "gamma", "delta" };
List<myItem> myArray = addKeys(array);//adds keys to array required for randomization and reversing
string[] randomarray = randomize(myArray);
string[] reversedarray = reverse(myArray);
}
private List<myItem> addKeys(string[] array)
{
Random random = new Random();
List<myItem> myArr = new List<myItem>();
for (int i = 0; i < array.Length; i++)
{
myArr.Add(new myItem { ID = i, Rand = random.Next(), Value = array[i] });
}
return myArr;
}
private string[] randomize(List<myItem> myArray)
{
return (from item in myArray
orderby item.Rand
select item.Value).ToArray();
}
private string[] reverse(List<myItem> myArray)
{
return (from item in myArray
orderby item.ID
select item.Value).ToArray();
}
Arraylist Shuffle algorithm using a key
private class myItem
{
public Int32 ID { get; set; }
public Int32 Rand { get; set; }
public String Value { get; set; }
}
private void Main(object sender, EventArgs e)
{
string[] array = new string[] { "alpha", "beta", "gamma", "delta" };
List<myItem> myArray = addKeys(array);//adds keys to array required for randomization and reversing
string[] randomarray = randomize(myArray);
string[] reversedarray = reverse(myArray);
}
private List<myItem> addKeys(string[] array)
{
Random random = new Random();
List<myItem> myArr = new List<myItem>();
for (int i = 0; i < array.Length; i++)
{
myArr.Add(new myItem { ID = i, Rand = random.Next(), Value = array[i] });
}
return myArr;
}
private string[] randomize(List<myItem> myArray)
{
return (from item in myArray
orderby item.Rand
select item.Value).ToArray();
}
private string[] reverse(List<myItem> myArray)
{
return (from item in myArray
orderby item.ID
select item.Value).ToArray();
}
Can a seeded shuffle be reversed?
Turns out the answer is yes, and pretty simple:
function seeded_unshuffle(array &$items, $seed) {
$items = array_values($items);
mt_srand($seed);
$indices = [];
for ($i = count($items) - 1; $i > 0; $i--) {
$indices[$i] = mt_rand(0, $i);
}
foreach (array_reverse($indices, true) as $i => $j) {
list($items[$i], $items[$j]) = [$items[$j], $items[$i]];
}
}
Just generate the same random number sequence using the known seed, and traverse it in reverse.
Unshuffle an array of int/char
I suggest following algorithm
- Generate a vector
indices
withstd::iota
ofst.size()
elements. - Shuffle that vector with the same engine with the same seed.
- While looping over the shuffled vector, generate a new string
result
whereresult[indices[i]] = st[i]
Related Topics
How Can a Windows Service Determine Its Servicename
Can You Compile C# So It Doesn't Need the .Net Framework at Runtime
Foreach Control in Form, How to Do Something to All the Textboxes in My Form
Active Directory Com Exception - an Operations Error Occurred (0X80072020)
How to Detect the Original MAC Address After It Has Been Spoofed
Adding Elements to an Xml File in C#
How to Run Commands on Ssh Server in C#
How to Convert Namevaluecollection to JSON String
Creating an Anonymous Type Dynamically
Lambda Expressions in Immediate Window for VS2015
Get Just the Domain Name from a Url
How to Get the Colour of a Pixel at X,Y Using C#
How to Asynchronously Read the Standard Output Stream and Standard Error Stream at Once
Testing Equality of Arrays in C#