Вспомнил олимпиадное программирование.
Посмотрел по коду:
Для генерации случайной строки размером 10 используется словарь из 9 символов
=> информации в этой строке около 31.7 бит. Что даёт нам 3486784401 разных вариантов. Что спокойно умещается в UInt32
=> задачу можно упростить до генерации ОДНОГО числа в этом диапазоне с последующим форматированием в эту десятисимвольную строку.
Дополнительно можно немного оптимизировать, если для гарантии уникальности использовать не список, а hashset.
А скорость генерации строки и требуемую память - путём выделения буфера на 10 символов заранее.
И ещё немного оптимизации, путём создания одного экземпляра Random
Вот такая реализация получилась:
public sealed class UniqueStringGenerator
{
private const long MAX = 3486784401;
private readonly Random _random;
private readonly HashSet<long> _history;
public UniqueStringGenerator(int seed)
{
_random = new Random(seed);
_history = new HashSet<long>();
}
public UniqueStringGenerator()
{
_random = new Random();
_history = new HashSet<long>();
}
public string Next() => Format(NextNumber());
public void Reset()
{
_history.TrimExcess();
_history.Clear();
}
private long NextNumber()
{
if (_history.Count >= int.MaxValue)
{
throw new InvalidOperationException("Variants exceeded. Please reset");
}
var next = _random.NextInt64(0, MAX);
while (_history.Contains(next))
{
next = _random.NextInt64(0, MAX);
}
_history.Add(next);
return next;
}
private static string Format(long number) => string.Create<long>(10, number,
static (span, number) =>
{
// Алгоритм по-лучше придумать не смог.
// Проходимся по каждому биту числа, понемногу сужая выбор между числами в словаре.
const string DICTIONARY = "123456789";
for (var i = 0; i < span.Length; i++)
{
var range = 0..DICTIONARY.Length;
while (!range.Start.Equals(range.End.Value))
{
var length = range.End.Value - range.Start.Value;
var step = length / 2;
if (step == 0)
{
break;
}
if ((number & 1) == 0)
{
range = range.Start..(range.End.Value - step);
}
else
{
range = (range.Start.Value + step)..range.End;
}
number >>= 1;
}
span[i] = DICTIONARY[range.Start];
}
});
}
100к строк генерирует менее чем за секунду на моём ноуте.
Хотя есть два недостатка (достаточно серьёзных):
1. "1" и "9" - две наиболее редкие цифры
2. последние две цифры - всегда единицы.