Last active
March 10, 2016 19:22
-
-
Save amilos/fbdbc14ebc605c2f1435 to your computer and use it in GitHub Desktop.
Friendly ID generator that creates 10-character string identifiers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Linq; | |
using System.Text; | |
namespace Asseco.MiscUtils | |
{ | |
/* | |
* Based on Firebase ids but modified for shorter ids and base62 alphabet | |
*/ | |
/* | |
* Friendly ID generator that creates 10-character string identifiers with the following properties: | |
* | |
* 1. They contain 30-bits of data from timestamp so that they sort *after* any existing ids. | |
* 2. They contain 30-bits of random data after the timestamp so that IDs won't collide with other clients' IDs. | |
* 3. They sort *lexicographically* (so the timestamp is converted to characters that will sort properly). | |
* 4. They're monotonically increasing. Even if you generate more than one in the same timestamp, the | |
* latter ones will sort after the former ones. We do this by using the previous random bits | |
* but "incrementing" them by 1 (only in the case of a timestamp collision). | |
* 5. With default 5 + 5 characters, in parallel id generation, here are some probabilities of collision | |
* - 1 in million chance of collision with ~90,000 ids per second | |
* - 1 in billion chance of collision with ~3,000 ids per second | |
*/ | |
public class FriendlyId | |
{ | |
const int lenRandom = 5; | |
const int lenTimestamp = 5; | |
const int numChars = lenTimestamp + lenRandom; | |
// UNIX time epoch | |
static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | |
// Modeled after base62 web-safe chars, but ordered by ASCII | |
static string alphabet62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; | |
static long lastPushTime = 0; | |
static int[] lastRandChars = new int[lenRandom]; | |
private static string Generate() | |
{ | |
var id = new char[numChars]; | |
var now = (long)((DateTime.UtcNow - epoch).TotalMilliseconds); | |
bool duplicateTime = now == lastPushTime; | |
lastPushTime = now; | |
//Generate first lenTimestamp characters from timestamp, more than 8 does not make sense | |
for (int i = lenTimestamp - 1; i >= 0; i--) | |
{ | |
id[i] = alphabet62.ElementAt((int)(now % 62)); | |
now = (long)Math.Floor(now / 62.0); | |
} | |
//generate next lenRandom characters from RNG, less than 2 does not make sense | |
if (!duplicateTime) | |
{ | |
for (int i = 0; i < lenRandom; i++) //rand | |
{ | |
lastRandChars[i] = StaticRandom.Next(62); | |
id[lenTimestamp + i] = alphabet62.ElementAt(lastRandChars[i]); | |
} | |
} | |
else | |
{ | |
//rand-1 | |
int i = lenRandom - 1; | |
for (; i >= 0 && lastRandChars[i] == 61; i--) | |
{ | |
lastRandChars[i] = 0; | |
} | |
lastRandChars[i]++; | |
} | |
for (int i = 0; i < lenRandom; i++) | |
{ | |
id[lenTimestamp + i] = alphabet62.ElementAt(lastRandChars[i]); | |
} | |
return new string(id); | |
} | |
public static string Next { get { return Generate(); } } | |
public static string NextFormatted | |
{ | |
get | |
{ | |
StringBuilder sb = new StringBuilder(Generate(), numChars + 1); | |
sb.Insert(5, '-'); | |
return sb.ToString(); | |
} | |
} | |
// Modified from Yoda Time misc utils | |
internal static class StaticRandom | |
{ | |
static Random random = new Random(); | |
static object myLock = new object(); | |
internal static int Next(int max) | |
{ | |
lock (myLock) | |
{ | |
return random.Next(max); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage: