@@ -20,11 +20,11 @@ public partial class Hashids : IHashids
2020
2121 private const int MaxNumberHashLength = 12 ; // Length of long.MaxValue;
2222
23- private char [ ] _alphabet ;
24- private long _alphabetLength ;
25- private char [ ] _seps ;
26- private char [ ] _guards ;
27- private char [ ] _salt ;
23+ private readonly char [ ] _alphabet ;
24+ private readonly long _alphabetLength ;
25+ private readonly char [ ] _seps ;
26+ private readonly char [ ] _guards ;
27+ private readonly char [ ] _salt ;
2828 private readonly int _minHashLength ;
2929
3030 private readonly StringBuilderPool _sbPool = new ( ) ;
@@ -36,7 +36,7 @@ public partial class Hashids : IHashids
3636 /// <summary>
3737 /// Instantiates a new Hashids encoder/decoder with defaults.
3838 /// </summary>
39- public Hashids ( ) : this ( string . Empty , 0 , DEFAULT_ALPHABET , DEFAULT_SEPS )
39+ public Hashids ( ) : this ( salt : string . Empty , minHashLength : 0 , alphabet : DEFAULT_ALPHABET , seps : DEFAULT_SEPS )
4040 {
4141 // empty constructor with defaults needed to allow mocking of public methods
4242 }
@@ -65,68 +65,82 @@ public Hashids(
6565 throw new ArgumentNullException ( nameof ( seps ) ) ;
6666
6767 _salt = salt . Trim ( ) . ToCharArray ( ) ;
68- _alphabet = alphabet . ToCharArray ( ) . Distinct ( ) . ToArray ( ) ;
69- _seps = seps . ToCharArray ( ) ;
7068 _minHashLength = minHashLength ;
7169
72- if ( _alphabet . Length < MIN_ALPHABET_LENGTH )
73- throw new ArgumentException ( $ "Alphabet must contain at least { MIN_ALPHABET_LENGTH } unique characters.",
74- nameof ( alphabet ) ) ;
75-
76- SetupSeps ( ) ;
77- SetupGuards ( ) ;
70+ InitCharArrays ( alphabet : alphabet , seps : seps , salt : _salt , alphabetChars : out _alphabet , sepChars : out _seps , guardChars : out _guards ) ;
7871
7972 _alphabetLength = _alphabet . Length ;
8073 }
8174
82- private void SetupSeps ( )
75+ /// <remarks>This method uses <c>out</c> params instead of returning a ValueTuple so it works with .NET 4.6.1.</remarks>
76+ private static void InitCharArrays ( string alphabet , string seps , char [ ] salt , out char [ ] alphabetChars , out char [ ] sepChars , out char [ ] guardChars )
8377 {
84- // seps should contain only characters present in alphabet;
85- _seps = _seps . Intersect ( _alphabet ) . ToArray ( ) ;
78+ alphabetChars = alphabet . ToCharArray ( ) . Distinct ( ) . ToArray ( ) ;
79+ sepChars = seps . ToCharArray ( ) ;
8680
87- // alphabet should not contain seps.
88- _alphabet = _alphabet . Except ( _seps ) . ToArray ( ) ;
81+ if ( alphabetChars . Length < MIN_ALPHABET_LENGTH )
82+ {
83+ throw new ArgumentException ( $ "Alphabet must contain at least { MIN_ALPHABET_LENGTH : N0} unique characters.", paramName : nameof ( alphabet ) ) ;
84+ }
8985
90- ConsistentShuffle ( _seps , _seps . Length , _salt , _salt . Length ) ;
86+ // SetupGuards():
9187
92- if ( _seps . Length == 0 || ( ( float ) _alphabet . Length / _seps . Length ) > SEP_DIV )
88+ // seps should contain only characters present in alphabet:
89+ if ( sepChars . Length > 0 )
90+ {
91+ sepChars = sepChars . Intersect ( alphabetChars ) . ToArray ( ) ;
92+ }
93+
94+ // alphabet should not contain seps // TODO: This comment contradicts the above, it needs rephrasing.
95+ if ( sepChars . Length > 0 )
9396 {
94- var sepsLength = ( int ) Math . Ceiling ( ( float ) _alphabet . Length / SEP_DIV ) ;
97+ alphabetChars = alphabetChars . Except ( sepChars ) . ToArray ( ) ;
98+ }
99+
100+ if ( alphabetChars . Length < ( MIN_ALPHABET_LENGTH - 6 ) ) // TODO: What should the minimum length be after removing chars in `sep`?
101+ {
102+ throw new ArgumentException ( $ "Alphabet must contain at least { MIN_ALPHABET_LENGTH : N0} unique characters that are also not present in .", paramName : nameof ( alphabet ) ) ;
103+ }
104+
105+ ConsistentShuffle ( alphabet : sepChars , alphabetLength : sepChars . Length , salt : salt , saltLength : salt . Length ) ;
106+
107+ if ( sepChars . Length == 0 || ( ( float ) alphabetChars . Length / sepChars . Length ) > SEP_DIV )
108+ {
109+ var sepsLength = ( int ) Math . Ceiling ( ( float ) alphabetChars . Length / SEP_DIV ) ;
95110
96111 if ( sepsLength == 1 )
97112 {
98113 sepsLength = 2 ;
99114 }
100115
101- if ( sepsLength > _seps . Length )
116+ if ( sepsLength > sepChars . Length )
102117 {
103- var diff = sepsLength - _seps . Length ;
104- _seps = _seps . Append ( _alphabet , 0 , diff ) ;
105- _alphabet = _alphabet . SubArray ( diff ) ;
118+ var diff = sepsLength - sepChars . Length ;
119+ sepChars = sepChars . Append ( alphabetChars , 0 , diff ) ;
120+ alphabetChars = alphabetChars . SubArray ( diff ) ;
106121 }
107122 else
108123 {
109- _seps = _seps . SubArray ( 0 , sepsLength ) ;
124+ sepChars = sepChars . SubArray ( 0 , sepsLength ) ;
110125 }
111126 }
112127
113- ConsistentShuffle ( _alphabet , _alphabet . Length , _salt , _salt . Length ) ;
114- }
115-
116- private void SetupGuards ( )
117- {
118- var guardCount = ( int ) Math . Ceiling ( _alphabet . Length / GUARD_DIV ) ;
128+ ConsistentShuffle ( alphabet : alphabetChars , alphabetChars . Length , salt : salt , salt . Length ) ;
129+
130+ // SetupGuards():
131+
132+ var guardCount = ( int ) Math . Ceiling ( alphabetChars . Length / GUARD_DIV ) ;
119133
120- if ( _alphabet . Length < 3 )
134+ if ( alphabetChars . Length < 3 )
121135 {
122- _guards = _seps . SubArray ( 0 , guardCount ) ;
123- _seps = _seps . SubArray ( guardCount ) ;
136+ guardChars = sepChars . SubArray ( index : 0 , length : guardCount ) ;
137+ sepChars = sepChars . SubArray ( index : guardCount ) ;
124138 }
125139
126140 else
127141 {
128- _guards = _alphabet . SubArray ( 0 , guardCount ) ;
129- _alphabet = _alphabet . SubArray ( guardCount ) ;
142+ guardChars = alphabetChars . SubArray ( index : 0 , length : guardCount ) ;
143+ alphabetChars = alphabetChars . SubArray ( index : guardCount ) ;
130144 }
131145 }
132146
0 commit comments