44using System . Text ;
55using System . Text . RegularExpressions ;
66using Microsoft . Extensions . ObjectPool ;
7+ using System . Buffers ;
78
89namespace HashidsNet
910{
@@ -19,7 +20,10 @@ public partial class Hashids : IHashids
1920 private const double SEP_DIV = 3.5 ;
2021 private const double GUARD_DIV = 12.0 ;
2122
23+ private const int MaxNumberHashLength = 12 ; // Length of long.MaxValue;
24+
2225 private char [ ] _alphabet ;
26+ private long _alphabetLength ;
2327 private char [ ] _seps ;
2428 private char [ ] _guards ;
2529 private char [ ] _salt ;
@@ -73,6 +77,8 @@ public Hashids(
7377
7478 SetupSeps ( ) ;
7579 SetupGuards ( ) ;
80+
81+ _alphabetLength = _alphabet . Length ;
7682 }
7783
7884 private void SetupSeps ( )
@@ -85,9 +91,9 @@ private void SetupSeps()
8591
8692 ConsistentShuffle ( _seps , _seps . Length , _salt , _salt . Length ) ;
8793
88- if ( _seps . Length == 0 || ( ( float ) _alphabet . Length / _seps . Length ) > SEP_DIV )
94+ if ( _seps . Length == 0 || ( ( float ) _alphabet . Length / _seps . Length ) > SEP_DIV )
8995 {
90- var sepsLength = ( int ) Math . Ceiling ( ( float ) _alphabet . Length / SEP_DIV ) ;
96+ var sepsLength = ( int ) Math . Ceiling ( ( float ) _alphabet . Length / SEP_DIV ) ;
9197
9298 if ( sepsLength == 1 )
9399 {
@@ -111,7 +117,7 @@ private void SetupSeps()
111117
112118 private void SetupGuards ( )
113119 {
114- var guardCount = ( int ) Math . Ceiling ( _alphabet . Length / GUARD_DIV ) ;
120+ var guardCount = ( int ) Math . Ceiling ( _alphabet . Length / GUARD_DIV ) ;
115121
116122 if ( _alphabet . Length < 3 )
117123 {
@@ -131,7 +137,7 @@ private void SetupGuards()
131137 /// </summary>
132138 /// <param name="numbers">List of integers.</param>
133139 /// <returns>Encoded hash string.</returns>
134- public virtual string Encode ( params int [ ] numbers ) => GenerateHashFrom ( Array . ConvertAll ( numbers , n => ( long ) n ) ) ;
140+ public virtual string Encode ( params int [ ] numbers ) => GenerateHashFrom ( Array . ConvertAll ( numbers , n => ( long ) n ) ) ;
135141
136142 /// <summary>
137143 /// Encodes the provided numbers into a hash string.
@@ -160,7 +166,7 @@ private void SetupGuards()
160166 /// <param name="hash">Hash string to decode.</param>
161167 /// <returns>Array of integers.</returns>
162168 /// <exception cref="T:System.OverflowException">If the decoded number overflows integer.</exception>
163- public virtual int [ ] Decode ( string hash ) => Array . ConvertAll ( GetNumbersFrom ( hash ) , n => ( int ) n ) ;
169+ public virtual int [ ] Decode ( string hash ) => Array . ConvertAll ( GetNumbersFrom ( hash ) , n => ( int ) n ) ;
164170
165171 /// <summary>
166172 /// Decodes the provided hash into numbers.
@@ -202,8 +208,14 @@ public virtual string DecodeHex(string hash)
202208 var numbers = DecodeLong ( hash ) ;
203209
204210 foreach ( var number in numbers )
205- foreach ( var ch in number . ToString ( "X" ) . AsSpan ( ) . Slice ( 1 ) )
206- builder . Append ( ch ) ;
211+ {
212+ var s = number . ToString ( "X" ) ;
213+
214+ for ( var i = 1 ; i < s . Length ; i ++ )
215+ {
216+ builder . Append ( s [ i ] ) ;
217+ }
218+ }
207219
208220 var result = builder . ToString ( ) ;
209221 _sbPool . Return ( builder ) ;
@@ -223,13 +235,14 @@ private string GenerateHashFrom(long[] numbers)
223235
224236 var builder = _sbPool . Get ( ) ;
225237
226- char [ ] buffer = null ;
238+ char [ ] shuffleBuffer = null ;
227239 var alphabet = _alphabet . CopyPooled ( ) ;
240+ var hashBuffer = ArrayPool < char > . Shared . Rent ( MaxNumberHashLength ) ;
228241 try
229242 {
230- var lottery = alphabet [ numbersHashInt % _alphabet . Length ] ;
243+ var lottery = alphabet [ numbersHashInt % _alphabetLength ] ;
231244 builder . Append ( lottery ) ;
232- buffer = CreatePooledBuffer ( _alphabet . Length , lottery ) ;
245+ shuffleBuffer = CreatePooledBuffer ( _alphabet . Length , lottery ) ;
233246
234247 var startIndex = 1 + _salt . Length ;
235248 var length = _alphabet . Length - startIndex ;
@@ -240,17 +253,20 @@ private string GenerateHashFrom(long[] numbers)
240253
241254 if ( length > 0 )
242255 {
243- Array . Copy ( alphabet , 0 , buffer , startIndex , length ) ;
256+ Array . Copy ( alphabet , 0 , shuffleBuffer , startIndex , length ) ;
244257 }
258+
259+ ConsistentShuffle ( alphabet , _alphabet . Length , shuffleBuffer , _alphabet . Length ) ;
260+ var hashLength = BuildReversedHash ( number , alphabet , hashBuffer ) ;
245261
246- ConsistentShuffle ( alphabet , _alphabet . Length , buffer , _alphabet . Length ) ;
247- var last = Hash ( number , alphabet , _alphabet . Length ) ;
248-
249- builder . Append ( last ) ;
262+ for ( var j = hashLength - 1 ; j > - 1 ; j -- )
263+ {
264+ builder . Append ( hashBuffer [ j ] ) ;
265+ }
250266
251267 if ( i + 1 < numbers . Length )
252268 {
253- number %= last [ 0 ] + i ;
269+ number %= hashBuffer [ hashLength - 1 ] + i ;
254270 var sepsIndex = number % _seps . Length ;
255271
256272 builder . Append ( _seps [ sepsIndex ] ) ;
@@ -277,8 +293,8 @@ private string GenerateHashFrom(long[] numbers)
277293
278294 while ( builder . Length < _minHashLength )
279295 {
280- Array . Copy ( alphabet , buffer , _alphabet . Length ) ;
281- ConsistentShuffle ( alphabet , _alphabet . Length , buffer , _alphabet . Length ) ;
296+ Array . Copy ( alphabet , shuffleBuffer , _alphabet . Length ) ;
297+ ConsistentShuffle ( alphabet , _alphabet . Length , shuffleBuffer , _alphabet . Length ) ;
282298 builder . Insert ( 0 , alphabet , halfLength , _alphabet . Length - halfLength ) ;
283299 builder . Append ( alphabet , 0 , halfLength ) ;
284300
@@ -293,36 +309,35 @@ private string GenerateHashFrom(long[] numbers)
293309 finally
294310 {
295311 alphabet . ReturnToPool ( ) ;
296- buffer . ReturnToPool ( ) ;
312+ shuffleBuffer . ReturnToPool ( ) ;
313+ hashBuffer . ReturnToPool ( ) ;
297314 }
298315
299316 var result = builder . ToString ( ) ;
300317 _sbPool . Return ( builder ) ;
301318 return result ;
302319 }
303320
304- private char [ ] Hash ( long input , char [ ] alphabet , int alphabetLength )
321+ private int BuildReversedHash ( long input , char [ ] alphabet , char [ ] hashBuffer )
305322 {
306- var hash = new List < char > ( 4 ) ;
307-
323+ var length = 0 ;
308324 do
309325 {
310- hash . Add ( alphabet [ input % alphabetLength ] ) ;
311- input /= alphabetLength ;
326+ hashBuffer [ length ++ ] = alphabet [ input % _alphabetLength ] ;
327+ input /= _alphabetLength ;
312328 } while ( input > 0 ) ;
313329
314- hash . Reverse ( ) ;
315- return hash . ToArray ( ) ;
330+ return length ;
316331 }
317332
318- private long Unhash ( string input , char [ ] alphabet , int alphabetLength )
333+ private long Unhash ( string input , char [ ] alphabet )
319334 {
320335 long number = 0 ;
321336
322337 for ( var i = 0 ; i < input . Length ; i ++ )
323338 {
324339 var pos = Array . IndexOf ( alphabet , input [ i ] ) ;
325- number = number * alphabetLength + pos ;
340+ number = number * _alphabetLength + pos ;
326341 }
327342
328343 return number ;
@@ -343,55 +358,56 @@ private long[] GetNumbersFrom(string hash)
343358 i = 1 ;
344359 }
345360
346- var result = new List < long > ( ) ;
347361 var hashBreakdown = hashArray [ i ] ;
348- if ( hashBreakdown [ 0 ] != default ( char ) )
349- {
350- var lottery = hashBreakdown [ 0 ] ;
351- hashBreakdown = hashBreakdown . Substring ( 1 ) ;
362+ var lottery = hashBreakdown [ 0 ] ;
352363
353- hashArray = hashBreakdown . Split ( _seps , StringSplitOptions . RemoveEmptyEntries ) ;
364+ if ( lottery == default ( char ) )
365+ return Array . Empty < long > ( ) ;
366+
367+ hashBreakdown = hashBreakdown . Substring ( 1 ) ;
354368
355- char [ ] buffer = null ;
356- var alphabet = _alphabet . CopyPooled ( ) ;
357- try
358- {
359- buffer = CreatePooledBuffer ( _alphabet . Length , lottery ) ;
369+ hashArray = hashBreakdown . Split ( _seps , StringSplitOptions . RemoveEmptyEntries ) ;
360370
361- var startIndex = 1 + _salt . Length ;
362- var length = _alphabet . Length - startIndex ;
371+ var result = new long [ hashArray . Length ] ;
372+ char [ ] buffer = null ;
373+ var alphabet = _alphabet . CopyPooled ( ) ;
374+ try
375+ {
376+ buffer = CreatePooledBuffer ( _alphabet . Length , lottery ) ;
363377
364- for ( var j = 0 ; j < hashArray . Length ; j ++ )
365- {
366- var subHash = hashArray [ j ] ;
378+ var startIndex = 1 + _salt . Length ;
379+ var length = _alphabet . Length - startIndex ;
367380
368- if ( length > 0 )
369- {
370- Array . Copy ( alphabet , 0 , buffer , startIndex , length ) ;
371- }
381+ for ( var j = 0 ; j < hashArray . Length ; j ++ )
382+ {
383+ var subHash = hashArray [ j ] ;
372384
373- ConsistentShuffle ( alphabet , _alphabet . Length , buffer , _alphabet . Length ) ;
374- result . Add ( Unhash ( subHash , alphabet , _alphabet . Length ) ) ;
385+ if ( length > 0 )
386+ {
387+ Array . Copy ( alphabet , 0 , buffer , startIndex , length ) ;
375388 }
376- }
377- finally
378- {
379- alphabet . ReturnToPool ( ) ;
380- buffer . ReturnToPool ( ) ;
381- }
382389
383- if ( EncodeLong ( result . ToArray ( ) ) != hash )
384- {
385- result . Clear ( ) ;
390+ ConsistentShuffle ( alphabet , _alphabet . Length , buffer , _alphabet . Length ) ;
391+ result [ j ] = Unhash ( subHash , alphabet ) ;
386392 }
387393 }
394+ finally
395+ {
396+ alphabet . ReturnToPool ( ) ;
397+ buffer . ReturnToPool ( ) ;
398+ }
399+
400+ if ( EncodeLong ( result ) == hash )
401+ {
402+ return result ;
403+ }
388404
389- return result . ToArray ( ) ;
405+ return Array . Empty < long > ( ) ;
390406 }
391407
392408 private char [ ] CreatePooledBuffer ( int alphabetLength , char lottery )
393409 {
394- var buffer = System . Buffers . ArrayPool < char > . Shared . Rent ( alphabetLength ) ;
410+ var buffer = ArrayPool < char > . Shared . Rent ( alphabetLength ) ;
395411 buffer [ 0 ] = lottery ;
396412 Array . Copy ( _salt , 0 , buffer , 1 , Math . Min ( _salt . Length , alphabetLength - 1 ) ) ;
397413 return buffer ;
0 commit comments