I need to generate random string for my automated tests. I use the following Chinese custom class to do it.
public class UniqueIdGenerator
{
static public string GenerateUniqueId(int idLength) // generates uniqueID for all fields (0-175 characters)
{
string uniqueID = "";
string initialID = Guid.NewGuid().ToString().Remove(35);
if (idLength <= 35)
{
uniqueID = Guid.NewGuid().ToString().Remove(idLength);
}
if (idLength > 35 & idLength <= 70)
{
uniqueID = initialID + Guid.NewGuid().ToString().Remove(idLength - 35);
}
if (idLength > 70 & idLength <= 105)
{
uniqueID = initialID + initialID + Guid.NewGuid().ToString().Remove(idLength - 70);
}
if (idLength > 105 & idLength <= 140)
{
uniqueID = initialID + initialID + initialID + Guid.NewGuid().ToString().Remove(idLength - 105);
}
if (idLength > 140 & idLength <= 175)
{
uniqueID = initialID + initialID + initialID + initialID + Guid.NewGuid().ToString().Remove(idLength - 140);
}
return uniqueID;
}
How can I simplify it to use for any natural number?
This is a very bad idea and you should not do this.
First off, you say that you need a random string, but guids are guaranteed to be unique, not random. Guids can be generated randomly, or they can be generated based on the current time, or they can be generated by assigning a range of guids to a particular machine and then generating them sequentially. There are any number of ways that you can generate unique identifiers, and only some of them are random. Guids make no guarantee of being random, so if you need randomness, you are using the wrong tool.
Second, you can't just take parts out of a guid and expect it to still be unique, any more than you can find the part of the airplane that "does the flying" and expect it to fly without the other parts of the airplane attached to it.
If you need a globally unique identifier then make a guid. It might not be random, but it will be unique. It does not need to be any longer, so do not add anything to it. It must not be shorter, otherwise it is no longer guaranteed to be unique
If you need a random string then get a source of randomness -- either pseudo-random or crypto strength, as required -- and generate random characters from the alphabet of your choice, and string them together. You can predict its likelihood of being unique by performing a "birthday problem" collision analysis on it. An inexact but close-enough computation is: take the number of possible characters in the alphabet. Raise that to the power of half the length of the string. That is the number of strings you have to generate before it becomes likely that you've generated at least one string twice.
For example, if you have an alphabet of 0123456789ABCDEF, then that's 16 characters. If the string is 12 letters long, then compute 166. That's about a 16 million, so you're going to run into high likelihood of generating the same string twice in the first 16 million strings generated.
If you want a random string of arbitrary length that is hex characters, you could use:
public static string GetRandomString(int length)
{
var rng = new RNGCryptoServiceProvider();
var padded = (int)Math.Ceiling(length / 2.0m);
var bytes = new byte[padded];
rng.GetBytes(bytes);
return bytes.Aggregate(new StringBuilder(), (f, s) => f.AppendFormat("{0:X2}", s)).ToString(0, length);
}
Try the following:
var builder = new StringBuilder();
while(builder.Length < idLength)
{
builder.Append(Guid.NewGuid().ToString());
}
return builder.ToString(0, idLength);
If all you require is a method capable of generating garbage for testing purposes, then perhaps the following would be more suitable:
private static Random Random = new Random();
public static string GenerateRandomString(int length, string characterSet = "abcdefghijklmnopqrstuvwxyzABCDDEFGHIJKLMNOPQRSTUVWXYZ")
{
var builder = new StringBuilder();
while(builder.Length < length)
{
builder.Append(characterSet.Chars[Random.Next(characterSet.Length)]);
}
return builder.ToString();
}
As an aside, when randomising values for tests, I prefer to still give my properties descriptive names as I find that the fail messages are generally far more helpful. I use an extension method for this purpose:
private static Random Random = new Random();
public static string Randomise(this string value)
{
return value + Random.Next();
}
Which is the useable in my tests like so:
var customer = new Customer { Id = "CustomerId".Randomise() };
This may not be applicable in this case, since a string of a given length is required, however it does save a bit of time if/when a test fails.
Note : A GUID with a user-defined length is no longer a GUID.
That said, if you want to generate a string that resembles a GUID of a given length, try something like the following (untested) code.
string myMethod(int length)
{
StringBuilder myGuidLikeString = new StringBuilder();
while(myGuidLikeString.Length < length)
{
myGuidLikeString .Append(Guid.NewGuid.ToString());
}
return myGuidLikeString.ToString(0,length);
}
Oops - rich.okelly beat me to it with an almost identical method.
Related
I have this code which I think I found somewhere on the internet some years ago and it doesn't quite work.
The purpose is to take any string and from that create a string that is lexicographically sorted by a large number - because then inverse (descending) ordering can be achieved by subtracting the number from another even larger number.
private static BigInteger maxSort = new BigInteger(Encoding.Unicode.GetBytes("5335522543087813528200259404529154678271640415603227881439560533607051111046319775598721171814499900"));
public static string GetSortString(string str, bool descending)
{
var sortNumber = new BigInteger(Encoding.Unicode.GetBytes(str));
if (descending)
{
sortNumber = maxSort - sortNumber;
}
return "$SORT!" + sortNumber.ToString().PadLeft(100, '0') + ":" + str;
}
The reason I need this is because I want to use it to insert as RowKey in Azure Table Storage which is the only way to sort in Table Storage. I need to sort any text, any number and any date, both ascending and descending.
Can anyone see the issue with the code or have any code that serves the same purpose?
The question is tagged with C# but of course this is not a question of syntax so if you have the answer in any other code that would be fine too.
Example
I want to convert any string to a number which is lexicographically sorted correctly - because if it's a number, then I can invert it and sort descending.
So for example, if I can convert:
ABBA to 1234
Beatles to 3131
ZZ Top to 9584
Then those numbers would sort them correctly ... and, if I subtract them from a large number, I would be able to invert the sort order:
10000 - 1234 = 8766
10000 - 3131 = 6869
10000 - 9584 = 0416
Of course, to support longer text input, I need to subtract them from a very large number, which is why I use the very large BigInteger.
Current output from this code
ABBA: $SORT!0000000000000000000000018296156958359617:ABBA
Beatles: $SORT!0000000009111360792640460912278748069954:Beatles
ZZ TOP: $SORT!0000000000000096715522885596192519618650:ZZ TOP
As you can see, the longest text gets the highest number. I have also tried to add padding immediately on the input str, but that didnt help either.
Answer
The accepted answer worked. For descending sort order, the "BigInteger" trick from above could be used.
There is some limitation as to how long the sortable string can be.
Here is the final code:
private static BigInteger maxSort = new BigInteger(Encoding.Unicode.GetBytes("5335522543087813528200259404529154678271640415603227881439560533607051111046319775598721171814499900"));
public static string GetSortString(string str, bool descending)
{
BigInteger result = 0;
int maxLength = 42;
foreach (var c in str.ToCharArray())
{
result = result * 256 + c;
}
for (int i = str.Length; i < maxLength; i++)
{
result = result * 256;
}
if (descending)
{
result = maxSort - result;
}
return "$SORT!" + result;
}
If you were looking for a way to give a a value to any string so that you could sort them accordingly to the number and get the same result as above you can't. The reason is that strings don't have any length limit. Because you can always add a char to a string and thereby get a larger number even through it should have a lower lexicographical value.
If they have a length limit you can do something like this
pseudo code
bignum res = 0;
maxLength = 42;
for (char c : string)
res = res * 256 + c
for (int i = string.length; i < maxLength; i++)
res = res *256
If you want to optimize a bit, the last loop could be a lookup table. If your only using a-z, the times 256 could reduced to 26 or 32.
I need to generate a random alphanumeric of length between 1k bytes to 2K bytes to store it to the csv
I got an error
System.ArgumentOutOfRangeException: 'capacity was less than the
current size. Parameter name: requiredLength'
private const String chars = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
protected void Button1_Click(object sender, EventArgs e)
{
String ID;
String stringContent;
int records = 100000;
StringBuilder csvcontent = new StringBuilder();
for (int r = 0; r < records; r++)
{
Guid guid = Guid.NewGuid();
ID = guid.ToString();
stringContent = GenerateRandomString(256000);
csvcontent.AppendLine(ID + "," + stringContent);
}
File.AppendAllText(csvPath, csvcontent.ToString());
}
File.AppendAllText(csvPath, csvcontent.ToString());
private string GenerateRandomString(int strLength)
{
return new string(Enumerable.Repeat(chars, strLength)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
Is it because the string can't hold that much characters ? Is there any way to fix it ?
Let's do the math here.
You want to write 100.000 rows, with this format:
<guid>,<256.000 characters>
Let's ignore line feed character(s) at the end just for simplicity. The above line is 36+1+256.000 characters long, 256.037 in total. Multiply this by 100.000 rows, and you get a total number of characters of 25.603.700.000 characters. Since a character in memory is stored as a 16-bit unicode codepoint, that means double that in terms of bytes, or 51.207.400.000 bytes, which is roughly 47.7GB of memory.
In one StringBuilder.
This is simply not possible. A StringBuilder has a limit of somehwere around 2.1 million characters, and you want to store 25 billion characters in it.
It's simply not possible, and even if you managed to find a way around it, or the limit for a StringBuilder was changed to handle this, storing this in memory only for then afterwards to dump it to a file is unnecessary. Instead just write it directly to your file to begin with. Here is a literal translation of your code, which can further be simplified somewhat.
protected void Button1_Click(object sender, EventArgs e)
{
int records = 100000;
File.AppendAllLines(csvPath,
(from r in Enumerable.Range(0, records)
let guid = Guid.NewGuid()
let stringContent = GenerateRandomString(256000)
select $"{guid},{stringContent}"));
}
Note that this will add around 27-28GB to the file. I question the usage you could have for a text file of this size, at this point I would probably start to think about a database, because searching for a Guid in this big file is going to have very low performance.
The StringBuilder in .Net has limits. Check the value of:
csvcontent.MaxCapacity;
You will probably get 2,147,483,647 (which is Int32.MaxValue). I say "probably" because Microsoft says that could change in future implementations.
You cannot exceed the size returned from that call (in fact it is slightly smaller due to overhead).
You will need to either break your string into multiple StringBuilders or, better yet, figure out why you are creating such a large string and change your design to not do that, since it is unusual to ever need such a huge string, practically speaking.
Why not just append the data to the file directly inside the loop instead of using the intermediate stringbuilder?
As #JesseChunn already mentioned, I think you should check why you generate such huge strings.
If I am not wrong you are trying to generate nearly 24 GB of random string data, correct?
But if you got still the need to create a big amount of string data, you could write the random strings directly to the file.
e.g.
private const String chars = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
protected void Button1_Click(object sender, EventArgs e)
{
String ID;
byte[] stringContent;
int records = 100000;
using(var textWriter = File.AppendText(csvPath))
{
for (int r = 0; r < records; r++)
{
Guid guid = Guid.NewGuid();
ID = guid.ToString();
stringContent = GenerateRandomString(256000);
textWriter.WriteLine($"{ID}, {stringContent}");
}
}
}
private string GenerateRandomString(int strLength)
{
return new string(Enumerable.Repeat(chars, strLength)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
I am not even sure how to phrase this question so I apologize in advance. I have a form used by our QA. it requires input of serial numbers - a lot of them (sometimes hundreds). I have two text boxes on the form for lower and upper numbers in the range (doesn't have to be this way but it is my best guess). I know how to to do this if it were just integers (see code below) but that is not the only format.Examples of the format could include a date code ("170508/1234") or could include alpha characters (ABC1234). there is a wide variety of formats but in every case, I want to find the range from the last set of numbers (typically last four digits - Like "170508/1234 170508/1235... and ABC1234 ABC1235 ... not to exclude 1234 1235 ....).
Thank you in advance
private void btnSN_Click(object sender, EventArgs e)
{
int from = Convert.ToInt32(txtSnStart.Text.Trim());
int to = Convert.ToInt32(txtSnEnd.Text.Trim());
for (int i = from; i <= to; i++)
{
txtSN.Text += i.ToString() +" ";
}
You can make use of RexEx for this, the RegEx expression below should match any number of digits at the end.:
(\d+)$
Here is the modified code:
// find the group of digits at end of entered text
var fromMatch = Regex.Match(txtSnStart.Text.Trim(), #"(\d+)$");
int from = Convert.ToInt32(fromMatch.Groups[1].Value);
// strip the matched digit group from entered text to get the prefix
string prefix = txtSnStart.Text.Trim().SubString(0, txtSnStart.Text.Trim().LastIndexOf(from.ToString()));
var toMatch = Regex.Match(txtSnEnd.Text.Trim(), #"(\d+)$");
int to = Convert.ToInt32(toMatch.Groups[1].Value);
for (int i = from; i <= to; i++)
{
// combine the prefix and range value
txtSN.Text += string.Format("{0}{1} ", prefix, i.ToString());
}
This problem becomes easier if you break it into smaller pieces.
This will become especially important once it becomes apparent that one small part of the problem might become more complex.
The inputs are a prefix, a number to begin the range, and a number to end the range. Given that, here's a class with a function to return those strings:
public class StringRangeCreator
{
public IEnumerable<string> CreateStringRange(
string prefix, int rangeStart, int rangeEnd)
{
if (rangeStart > rangeEnd) throw new ArgumentException(
$"{nameof(rangeStart)} cannot be greater than {nameof(rangeEnd)}");
return Enumerable.Range(rangeStart, (rangeEnd-rangeStart) + 1)
.Select(n => prefix + n);
}
}
Enumerable.Range creates a range of numbers. The Select takes that range and returns a set of strings consisting of the prefix concatenated with the number in the range.
And a unit test to make sure it works:
[TestClass]
public class DetermineRangeFromLastFourCharacters
{
[TestMethod]
public void ReturnsExpectedRangeOfStrings()
{
var result = new StringRangeCreator().CreateStringRange("abc", 1, 10).ToList();
Assert.AreEqual(10, result.Count);
Assert.AreEqual("abc1", result[0]);
Assert.AreEqual("abc10", result[9]);
}
}
But if these are serial numbers then you maybe you don't want ABC8, ABC9, ABC10. You might want ABC08, ABC09, ABC10. All the same length.
So here's the modified class. I'm guessing a little at what the expected behavior might be. It lets you specify an optional minimum number of digits so that you can pad accordingly:
public class StringRangeCreator
{
public IEnumerable<string> CreateStringRange(
string prefix, int rangeStart, int rangeEnd, int minimumDigits = 1)
{
if (rangeStart > rangeEnd) throw new ArgumentException(
$"{nameof(rangeStart)} cannot be greater than {nameof(rangeEnd)}");
return Enumerable.Range(rangeStart, (rangeEnd-rangeStart) + 1)
.Select(n => prefix + n.ToString("0").PadLeft(minimumDigits, '0'));
}
}
And another unit test:
[TestMethod]
public void PadsNumbersAccordingToParameter()
{
var result = new StringRangeCreator().CreateStringRange("abc", 999, 1001, 3).ToList();
Assert.AreEqual(3, result.Count);
Assert.AreEqual("abc999", result[0]);
Assert.AreEqual("abc1001", result[2]);
}
Some other scenarios you might want to account for are negative numbers or extremely large ranges.
Now that the problem of creating the result set is separated into a class with simple inputs and outputs that you can test, what remains is to take the input from the form and break it down into these inputs. That might include making sure that both strings have the same prefixes and making sure that the last parts of the strings are numbers.
But the whole thing will be a little simpler once it's not all in one big method. That also makes it easier to change when you realize something different about how you want it to work. Validating your input can be one step, and then getting the results can be another.
The unit tests help because you don't want to have to debug the whole thing to see if it works. Enumerable.Range didn't work the way I thought it did, so I had to fix a bug. It was easy to find and fix with the unit tests. It would have been harder if I had to run a whole app and then step through it in the debugger.
I tried to sort Guids generated by UuidCreateSequential, but I see the results are not correct, am I mising something? here is the code
private class NativeMethods
{
[DllImport("rpcrt4.dll", SetLastError = true)]
public static extern int UuidCreateSequential(out Guid guid);
}
public static Guid CreateSequentialGuid()
{
const int RPC_S_OK = 0;
Guid guid;
int result = NativeMethods.UuidCreateSequential(out guid);
if (result == RPC_S_OK)
return guid;
else throw new Exception("could not generate unique sequential guid");
}
static void TestSortedSequentialGuid(int length)
{
Guid []guids = new Guid[length];
int[] ids = new int[length];
for (int i = 0; i < length; i++)
{
guids[i] = CreateSequentialGuid();
ids[i] = i;
Thread.Sleep(60000);
}
Array.Sort(guids, ids);
for (int i = 0; i < length - 1; i++)
{
if (ids[i] > ids[i + 1])
{
Console.WriteLine("sorting using guids failed!");
return;
}
}
Console.WriteLine("sorting using guids succeeded!");
}
EDIT1:
Just to make my question clear, why the guid struct is not sortable using the default comparer ?
EDIT 2:
Also here are some sequential guids I've generated, seems they are not sorted ascending as presented by the hex string
"53cd98f2504a11e682838cdcd43024a7",
"7178df9d504a11e682838cdcd43024a7",
"800b5b69504a11e682838cdcd43024a7",
"9796eb73504a11e682838cdcd43024a7",
"c14c5778504a11e682838cdcd43024a7",
"c14c5779504a11e682838cdcd43024a7",
"d2324e9f504a11e682838cdcd43024a7",
"d2324ea0504a11e682838cdcd43024a7",
"da3d4460504a11e682838cdcd43024a7",
"e149ff28504a11e682838cdcd43024a7",
"f2309d56504a11e682838cdcd43024a7",
"f2309d57504a11e682838cdcd43024a7",
"fa901efd504a11e682838cdcd43024a7",
"fa901efe504a11e682838cdcd43024a7",
"036340af504b11e682838cdcd43024a7",
"11768c0b504b11e682838cdcd43024a7",
"2f57689d504b11e682838cdcd43024a7"
First off, let's re-state the observation: when creating sequential GUIDs with a huge time delay -- 60 billion nanoseconds -- between creations, the resulting GUIDs are not sequential.
am I missing something?
You know every fact you need to know to figure out what is going on. You're just not putting them together.
You have a service that provides numbers which are both sequential and unique across all computers in the universe. Think for a moment about how that is possible. It's not a magic box; someone had to write that code.
Imagine if you didn't have to do it using computers, but instead had to do it by hand. You advertise a service: you provide sequential globally unique numbers to anyone who asks at any time.
Now, suppose I ask you for three such numbers and you hand out 20, 21, and 22. Then sixty years later I ask you for three more and surprise, you give me 13510985, 13510986 and 13510987. "Wait just a minute here", I say, "I wanted six sequential numbers, but you gave me three sequential numbers and then three more. What gives?"
Well, what do you suppose happened in that intervening 60 years? Remember, you provide this service to anyone who asks, at any time. Under what circumstances could you give me 23, 24 and 25? Only if no one else asked within that 60 years.
Now is it clear why your program is behaving exactly as it ought to?
In practice, the sequential GUID generator uses the current time as part of its strategy to enforce the globally unique property. Current time and current location is a reasonable starting point for creating a unique number, since presumably there is only one computer on your desk at any one time.
Now, I caution you that this is only a starting point; suppose you have twenty virtual machines all in the same real machine and all trying to generate sequential GUIDs at the same time? In these scenarios collisions become much more likely. You can probably think of techniques you might use to mitigate collisions in these scenarios.
After researching, I can't sort the guid using the default sort or even using the default string representation from guid.ToString as the byte order is different.
to sort the guids generated by UuidCreateSequential I need to convert to either BigInteger or form my own string representation (i.e. hex string 32 characters) by putting bytes in most signification to least significant order as follows:
static void TestSortedSequentialGuid(int length)
{
Guid []guids = new Guid[length];
int[] ids = new int[length];
for (int i = 0; i < length; i++)
{
guids[i] = CreateSequentialGuid();
ids[i] = i;
// this simulates the delay between guids creation
// yes the guids will not be sequential as it interrupts generator
// (as it used the time internally)
// but still the guids should be in increasing order and hence they are
// sortable and that was the goal of the question
Thread.Sleep(60000);
}
var sortedGuidStrings = guids.Select(x =>
{
var bytes = x.ToByteArray();
//reverse high bytes that represents the sequential part (time)
string high = BitConverter.ToString(bytes.Take(10).Reverse().ToArray());
//set last 6 bytes are just the node (MAC address) take it as it is.
return high + BitConverter.ToString(bytes.Skip(10).ToArray());
}).ToArray();
// sort ids using the generated sortedGuidStrings
Array.Sort(sortedGuidStrings, ids);
for (int i = 0; i < length - 1; i++)
{
if (ids[i] > ids[i + 1])
{
Console.WriteLine("sorting using sortedGuidStrings failed!");
return;
}
}
Console.WriteLine("sorting using sortedGuidStrings succeeded!");
}
Hopefully I understood your question correctly. It seems you are trying to sort the HEX representation of your Guids. That really means that you are sorting them alphabetically and not numerically.
Guids will be indexed by their byte value in the database. Here is a console app to prove that your Guids are numerically sequential:
using System;
using System.Linq;
using System.Numerics;
class Program
{
static void Main(string[] args)
{
//These are the sequential guids you provided.
Guid[] guids = new[]
{
"53cd98f2504a11e682838cdcd43024a7",
"7178df9d504a11e682838cdcd43024a7",
"800b5b69504a11e682838cdcd43024a7",
"9796eb73504a11e682838cdcd43024a7",
"c14c5778504a11e682838cdcd43024a7",
"c14c5779504a11e682838cdcd43024a7",
"d2324e9f504a11e682838cdcd43024a7",
"d2324ea0504a11e682838cdcd43024a7",
"da3d4460504a11e682838cdcd43024a7",
"e149ff28504a11e682838cdcd43024a7",
"f2309d56504a11e682838cdcd43024a7",
"f2309d57504a11e682838cdcd43024a7",
"fa901efd504a11e682838cdcd43024a7",
"fa901efe504a11e682838cdcd43024a7",
"036340af504b11e682838cdcd43024a7",
"11768c0b504b11e682838cdcd43024a7",
"2f57689d504b11e682838cdcd43024a7"
}.Select(l => Guid.Parse(l)).ToArray();
//Convert to BigIntegers to get their numeric value from the Guids bytes then sort them.
BigInteger[] values = guids.Select(l => new BigInteger(l.ToByteArray())).OrderBy(l => l).ToArray();
for (int i = 0; i < guids.Length; i++)
{
//Convert back to a guid.
Guid sortedGuid = new Guid(values[i].ToByteArray());
//Compare the guids. The guids array should be sequential.
if(!sortedGuid.Equals(guids[i]))
throw new Exception("Not sequential!");
}
Console.WriteLine("All good!");
Console.ReadKey();
}
}
I am using a generic class to convert an INT to a X base:
BaseX basex = new BaseX("abcdefghijklmnopqrstuvwxyz");
var a = basex.ToBaseX(1002);
var b = basex.FromBaseX("aghe");
And the BaseX class is as follows:
public class BaseX {
private readonly string _digits;
public BaseX(string digits) {
_digits = digits;
}
public string ToBaseX(int number) {
var output = "";
do {
output = _digits[number % _digits.Length] + output;
number = number / _digits.Length;
}
while (number > 0);
return output;
}
public int FromBaseX(string number) {
return number.Aggregate(0, (a, c) => a * _digits.Length + _digits.IndexOf(c));
}
}
I am using the lowercase base but I can use any other base.
Is it possible to make the output in the base X always the same length?
I think I should use "Multiplicative Inverse" and some similar process with mapping and encoding but I am not sure how to do this ...
Could I get some help to create this?
Basically, my objective is instead of creating random fixed lenght codes to use in promotions or in ID obfuscation just create a fixed length of an INT (The ID on the database).
Thank You,
Miguel
If I understand you correctly you want to pad the generated value with "zeroes". E.g. if you were using plain numbers and you wanted an ID of length 10 and the ID was 1234 the padded ID would be 0000001234.
The simplest way is to pad the generated value. You can add a new method to the BaseX class:
public string ToBaseX(int number, int width) {
var output = ToBaseX(number);
return output.PadLeft(width, _digits[0]);
}
With this method basex.ToBaseX(1002, 10) returns
aaaaaaabmo
and basex.FromBaseX("aaaaaaabmo") returns
1002
In the comments you indicate that the resulting string aaaaaaabmo does not seem very random. But then you can use the approach that Eric Lippert describes in the article A practical use of multiplicative inverses that you are referring to.
First you need to pick an upper limit to the numbers you want to obfuscate (and this number should fit into a 32 bit integer). Eric Lippert uses 1000000000 (1 billion). You then need to pick a number less than the limit that is coprime with the limit (e.g. they do not share any prime factors). Eric Lippert chooses 387420489 (and explains that any number that ends in 9 will be coprime with a number that is a power of 10). You then need to calculate the modular multiplicative inverse of this number, e.g. a number inverse-x that satisfies the following condition:
387420489 * inverse-x = 1 (mod 1000000000)
You can use the extended Euclidian algorithm for this calculation for instance using an online calculator. The modular multiplicative inverse is 513180409.
To obfuscate you number you can use this code (to avoid overflow it is important to perform the calculation using 64 bit integers):
var value = 1002;
var m = 1000000000L;
var x = 387420489L;
var inverseX = 513180409L;
var encoded = value*x%m;
var decoded = encoded*inverseX%m;
For this particular calculation encoded is 195329978.
If you want to use the lower case letters to represent the obfuscated number you can use your BaseX class to convert the number to base 26. You can compute the maximum letters required to represent any number below 1 billion:
Math.Log(1000000000)/Math.Log(26) = 6.36054383137796
This means that you need no more than 7 letters to represent your number.
I have combined all this into two simple methods using some constants you can easily customize:
static class Obfuscator {
const Int64 modulo = 1000000000L;
const Int64 coprime = 280619659L;
const Int64 inverseCoprime = 687208739L;
const String digits = "abcdefghijklmnopqrstuvwxyz";
const Int32 maxDigits = 7; // Math.Log(modulo)/Math.Log(digits.Length) rounded up.
public static String Obfuscate(Int32 originalValue) {
if (originalValue >= modulo || originalValue < 0)
throw new ArgumentOutOfRangeException();
var value = (Int32) (originalValue*coprime%modulo);
var buffer = new Char[maxDigits];
var i = maxDigits;
do {
buffer[--i] = digits[value%digits.Length];
value /= digits.Length;
} while (value > 0);
while (i > 0)
buffer[--i] = digits[0];
return new String(buffer);
}
public static Int32 Deobfuscate(String obfuscatedValue) {
if (String.IsNullOrEmpty(obfuscatedValue))
throw new ArgumentException();
var value = obfuscatedValue
.Aggregate(0, (a, c) => a*digits.Length + digits.IndexOf(c));
return (Int32) (value*inverseCoprime%modulo);
}
}
Only detail to be aware of is that 0 is obfuscated into aaaaaaa. For any number between 1 and 999999999 (inclusive) you get what looks like a random string of 7 characters.