Alphabet mathematics
Alphabet Mathematics solution v1, Jacob Dlougach.
Winner
Very clean implementation, and the use of coroutines in order to generate permutations was a nice way of handling it.
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 | --[[ Super-fast bruteforcing The description of the algorithm follows: 1. For each alphanumeric operand (left, right and answer) build masks: For example, initialSmth = numericMask(Smth) numericMask(A42B90A) = 0420900 , that is only consider digits. Then calculate mask for each letter: letterMask(A42B90A, A) = 1000001 letterMask(A42B90A, B) = 0001000 2. Go through all possible assignments of letters 3. For each assignment (A => x, B => y) you can get number value with the following formula: N = numericMask + x*letterMask(A) + y*letterMask(B) 4. Check if those numbers are cool for us, and put them into the list if they fit into the equation. Why this actually works so well, or history of making this solution: First when I read the problem, first thing which came to my mind was: "Hey, 10! is less than 4000000, now what I am waiting for, it's simple bruteforcing!!!" Then I sat down and implemented that simple bruteforcing. I used gsub in cycle to replace letters with digits, and then checked if those numbers were good or not. Also I was very optimistic about performance that time, so I even implemented BigIntegers in LUA... It worked on the tests from problem statement, but it would take whole 15 seconds to handle a simple case with only five letters... Then I decided to see, how much it would work if exactly 10 distinct letters were present. After 2 minutes of waiting, I pressed Ctrl-C. Nobody in WoWAce would wait so long. This had to be optimized. I decided, that BigIntegers were not worth taking care of in this problem, so I got rid of them, hoping that there won't be test cases for big numbers. That still hasn't solved my problems. I didn't know, where the bottleneck was, so I started commenting out pieces of code to see, which parts needed optimization. Minutes later it was clear, that the part with replacing letters with digits somehow took the most time. Then I implemented my own function for replacing everything in a single pass. Now it looked much better, only had to wait 40 seconds to get full screen of results. Now parsing and replacing took almost equal amount of time, so even joining both those operations into one function wouldn't dramatically increase performance. I was stuck, and I even thought that it was impossible to do it better than in 20 seconds. I decided to talk to my good old friend, Ilya. He tested my program like I did - looked for slow places and read Lua documentation (he hadn't seen Lua at all before). Then he said "Vrotmnenogi (russian for f*ckmybrain) these aren't integers, these are doubles!!!". Then we decided, that accessing elements of arrays by index slows down everything way too much and it should be avoided at all costs. Also before going to bed Ilya enlighted me about masks. Now it was clear, that with these two improvements my program will fly like engineers on their boots. Indeed, next day, when I implemented all those improvements in calculate function it worked only 10 seconds in worst case. Wonderful! ]] local ZERO_CHAR = string.byte("0"); function calculate(left, operation, right, answer) left = left:reverse() right = right:reverse() answer = answer:reverse() local lettersMaskLeft = {} local lettersMaskRight = {} local lettersMaskAnswer = {} local initialLeft = 0 local initialRight = 0 local initialAnswer = 0 local hasMet = {} for i = 1,#left do local c = left:byte(i) if ( (c >= ZERO_CHAR) and (c < ZERO_CHAR + 10)) then initialLeft = initialLeft + (10^(i-1)) * (c - ZERO_CHAR) else hasMet[c] = true lettersMaskLeft[c] = (lettersMaskLeft[c] or 0) + 10^(i-1) end end for i = 1,#right do local c = right:byte(i) if ( (c >= ZERO_CHAR) and (c < ZERO_CHAR + 10)) then initialRight = initialRight + (10^(i-1)) * (c - ZERO_CHAR) else hasMet[c] = true lettersMaskRight[c] = (lettersMaskRight[c] or 0) + 10^(i-1) end end for i = 1,#answer do local c = answer:byte(i) if ( (c >= ZERO_CHAR) and (c < ZERO_CHAR + 10)) then initialAnswer = initialAnswer + (10^(i-1)) * (c - ZERO_CHAR) else hasMet[c] = true lettersMaskAnswer[c] = (lettersMaskAnswer[c] or 0) + 10^(i-1) end end local minLeft = 10 ^ (#left - 1) local minRight = 10 ^ (#right - 1) local minAnswer = 10 ^ (#answer - 1) local metLetters = {} for k,v in pairs(hasMet) do table.insert(metLetters, k) end if (table.maxn(metLetters) > 10) then return -- Impossible to do the matching (Dirichlet's box principle) end while (table.maxn(metLetters) < 10) do table.insert(metLetters, 0) -- Filling metLetters with inexistent char to simplify permutations bruteforcing end table.sort(metLetters) local result = {} local doPlus = (operation == "+") or (operation == "?") local doMinus = (operation == "-") or (operation == "?") local doMult = (operation == "*") or (operation == "?") local doPow = (operation == "^") or (operation == "?") local l, r, a for p in perm(metLetters) do l = initialLeft r = initialRight a = initialAnswer for i,c in ipairs(p) do if (c ~= 0) then l = l + (lettersMaskLeft[c] or 0) * (i - 1) r = r + (lettersMaskRight[c] or 0) * (i - 1) a = a + (lettersMaskAnswer[c] or 0) * (i - 1) end end -- Make sure that none of the numbers start with a "0" if ( (l >= minLeft) and (r >= minRight) and (a >= minAnswer)) then if (doPlus) then if (l + r == a) then table.insert(result, tostring(l) .. " + " .. tostring(r) .. " = " .. tostring(a)) end end if (doMinus) then if (l - r == a) then table.insert(result, tostring(l) .. " - " .. tostring(r) .. " = " .. tostring(a)) end end if (doMult) then if (l * r == a) then table.insert(result, tostring(l) .. " * " .. tostring(r) .. " = " .. tostring(a)) end end if (doPow) then if (l ^ r == a) then table.insert(result, tostring(l) .. " ^ " .. tostring(r) .. " = " .. tostring(a)) end end end end table.sort(result) for i,lres in ipairs(result) do print(lres) end end function arrayReverse(a, first, last) local i = first local j = last while (i + 1 < j) do a[i],a[j] = a[j],a[i] i = i + 1 j = j - 1 end end function perm(a) local prev, i, n -- avoid creating new variables each time n = table.getn(a) function permgen(a) -- using next_permutation approach while true do coroutine.yield(a) prev = a[1]; i = nil for j,v in ipairs(a) do if (v > prev) then i = j prev = v break end prev = v end if (i == nil) then arrayReverse(a, 1, n) return end for j,v in ipairs(a) do if (v < prev) then a[i],a[j] = a[j],a[i] break end end arrayReverse(a, 1, i - 1) end end return coroutine.wrap(function() permgen(a) end) end -- Testing code begins here -- calculate("ABCDEFGHI", "+", "IHGFEDCBA", "1JJJJJJJJJ") -- calculate("ABCD", "+", "BDC", "EAEA") -- calculate("AAAAAAAA", "+", "B", "C") |
Facts
- Date created
- 06 Jun 2009
- Updated
- 08 Jun 2009