In my previous post, I introduced you to the Advent of Code programming challenge, and explained how I’m going through the days, solving both parts of each puzzle. Continuing on, today we have day 11. In this challenge, Santa’s password has expired and it needs to be reset. Santa changes his password by incrementing the last character until the password passes the security requirements. If the character rolls over, then the previous character is incremented, and the process continues for all of the letters of the password. The password requirements are that it is to be eight lower-case letters, it must have two different sets of non-overlapping pairs of letters, a set of three consecutive letters, and it cannot contain the letters “i”, “l” or “o”. We are given the password that just expired, and the task is to get the next password.
My initial inclination is to do this in a set based manner. This would entail taking 8 sets of letters (one set for each character in the string), and each set consists of 23 letters (excluding the three that it cannot contain). Just perform a cross join between these set of letters 8 times, and you will have every possible letter combination, and then just get the ones that match all of the criteria and get the first one that is greater than the starting password. This is a simple, straightforward approach… until you consider just how much data this is- 238 rows… or over 78 billion combinations. Hmm, that doesn’t bode too well. And a quick test of just 5 sets of those 23 characters, and to get the possible matches for those, takes almost two minutes. So, here is the set-based code that I would have used for getting all of the strings that match the criteria across all 8 letters:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
WITH Tens (N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1), Hundreds(N) AS (SELECT 1 FROM Tens t1, Tens t2), Millions(N) AS (SELECT 1 FROM Hundreds t1, Hundreds t2, Hundreds t3), Tally (N) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM Millions) , AllowedChars AS ( SELECT TOP (23) N, CHAR(N) AS Ltr FROM Tally WHERE N BETWEEN 97 AND 97+25 AND CHAR(N) NOT LIKE '[ilo]' ), NewStrings AS ( SELECT t1.Ltr + t2.Ltr + t3.Ltr + t4.Ltr + t5.Ltr + t6.Ltr + t7.Ltr + t8.Ltr AS Input FROM AllowedChars t1, AllowedChars t2, AllowedChars t3, AllowedChars t4, AllowedChars t5, AllowedChars t6, AllowedChars t7, AllowedChars t8 ), cte (Input, MinDblLtr, MaxDblLtr, ConsecutiveLetters) AS ( SELECT Input, MIN(ca1.PosDblLtr) , MAX(ca1.PosDblLtr) , MAX(ca2.PosIncConsecutiveLtrs) --FROM (VALUES (@Input)) dt(Input) FROM NewStrings -- this will get the position of each double letter occurrance, and put all of these into separate rows so that we can use the MIN/MAX functions -- CHARINDEX gets the first occurrence of this string, so if there is a min and max, then there are two different letters that are duplicated. -- If not found (0), set to null so min/max will not get the zero. CROSS APPLY (VALUES (NULLIF(CHARINDEX('aa', Input), 0)), (NULLIF(CHARINDEX('bb', Input), 0)), (NULLIF(CHARINDEX('cc', Input), 0)), (NULLIF(CHARINDEX('dd', Input), 0)), (NULLIF(CHARINDEX('ee', Input), 0)), (NULLIF(CHARINDEX('ff', Input), 0)), (NULLIF(CHARINDEX('gg', Input), 0)), (NULLIF(CHARINDEX('hh', Input), 0)), (NULLIF(CHARINDEX('jj', Input), 0)), (NULLIF(CHARINDEX('kk', Input), 0)), (NULLIF(CHARINDEX('mm', Input), 0)), (NULLIF(CHARINDEX('nn', Input), 0)), (NULLIF(CHARINDEX('pp', Input), 0)), (NULLIF(CHARINDEX('qq', Input), 0)), (NULLIF(CHARINDEX('rr', Input), 0)), (NULLIF(CHARINDEX('ss', Input), 0)), (NULLIF(CHARINDEX('tt', Input), 0)), (NULLIF(CHARINDEX('uu', Input), 0)), (NULLIF(CHARINDEX('vv', Input), 0)), (NULLIF(CHARINDEX('ww', Input), 0)), (NULLIF(CHARINDEX('xx', Input), 0)), (NULLIF(CHARINDEX('yy', Input), 0)), (NULLIF(CHARINDEX('zz', Input), 0)) ) ca1 (PosDblLtr) -- this will get whether there are any consecutive letters present from all of the combinations, and if there are any invalid letters present CROSS APPLY (VALUES ( CHARINDEX('abc', Input) + CHARINDEX('bcd', Input) + CHARINDEX('cde', Input) + CHARINDEX('def', Input) + CHARINDEX('efg', Input ) + CHARINDEX('fgh', Input) + CHARINDEX('pqr', Input) + CHARINDEX('qrs', Input) + CHARINDEX('rst', Input) + CHARINDEX('stu', Input ) + CHARINDEX('tuv', Input) + CHARINDEX('uvw', Input) + CHARINDEX('vwx', Input) + CHARINDEX('wxy', Input) + CHARINDEX('xyz', Input ) ) ) ca2 (PosIncConsecutiveLtrs) GROUP BY NewStrings.Input ) SELECT * FROM cte WHERE cte.MinDblLtr > 0 AND cte.MaxDblLtr > cte.MinDblLtr AND cte.ConsecutiveLetters > 0 ORDER BY Input; |
In this code, a virtual tally table is used to create the list of valid characters, and then it gets the position of each possible double-letter combination, and the three-character string of sequential letters that do not include the characters to not use. For the pairs of letters, each pair is examined to determine if that character pair exists in the string, and if so it returns the starting position of the first occurrence of this pair. All of the pairs are cross-applied, which ends up making 23 rows for each input string. To get whether there are two different sets, the code then gets the MIN and MAX for these. The code also checks to see if there are any of the three-character sequences. It just adds the starting positions up for all possible valid three-character sequences – if this is greater than zero, then there is at least one sequence there. The last part of this is the actual check for validity – it has to have a minimum pair that is greater than zero, a maximum pair that is higher than the minimum, and a 3-character sequence that is greater than zero. To get just the next password, just change this final part to a SELECT TOP (1) and add to the WHERE clause “AND Input > @Input”.
So, this turns out to be one of those things that just plain shouldn’t be run in SQL Server – your front-end applications will do a much better job (just because you can do something in SQL Server doesn’t mean that you should do it). However, since this is a one-off programming challenge, I’m going to go into another way where this can be performed in SQL Server. In this next method, I’m going to start with the specified existing password, and just start incrementing the password until we get a valid password. The code that I’m using for this is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
SET NOCOUNT ON; DECLARE @Input CHAR(8); SET @Input = 'xxyzcdzh'; -- string is required to be in lower case SET @Input = LOWER(@Input); DECLARE @MinDblLtr TINYINT, @MaxDblLtr TINYINT, @ConSecLtr TINYINT, @L1 CHAR(1), @L2 CHAR(1), @L3 CHAR(1), @L4 CHAR(1), @L5 CHAR(1), @L6 CHAR(1), @L7 CHAR(1), @L8 CHAR(1), @ilo TINYINT; DECLARE @aa TINYINT, @bb TINYINT, @cc TINYINT, @dd TINYINT, @ee TINYINT, @ff TINYINT, @gg TINYINT, @hh TINYINT, @jj TINYINT, @kk TINYINT, @mm TINYINT, @nn TINYINT, @pp TINYINT, @qq TINYINT, @rr TINYINT, @ss TINYINT, @tt TINYINT, @uu TINYINT, @vv TINYINT, @ww TINYINT, @xx TINYINT, @yy TINYINT, @zz TINYINT, @abc TINYINT, @bcd TINYINT, @cde TINYINT, @def TINYINT, @efg TINYINT, @fgh TINYINT, @pqr TINYINT, @qrs TINYINT, @rst TINYINT, @stu TINYINT, @tuv TINYINT, @uvw TINYINT, @vwx TINYINT, @wxy TINYINT, @xyz TINYINT; -- see if the current string has any invalid characters. If so, increment that character and set the rest of the string to "a". SET @ilo = PATINDEX('%[ilo]%', @Input); IF @ilo > 0 SET @Input = LEFT(LEFT(@Input, @ilo-1) + CHAR(ASCII(SUBSTRING(@Input, @ilo, 1))+1) + 'aaaaaaaa', 8); SELECT @ConSecLtr = CHARINDEX('abc', @Input)+ CHARINDEX('bcd', @Input)+ CHARINDEX('cde', @Input)+ CHARINDEX('def', @Input)+ CHARINDEX('efg', @Input)+ CHARINDEX('fgh', @Input)+ CHARINDEX('pqr', @Input)+ CHARINDEX('qrs', @Input)+ CHARINDEX('rst', @Input)+ CHARINDEX('stu', @Input)+ CHARINDEX('tuv', @Input)+ CHARINDEX('uvw', @Input)+ CHARINDEX('vwx', @Input)+ CHARINDEX('wxy', @Input)+ CHARINDEX('xyz', @Input), @aa = CHARINDEX('aa', @Input), @bb = CHARINDEX('bb', @Input), @cc = CHARINDEX('cc', @Input), @dd = CHARINDEX('dd', @Input), @ee = CHARINDEX('ee', @Input), @ff = CHARINDEX('ff', @Input), @gg = CHARINDEX('gg', @Input), @hh = CHARINDEX('hh', @Input), @jj = CHARINDEX('jj', @Input), @kk = CHARINDEX('kk', @Input), @mm = CHARINDEX('mm', @Input), @nn = CHARINDEX('nn', @Input), @pp = CHARINDEX('pp', @Input), @qq = CHARINDEX('qq', @Input), @rr = CHARINDEX('rr', @Input), @ss = CHARINDEX('ss', @Input), @tt = CHARINDEX('tt', @Input), @uu = CHARINDEX('uu', @Input), @vv = CHARINDEX('vv', @Input), @ww = CHARINDEX('ww', @Input), @xx = CHARINDEX('xx', @Input), @yy = CHARINDEX('yy', @Input), @zz = CHARINDEX('zz', @Input), @ilo = PATINDEX('%[ilo]%', @Input); SET @MinDblLtr = 10; IF @aa < @MinDblLtr AND @aa > 0 SET @MinDblLtr = @aa; IF @bb < @MinDblLtr AND @bb > 0 SET @MinDblLtr = @bb; IF @cc < @MinDblLtr AND @cc > 0 SET @MinDblLtr = @cc; IF @dd < @MinDblLtr AND @dd > 0 SET @MinDblLtr = @dd; IF @ee < @MinDblLtr AND @ee > 0 SET @MinDblLtr = @ee; IF @ff < @MinDblLtr AND @ff > 0 SET @MinDblLtr = @ff; IF @gg < @MinDblLtr AND @gg > 0 SET @MinDblLtr = @gg; IF @hh < @MinDblLtr AND @hh > 0 SET @MinDblLtr = @hh; IF @jj < @MinDblLtr AND @jj > 0 SET @MinDblLtr = @jj; IF @kk < @MinDblLtr AND @kk > 0 SET @MinDblLtr = @kk; IF @mm < @MinDblLtr AND @mm > 0 SET @MinDblLtr = @mm; IF @nn < @MinDblLtr AND @nn > 0 SET @MinDblLtr = @nn; IF @pp < @MinDblLtr AND @pp > 0 SET @MinDblLtr = @pp; IF @qq < @MinDblLtr AND @qq > 0 SET @MinDblLtr = @qq; IF @rr < @MinDblLtr AND @rr > 0 SET @MinDblLtr = @rr; IF @ss < @MinDblLtr AND @ss > 0 SET @MinDblLtr = @ss; IF @tt < @MinDblLtr AND @tt > 0 SET @MinDblLtr = @tt; IF @uu < @MinDblLtr AND @uu > 0 SET @MinDblLtr = @uu; IF @vv < @MinDblLtr AND @vv > 0 SET @MinDblLtr = @vv; IF @ww < @MinDblLtr AND @ww > 0 SET @MinDblLtr = @ww; IF @xx < @MinDblLtr AND @xx > 0 SET @MinDblLtr = @xx; IF @yy < @MinDblLtr AND @yy > 0 SET @MinDblLtr = @yy; IF @zz < @MinDblLtr AND @zz > 0 SET @MinDblLtr = @zz; SET @MaxDblLtr = 0; IF @aa > @MaxDblLtr AND @aa > @MinDblLtr SET @MaxDblLtr = @aa; IF @bb > @MaxDblLtr AND @bb > @MinDblLtr SET @MaxDblLtr = @bb; IF @cc > @MaxDblLtr AND @cc > @MinDblLtr SET @MaxDblLtr = @cc; IF @dd > @MaxDblLtr AND @dd > @MinDblLtr SET @MaxDblLtr = @dd; IF @ee > @MaxDblLtr AND @ee > @MinDblLtr SET @MaxDblLtr = @ee; IF @ff > @MaxDblLtr AND @ff > @MinDblLtr SET @MaxDblLtr = @ff; IF @gg > @MaxDblLtr AND @gg > @MinDblLtr SET @MaxDblLtr = @gg; IF @hh > @MaxDblLtr AND @hh > @MinDblLtr SET @MaxDblLtr = @hh; IF @jj > @MaxDblLtr AND @jj > @MinDblLtr SET @MaxDblLtr = @jj; IF @kk > @MaxDblLtr AND @kk > @MinDblLtr SET @MaxDblLtr = @kk; IF @mm > @MaxDblLtr AND @mm > @MinDblLtr SET @MaxDblLtr = @mm; IF @nn > @MaxDblLtr AND @mm > @MinDblLtr SET @MaxDblLtr = @nn; IF @pp > @MaxDblLtr AND @pp > @MinDblLtr SET @MaxDblLtr = @pp; IF @qq > @MaxDblLtr AND @qq > @MinDblLtr SET @MaxDblLtr = @qq; IF @rr > @MaxDblLtr AND @rr > @MinDblLtr SET @MaxDblLtr = @rr; IF @ss > @MaxDblLtr AND @ss > @MinDblLtr SET @MaxDblLtr = @ss; IF @tt > @MaxDblLtr AND @tt > @MinDblLtr SET @MaxDblLtr = @tt; IF @uu > @MaxDblLtr AND @uu > @MinDblLtr SET @MaxDblLtr = @uu; IF @vv > @MaxDblLtr AND @vv > @MinDblLtr SET @MaxDblLtr = @vv; IF @ww > @MaxDblLtr AND @ww > @MinDblLtr SET @MaxDblLtr = @ww; IF @xx > @MaxDblLtr AND @xx > @MinDblLtr SET @MaxDblLtr = @xx; IF @yy > @MaxDblLtr AND @yy > @MinDblLtr SET @MaxDblLtr = @yy; IF @zz > @MaxDblLtr AND @zz > @MinDblLtr SET @MaxDblLtr = @zz; SELECT @Input, @MinDblLtr, @MaxDblLtr, @ConSecLtr; WHILE NOT (@MinDblLtr > 0 AND @MaxDblLtr > @MinDblLtr AND @ConSecLtr > 0) -- nope, it doesn't pass the requirements BEGIN -- get the current character at each position of the string SET @L1 = SUBSTRING(@Input, 1, 1); SET @L2 = SUBSTRING(@Input, 2, 1); SET @L3 = SUBSTRING(@Input, 3, 1); SET @L4 = SUBSTRING(@Input, 4, 1); SET @L5 = SUBSTRING(@Input, 5, 1); SET @L6 = SUBSTRING(@Input, 6, 1); SET @L7 = SUBSTRING(@Input, 7, 1); SET @L8 = SUBSTRING(@Input, 8, 1); -- starting with the end of the string, increment the character. -- if the character being incremented = 'z', then set to 'a' and increment the prior character. IF @L8 < 'z' -- if the current character is one shy of the invalid character, then the next one is invalid so skip past it. SET @L8 = CHAR(ASCII(@L8)+1+CASE WHEN @L8 LIKE '[hkn]' THEN 1 ELSE 0 END); ELSE BEGIN SET @L8 = 'a'; IF @L7 < 'z' SET @L7 = CHAR(ASCII(@L7)+1+CASE WHEN @L7 LIKE '[hkn]' THEN 1 ELSE 0 END); ELSE BEGIN SET @L7 = 'a'; IF @L6 < 'z' SET @L6 = CHAR(ASCII(@L6)+1+CASE WHEN @L6 LIKE '[hkn]' THEN 1 ELSE 0 END); ELSE BEGIN SET @L6 = 'a'; IF @L5 < 'z' SET @L5 = CHAR(ASCII(@L5)+1+CASE WHEN @L5 LIKE '[hkn]' THEN 1 ELSE 0 END); ELSE BEGIN SET @L5 = 'a'; IF @L4 < 'z' SET @L4 = CHAR(ASCII(@L4)+1+CASE WHEN @L4 LIKE '[hkn]' THEN 1 ELSE 0 END); ELSE BEGIN SET @L4 = 'a'; IF @L3 < 'z' SET @L3 = CHAR(ASCII(@L3)+1+CASE WHEN @L3 LIKE '[hkn]' THEN 1 ELSE 0 END); ELSE BEGIN SET @L3 = 'a'; IF @L2 < 'z' SET @L2 = CHAR(ASCII(@L2)+1+CASE WHEN @L2 LIKE '[hkn]' THEN 1 ELSE 0 END); ELSE BEGIN SET @L2 = 'a'; IF @L1 < 'z' SET @L1 = CHAR(ASCII(@L1)+1+CASE WHEN @L1 LIKE '[hkn]' THEN 1 ELSE 0 END); END END END END END END END; SET @Input = @L1 + @L2 + @L3 + @L4 + @L5 + @L6 + @L7 + @L8; SELECT @ConSecLtr = CHARINDEX('abc', @Input)+ CHARINDEX('bcd', @Input)+ CHARINDEX('cde', @Input)+ CHARINDEX('def', @Input)+ CHARINDEX('efg', @Input)+ CHARINDEX('fgh', @Input)+ CHARINDEX('pqr', @Input)+ CHARINDEX('qrs', @Input)+ CHARINDEX('rst', @Input)+ CHARINDEX('stu', @Input)+ CHARINDEX('tuv', @Input)+ CHARINDEX('uvw', @Input)+ CHARINDEX('vwx', @Input)+ CHARINDEX('wxy', @Input)+ CHARINDEX('xyz', @Input), @aa = CHARINDEX('aa', @Input), @bb = CHARINDEX('bb', @Input), @cc = CHARINDEX('cc', @Input), @dd = CHARINDEX('dd', @Input), @ee = CHARINDEX('ee', @Input), @ff = CHARINDEX('ff', @Input), @gg = CHARINDEX('gg', @Input), @hh = CHARINDEX('hh', @Input), @jj = CHARINDEX('jj', @Input), @kk = CHARINDEX('kk', @Input), @mm = CHARINDEX('mm', @Input), @nn = CHARINDEX('nn', @Input), @pp = CHARINDEX('pp', @Input), @qq = CHARINDEX('qq', @Input), @rr = CHARINDEX('rr', @Input), @ss = CHARINDEX('ss', @Input), @tt = CHARINDEX('tt', @Input), @uu = CHARINDEX('uu', @Input), @vv = CHARINDEX('vv', @Input), @ww = CHARINDEX('ww', @Input), @xx = CHARINDEX('xx', @Input), @yy = CHARINDEX('yy', @Input), @zz = CHARINDEX('zz', @Input), @ilo = PATINDEX('%[ilo]%', @Input); SET @MinDblLtr = 10; IF @aa < @MinDblLtr AND @aa > 0 SET @MinDblLtr = @aa; IF @bb < @MinDblLtr AND @bb > 0 SET @MinDblLtr = @bb; IF @cc < @MinDblLtr AND @cc > 0 SET @MinDblLtr = @cc; IF @dd < @MinDblLtr AND @dd > 0 SET @MinDblLtr = @dd; IF @ee < @MinDblLtr AND @ee > 0 SET @MinDblLtr = @ee; IF @ff < @MinDblLtr AND @ff > 0 SET @MinDblLtr = @ff; IF @gg < @MinDblLtr AND @gg > 0 SET @MinDblLtr = @gg; IF @hh < @MinDblLtr AND @hh > 0 SET @MinDblLtr = @hh; IF @jj < @MinDblLtr AND @jj > 0 SET @MinDblLtr = @jj; IF @kk < @MinDblLtr AND @kk > 0 SET @MinDblLtr = @kk; IF @mm < @MinDblLtr AND @mm > 0 SET @MinDblLtr = @mm; IF @nn < @MinDblLtr AND @nn > 0 SET @MinDblLtr = @nn; IF @pp < @MinDblLtr AND @pp > 0 SET @MinDblLtr = @pp; IF @qq < @MinDblLtr AND @qq > 0 SET @MinDblLtr = @qq; IF @rr < @MinDblLtr AND @rr > 0 SET @MinDblLtr = @rr; IF @ss < @MinDblLtr AND @ss > 0 SET @MinDblLtr = @ss; IF @tt < @MinDblLtr AND @tt > 0 SET @MinDblLtr = @tt; IF @uu < @MinDblLtr AND @uu > 0 SET @MinDblLtr = @uu; IF @vv < @MinDblLtr AND @vv > 0 SET @MinDblLtr = @vv; IF @ww < @MinDblLtr AND @ww > 0 SET @MinDblLtr = @ww; IF @xx < @MinDblLtr AND @xx > 0 SET @MinDblLtr = @xx; IF @yy < @MinDblLtr AND @yy > 0 SET @MinDblLtr = @yy; IF @zz < @MinDblLtr AND @zz > 0 SET @MinDblLtr = @zz; SET @MaxDblLtr = 0; IF @aa > @MaxDblLtr AND @aa > @MinDblLtr SET @MaxDblLtr = @aa; IF @bb > @MaxDblLtr AND @bb > @MinDblLtr SET @MaxDblLtr = @bb; IF @cc > @MaxDblLtr AND @cc > @MinDblLtr SET @MaxDblLtr = @cc; IF @dd > @MaxDblLtr AND @dd > @MinDblLtr SET @MaxDblLtr = @dd; IF @ee > @MaxDblLtr AND @ee > @MinDblLtr SET @MaxDblLtr = @ee; IF @ff > @MaxDblLtr AND @ff > @MinDblLtr SET @MaxDblLtr = @ff; IF @gg > @MaxDblLtr AND @gg > @MinDblLtr SET @MaxDblLtr = @gg; IF @hh > @MaxDblLtr AND @hh > @MinDblLtr SET @MaxDblLtr = @hh; IF @jj > @MaxDblLtr AND @jj > @MinDblLtr SET @MaxDblLtr = @jj; IF @kk > @MaxDblLtr AND @kk > @MinDblLtr SET @MaxDblLtr = @kk; IF @mm > @MaxDblLtr AND @mm > @MinDblLtr SET @MaxDblLtr = @mm; IF @nn > @MaxDblLtr AND @mm > @MinDblLtr SET @MaxDblLtr = @nn; IF @pp > @MaxDblLtr AND @pp > @MinDblLtr SET @MaxDblLtr = @pp; IF @qq > @MaxDblLtr AND @qq > @MinDblLtr SET @MaxDblLtr = @qq; IF @rr > @MaxDblLtr AND @rr > @MinDblLtr SET @MaxDblLtr = @rr; IF @ss > @MaxDblLtr AND @ss > @MinDblLtr SET @MaxDblLtr = @ss; IF @tt > @MaxDblLtr AND @tt > @MinDblLtr SET @MaxDblLtr = @tt; IF @uu > @MaxDblLtr AND @uu > @MinDblLtr SET @MaxDblLtr = @uu; IF @vv > @MaxDblLtr AND @vv > @MinDblLtr SET @MaxDblLtr = @vv; IF @ww > @MaxDblLtr AND @ww > @MinDblLtr SET @MaxDblLtr = @ww; IF @xx > @MaxDblLtr AND @xx > @MinDblLtr SET @MaxDblLtr = @xx; IF @yy > @MaxDblLtr AND @yy > @MinDblLtr SET @MaxDblLtr = @yy; IF @zz > @MaxDblLtr AND @zz > @MinDblLtr SET @MaxDblLtr = @zz; END; SELECT @Input, @MinDblLtr, @MaxDblLtr, @ConSecLtr; |
This code starts off by setting a bunch of variables. Into these variables it populates the position of all of the pairs and three-character sequences, and then determines if the password passes the criteria. If it doesn’t, it enters a while loop that increments the password until it does match, and then it exits the loop and returns this new string. This code runs in about 6 seconds, and it produces the correct result.
But can it be made better? Sure, there are a lot of shortcuts that can be coded into this. For instance, if the string doesn’t have any of the requirements, then the shortest string that matches all of the criteria is “aabcc”. The shortest string with the highest pair that can be used is “xxyzz”. So, if the character at position 5 is less than or equal to the position at character 4 and character 4 is no higher than “x”, then the password can be set to end by doubling character 4 (positions 4/5), taking the next two letters (positions 6/7), and then doubling character 7 (at position 8). The problem with this approach is that you have to consider every possible combination of occurrences and locations of these, making this some quite long code. But, in the testing that I did before giving up because of the complexity, it finishes immediately.
For part 2, we need to get the next password. To handle this, I just incremented the last password by one character, and ran the routine again.