Ответ 1
Решения, основанные на случайной подстроке, не являются хорошими, поскольку выходы будут сталкиваться. Это может произойти преждевременно (с неудачей), и это в конечном итоге произойдет, когда список генерируемых значений станет большим. Это даже не должно быть настолько большим, чтобы вероятность столкновений стала высокой (см. атака дня рождения).
Какая польза для этой проблемы - это псевдослучайная перестановка между инкрементным идентификатором и его партнером, который будет отображаться в URL-адресе. Этот метод гарантирует, что столкновение невозможно, но при этом генерируется в пространство вывода, которое меньше входного.
Реализация
Я предлагаю эту версию С# шифр Feistel с 32-битными блоками, 3 раундами и круглой функцией который вдохновлен псевдослучайными генераторами.
private static double RoundFunction(uint input)
{
// Must be a function in the mathematical sense (x=y implies f(x)=f(y))
// but it doesn't have to be reversible.
// Must return a value between 0 and 1
return ((1369 * input + 150889) % 714025) / 714025.0;
}
private static uint PermuteId(uint id)
{
uint l1=(id>>16)&65535;
uint r1=id&65535;
uint l2, r2;
for (int i = 0; i < 3; i++)
{
l2 = r1;
r2 = l1 ^ (uint)(RoundFunction(r1) * 65535);
l1 = l2;
r1 = r2;
}
return ((r1 << 16) + l1);
}
Чтобы выразить переменный ID в строке base62:
private static string GenerateCode(uint id)
{
return ToBase62(PermuteId(id));
}
Функция Base62
совпадает с предыдущим ответом, за исключением того, что принимает uint
вместо int
(иначе эти функции пришлось бы переписать для решения отрицательных значений).
Настройка алгоритма
RoundFunction
- секретный соус алгоритма. Вы можете изменить его на непубличную версию, возможно, включая секретный ключ. Сеть Feistel имеет две очень приятные свойства:
-
даже если поставляемый
RoundFunction
не обратим, алгоритм гарантирует, чтоPermuteId()
будет перестановкой в математическом смысле (подразумевает нулевое столкновение). -
изменение выражения внутри круглой функции даже незначительно сильно изменит список конечных выходных значений.
Остерегайтесь того, что положить что-то слишком тривиальное в круглое выражение может испортить псевдослучайный эффект, хотя он все равно будет работать с точки зрения уникальности каждого вывода PermuteId
. Кроме того, выражение, которое не было бы функцией в математическом смысле, было бы несовместимо с алгоритмом, поэтому, например, ничего, связанное с random()
, не допускается.
Reversability
В своей текущей форме функция PermuteId
является ее собственным обратным, что означает, что:
PermuteId(PermuteId(id))==id
Поэтому, если вы указали короткую строку, создаваемую программой, если вы переведете ее обратно в uint
с помощью функции FromBase62
и дадите ее как вход в PermuteId()
, которая вернет соответствующий начальный идентификатор. Это довольно круто, если у вас нет базы данных для хранения отношений [internal-ID/shortstring]: их фактически не нужно хранить!
Создание еще более коротких строк
Диапазон вышеуказанной функции - 32 бита, то есть около 4 миллиардов значений от 0 до 2^32-1
. Чтобы выразить этот диапазон в base62, требуется 6 символов.
Имея всего 5 символов, мы можем надеяться представить не более 62^5
значения, что немного меньше 1 миллиарда. Если строка вывода ограничена 5 символами, код должен быть изменен следующим образом:
-
найдите
N
, чтобыN
был четным, а2^N
был как можно выше, но ниже62^5
. Это 28, поэтому наш реальный диапазон вывода, который находится в62^5
, будет2^28
или около 268 миллионов значений. -
в
PermuteId
используйте28/2=14
значения битов дляl1
иr1
вместо 16 бит, при этом старайтесь не игнорировать один бит ввода (который должен быть меньше 2 ^ 28). -
умножьте результат
RoundFunction
на 16383 вместо 65535, чтобы оставаться в пределах 14 бит. -
в конце
PermuteId
, рекомбинируйтеr1
иl1
, чтобы сформировать значение бита14+14=28
вместо 32.
Тот же метод может применяться для 4 символов с диапазоном вывода 2^22
или около 4 миллионов значений.
Как выглядит
В вышеприведенной версии первые 10 строк, начинающихся с id = 1, следующие:
cZ6ahF 3t5mM xGNPN dxwUdS ej9SyV cmbVG3 cOlRkc bfCPOX JDr8Q eg7iuA
Если я делаю тривиальное изменение в круглой функции, это становится:
ey0LlY ddy0ak dDw3wm bVuNbg bKGX22 c0s5GZ dfNMSp ZySqE cxKH4b dNqMDA