On 15/12/2018 09:56, jf...@ms4.hinet.net wrote:
> Appreciate your thoughtfully analysis on this code. Before generalize it with 
> arbitrary additions, as Peter suggested:-), a recursive version is needed. I 
> may give it a try on this Sunday.
> 
> 
> Avi Gross at 2018/12/15 UTC+8 AM8:13:37 wrote:
>> REAL SUBJECT: Analysis of alternate algorithms.
>>
>> Peter & Jach and anyone interested,
>>
>> As Peter said in his altered subject line, Jack changed directions from 
>> tweaking an algorithm to trying something quite different.
>>
>> Reminder of the problem. 
>>
>> Horizontal View:
>> SEND + MORE = MONEY
>>
>> Vertical View:
>>   SEND
>> +MORE
>> ...........
>> MONEY
>>
>> Hard to be precise as I am sticking to plain text in my message. The three 
>> words above are meant to be right adjusted.
>>
>> When solving it horizontally, Jach and I used variants of a brute force 
>> approach. Substitute all possible combinations. He did it in-line and used 
>> eval. I did it by making lists of items on both sides and summing the int() 
>> of each component and comparing. We tried both our algorithms and his was 
>> slower and he profiled that the cause was that eval() was much more 
>> expensive as were his use of regular expression functions. For comparison, 
>> mine used int() and string manipulation functions and sets and dictionaries.
>>
>> But the real puzzle is meant to be solved in a more vertical way by humans 
>> using logic. I won't do that here but note we have 4 equations going down 
>> but 8 unknowns. And the equations are not unique.
>>
>> The rightmost column (I will call it the first as our arithmetic proceeds 
>> from right to left) is meant to represent ONES and provides the equation:
>>
>> (D+E== Y) or (D+E == Y + 10)
>>
>> Worse, for the next column, you either get a "1" carried from the previous 
>> addition or not and either pass a "1" along to the next column or not. 4 
>> Possibilities.
>>
>> (N+R==E) or (N+R+1==E) or (N+R==E+10) or (N+R+1==E+10)
>>
>> Getting a headache yet?
>>
>> For a human, they need a way to come up with additional info in terms of 
>> constraints.
>>
>> There is a guiding inequality that looks like this:
>>
>> S is not the same as any of the others. Anytime you solve for another, the 
>> list of possible values for S shrinks.
>> Ditto for each other variable.
>> Or, since anything plus 0 is itself, then D and E adding up to Y (with no 
>> possible carry) cannot be 0.
>>
>> But getting a computer to do this might be a bit harder than blunt-force 
>> searches. So let's see why Jach's new algorithm was faster.
>>
>> The code I am analyzing can be viewed in the archives and will not be 
>> entered again:
>>
>> https://mail.python.org/pipermail/python-list/2018-December/738454.html
>>
>> So what did Jach try in his newer version? It is what I call a vertical 
>> approach but one a computer can do easier than a human can or would. I see 
>> it is a very specific algorithm that hard codes in these variables as global 
>> entities that are altered by a set of nested functions. S, E, N, D, M, O, R, 
>> Y. There are approaches that might be better such as passing a dictionary 
>> partially filled out from one function to the next as the only one that 
>> prints the solution is the final function call.
>>
>> So his is not a general solution.
>>
>> What interests me as a probable reason this is faster is the number of 
>> situations it tries. The earlier versions asked itertools.permutations() to 
>> provide all unique combinations of ten tokens in eight positions. So there 
>> were 10 choices for the first and 9 for the second and so on adding up to 
>> 10!/2! or 1,814,400  different combinations tried. That approaches 2 million.
>>
>> Jack broke the problem down into evaluating the units column with a loop 
>> like this:
>>
>> itertools.permutations(range(10), 3)
>>
>> That is 720 possibilities. He then doubled that to 1,440 to consider a 
>> carry. Only if the selected values for the three variables in contention 
>> (plus a carry) does he go on to call to evaluate the tens column.
>>
>> It then shrinks a bit more as he no longer gets the permutations of all 10 
>> digits. He subtracts the three values that might work for the units, so he 
>> is asking for permutations of 7 digits, two at a time. That is 42, doubled 
>> again to 84 for carry purposes. And this function is not called 1,440 times, 
>> but quite a bit fewer. 
>>
>> So, similarly, of those 84 loops for tens, he only sometimes calls to 
>> evaluate hundreds. As mentioned, the set of 10 digits shrinks some more and 
>> this continues upward to functions that evaluate hundreds and thousands  and 
>> finally the one evaluating ten thousands pretty much prints out an answer it 
>> finds. 
>>
>> So overall iterations can be shown to drop. We could add code to measure how 
>> many times each function is called and come up with an exact value for this 
>> built-in problem. I did and the functions were called this many times:
>>
>>>>> counting
>> {'unit': 1, 'ten': 72, 'hundred': 290, 'thou': 183, '10thou': 196}
>>>>> sum(counting.values())
>> 742
>>
>> But I also see the permutation function was called 542 times. Most of those 
>> runs though were fairly trivial as the combined number of items issued back 
>> were only 7,390 as compared to nearly two million in the earlier version. 
>> Overall, this is much faster.
>>
>> Range() is probably a highly efficient built-in written in C, but it can 
>> totally be eliminated in this code as it is a constant. You can write 
>> {1,2,2,3,4,5,6,7,8,9} or [0,1] instead of calculating ranges.
>>
>> Naturally, this code does not scale up to finding the two solutions for:
>>
>> HAWAII + IDAHO +IOWA + OHIO = STATES
>>
>> The horizontal versions solved that easily.
>>
>> The carry issues get more complex here even if a general solution is 
>> written. One approach might be to make a matrix as wide as the widest term 
>> and place all entries in it as characters, right justified. You can then 
>> work on any number of columns by extracting columns backwards from the right 
>> and applying whatever logic, perhaps recursively. The possible carry amounts 
>> need to be estimated as no more than about the number of items being added 
>> minus one.
>>
>> But, I am ready to do other things so I leave it as an exercise for the 
>> reader 😉
>>
>> -----Original Message-----
>> From: Python-list <python-list-bounces+avigross=verizon....@python.org> On 
>> Behalf Of Peter Otten
>> Sent: Friday, December 14, 2018 3:21 AM
>> To: python-list@python.org
>> Subject: Smarter algo, was Re: 03 digression by brute force
>>
>> jf...@ms4.hinet.net wrote:
>>
>>> Just for fun:-) On my ooold PC, it takes 0.047 seconds to run the 
>>> following algorithm on the problem 'SNED + MORE == MONEY".
>>
>>> def tenThousand(u, Cin):  # Cin == M
>>>     global n
>>>     if Cin == M:
>>>         print(S, E, N, D, '+', M, O, R, E, '==', M, O, N, E, Y)
>>>         n += 1
>>
>> And it probably took under two minutes with the brute force approach.
>> So if you were only interested in the result the code below were efficient 
>> only if it took at most two more minutes to write it;)
>>
>> But seriously, the next step is to generalize this to work with arbitrary 
>> additions...
>> More fun!

There are quite a few Python based solvers for alphametics around on the
net, for example:

http://code.activestate.com/recipes/576615-alphametics-solver/

There is also a long discussion here on ways of doing this:

https://enigmaticcode.wordpress.com/2016/06/22/solving-alphametics-with-python/
-- 
https://mail.python.org/mailman/listinfo/python-list

Reply via email to