Skip to content
This repository has been archived by the owner on Aug 30, 2023. It is now read-only.

Commit

Permalink
Lmao even faster random string
Browse files Browse the repository at this point in the history
  • Loading branch information
hhvrc committed Jul 20, 2023
1 parent 9833f28 commit b00201a
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 33 deletions.
11 changes: 10 additions & 1 deletion Common.Tests/Utils/StringUtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ public sealed class UtilTests
[Fact]
public void GenerateRandomString_Length()
{
Assert.Equal(32, StringUtils.GenerateUrlSafeRandomString(32).Length);
for (int i = 0; i < 512; i++)
{
Assert.Equal(i, StringUtils.GenerateUrlSafeRandomString(i).Length);
}
}

[Fact]
public void GenerateRandomString_InvalidLength()
{
Assert.Throws<ArgumentOutOfRangeException>(() => StringUtils.GenerateUrlSafeRandomString(-1));
}
}
111 changes: 79 additions & 32 deletions Common/Utils/StringUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,90 @@ namespace ZapMe.Utils;

public static class StringUtils
{
private static ReadOnlySpan<char> UrlSafeChars => "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";

private static int GetElementsCount(int length)
{
int fullCycles = length / 16;
length -= fullCycles * 16;

int wasteCycles = length / 5;
length -= wasteCycles * 5;

if (length > 0)
{
wasteCycles++;
}

return (fullCycles * 3) + wasteCycles;
}

private static void FillUrlSafeRandomString(Span<char> buffer, int length)
{
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
int elements = GetElementsCount(length);

int[] array = ArrayPool<int>.Shared.Rent((length / 5) + 1);
try
int[] rented = ArrayPool<int>.Shared.Rent(elements);
Span<int> data = new Span<int>(rented);

using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
Span<int> data = new(array);
rng.GetBytes(MemoryMarshal.AsBytes(data));
}

using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
rng.GetBytes(MemoryMarshal.AsBytes(data));
}
int bits, i = 0, offset = 0;
for (; (offset + 16) <= length; i += 3, offset += 16)
{
int spareBits;

int i = 0;
int j = 0;
do
{
int bitBuffer = data[i++];
bits = data[i + 0];
buffer[offset + 00] = UrlSafeChars[(bits >> 00) & 0x3F];
buffer[offset + 01] = UrlSafeChars[(bits >> 06) & 0x3F];
buffer[offset + 02] = UrlSafeChars[(bits >> 12) & 0x3F];
buffer[offset + 03] = UrlSafeChars[(bits >> 18) & 0x3F];
buffer[offset + 04] = UrlSafeChars[(bits >> 24) & 0x3F];
spareBits = (bits >> 30) & 0x03;

buffer[j + 0] = chars[bitBuffer & 0x3F];
buffer[j + 1] = chars[(bitBuffer >> 6) & 0x3F];
buffer[j + 2] = chars[(bitBuffer >> 12) & 0x3F];
buffer[j + 3] = chars[(bitBuffer >> 18) & 0x3F];
buffer[j + 4] = chars[(bitBuffer >> 24) & 0x3F];
bits = data[i + 1];
buffer[offset + 05] = UrlSafeChars[(bits >> 00) & 0x3F];
buffer[offset + 06] = UrlSafeChars[(bits >> 06) & 0x3F];
buffer[offset + 07] = UrlSafeChars[(bits >> 12) & 0x3F];
buffer[offset + 08] = UrlSafeChars[(bits >> 18) & 0x3F];
buffer[offset + 09] = UrlSafeChars[(bits >> 24) & 0x3F];
spareBits |= (bits >> 28) & 0x0C;

j += 5;
}
while ((j + 5) < length);
bits = data[i + 2];
buffer[offset + 10] = UrlSafeChars[(bits >> 00) & 0x3F];
buffer[offset + 11] = UrlSafeChars[(bits >> 06) & 0x3F];
buffer[offset + 12] = UrlSafeChars[(bits >> 12) & 0x3F];
buffer[offset + 13] = UrlSafeChars[(bits >> 18) & 0x3F];
buffer[offset + 14] = UrlSafeChars[(bits >> 24) & 0x3F];
spareBits |= (bits >> 26) & 0x30;

int remainder = length - j;
if (remainder == 0)
{
return;
}
buffer[offset + 15] = UrlSafeChars[spareBits];
}

for (; (offset + 5) <= length; offset += 5)
{
bits = data[i++];
buffer[offset + 00] = UrlSafeChars[(bits >> 00) & 0x3F];
buffer[offset + 01] = UrlSafeChars[(bits >> 06) & 0x3F];
buffer[offset + 02] = UrlSafeChars[(bits >> 12) & 0x3F];
buffer[offset + 03] = UrlSafeChars[(bits >> 18) & 0x3F];
buffer[offset + 04] = UrlSafeChars[(bits >> 24) & 0x3F];
}

int lastBitBuffer = data[i];
int remainder = length - offset;
if (remainder > 0)
{
bits = data[i++];
do
{
buffer[j++] = chars[(lastBitBuffer >> (--remainder * 6)) & 0x3F];
buffer[offset++] = UrlSafeChars[(bits >> (--remainder * 6)) & 0x3F];
}
while (remainder > 0);
}
finally
{
ArrayPool<int>.Shared.Return(array);
}

ArrayPool<int>.Shared.Return(rented);
}

/// <summary>
Expand All @@ -62,6 +99,16 @@ private static void FillUrlSafeRandomString(Span<char> buffer, int length)
/// <returns></returns>
public static string GenerateUrlSafeRandomString(int length)
{
if (length < 0)
{
throw new ArgumentOutOfRangeException(nameof(length), "Length must be greater than or equal to 0");
}

if (length == 0)
{
return String.Empty;
}

return String.Create(length, length, FillUrlSafeRandomString);
}
}

0 comments on commit b00201a

Please sign in to comment.