Python GUI 016 – Cryptarithm Solver, part 7, Normal Solver

Well, we’ve gone through the madness of preparing the digital cryptarithm CONs for solving and it’s finally time to get to solving them. Right now, the GUI only attempts to solve the CONs one at a time, rather than everything in the _processed.txt file one after the other automatically, in part because I’m still in the debugging phase and I haven’t guaranteed that the parser can prep the CONs flawlessly. Also, I’m still wrestling with the hillclimbing code, and I need to do more testing on the higher bases (12 on up). I can always come back to this issue later on.

Say the user is looking at the following CON.

MULTIPLICATION
READY * MIN = ETROTO; + TTOROM; + NMRYIT = TNROERAO

In the parse_area text widget, we show the following mini-equations that have already been parsed for us:

2|READY|*|N|=|ETROTO
2|READY|*|I|=|TTOROM
2|READY|*|M|=|NMRYIT
2|READY|*|MIN|=|TNROERAO

The user clicks the Solve button. The GUI calls key_stat_data.reinit(), passing c_type, mlist, mbase, mletters, mkey_order and hsettings. key_stat_data is an object of the SolverKeyClass.

class SolverKeyClass():
... key = make_key(10)
... master_list = []
... done = False
... crypt_type = 0
... base = 10
... letters = ''
... key_order = ''
... hill_settings = ''
... solution = ''
... keyword = ''

... def reinit(self, c_type, mlist, mbase, mletters, mkey_order,
.............. hsettings):
....... self.crypt_type = c_type
....... if c_type == 0: self.key = make_key(mbase)
....... else: self.key = 0
....... self.master_list = mlist
....... self.done = False
....... self.base = mbase
....... self.letters = mletters
....... self.key_order = mkey_order
....... self.hill_settings = hsettings
....... self.solution = ''
....... self.keyword = ''

... def print_stats(self):
....... print('\nkey: %s\nType : %s (0 = Bruteforce, 1 = ' +

............. 'Hillclimb)\nDone: %s' %
............. (self.key, self.crypt_type, self.done))
....... print('Master List: %s\nBase: %s\nLetters: %s\nKey Order: ' +

............. '%s\nsolution: %s\nKeyword: %s' %
............. (self.master_list, self.base, self.letters,

............. self.key_order, self.solution, self.keyword ))

self: A pointer to the object itself.
c_type: 0 = Bruteforce incrementing, 1 = Hillclimbing.
mlist: The list containing our parsed equation strings.
mbase: The counting base the equation is in (default to 10).
mletters: The list of unique letters in the CON.
mkey_order: The order for recovering the keyword (i.e. 0-9).
hsettings: The settings to use for solving via hillclimbing.

reinit() is called by init_normal_cryptarithm(), which in turn is called by solve_con() from cryptarithm_gui.py, to initialize the object’s settings prior to solving each cryptarithm in the file.
print_stats() pretty prints the contents of the object for debugging.

done: Will be set when we either get the solution, or hit the end of the testing conditions (either the end of the key space, or the max loop counts for hillclimbing).
solution: Will contain our found solution, or feedback from the solver.
keyword: Will contain our recovered keyword, based on key_order.

After initializing the key_stat_data object, the GUI will make a call to solve_normal_cryptarithm(), which will look at the c_type member to decide whether to call solve_with_incrementer() or solve_with_hillclimber(). Both functions, which are in this file, will read the contents of key_stat_data, set a 1-second timer, and then start solving the CON. At the end of 1 second, or if they’ve found the solution, both functions will store their current status data to key_stat_data, and return control to solve_normal_cryptarithm().

solve_normal_cryptarithm() will check key_stat_data.done.
If True, display the solution and keyword on screen and set the Done checkbox to “checked.”
Otherwise, display the current key contents to the message widget, and call after() to run solve_normal_cryptarithm() again after 1ms.

At the beginning, solve_with_incrementer() expects the key to be pre-initialized by calling make_key(mbase) from the inc_utils module. Afterwards, each subsequent call of after() causes solve_with_incrementer() to read the key from key_stat_data.key to pick up from where it left off.

solve_with_hillclimber() uses the key_stat_data.key field to keep track of how many outer loops it’s already completed, and expects key to be initialized to 0.

Performing the actual calculations:

Using solve_with_incrementer() as an example, say we have the CON

READY * MIN = ETROTO; + TTOROM; + NMRYIT = TNROERAO

This will give us the letters list: READYMINTO

If we just look at the first operation:

2|READY|*|N|=|ETROTO

We will convert READY, N and ETROTO into the following lists containing the positions of each letter in the letters list:

READY .= [0, 1, 2, 3, 4]
N .... = [7]
ETROTO = [1, 8, 0, 9, 8, 9]

These lines will be stored to key_stat_data.master_list by init_normal_cryptarithm() so we don’t have to recalculate them all the time.

Now, say we have the key [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
in our first calculation, we will build up the real integer values of our “variables” as:

READY .= 01234
N .... = 7
ETROTO = 180989

Before doing the actual multiplication, we’ll fail the operation on a sanity check. The ACA rules state that a number can’t start with a 0. Test if READY[0] == 0, which is True. Quit the test and increment the key to try again.

Otherwise, we’d test whether 01234 * 7 == 180989. It doesn’t, so again quit the test and increment the key to try again.

Now, how do we know that we need to do a multiplication versus addition or subtraction? The answer is that in creating the master matrix, we also turn the operators from text characters to numeric representations in the parse() method:

'+': op_val = 0
'-': op_val = 1
'*': op_val = 2
'/': op_val = 3
'^2': op_val = 12
'^3': op_val = 13
'^4': op_val = 14
'^5': op_val = 15
'^6': op_val = 16
'-2': op_val = 22
'-3': op_val = 23
'-4': op_val = 24
'-5': op_val = 25
'-6': op_val = 26

That way, when we call do_operation(list_val, base, key), we have all the pieces in place.

If list_val = [2, [0, 1, 2, 3, 4], 2, [7], -1, [1, 8, 0, 9, 8, 9]]

then we have two arguments and the total is in element 5.

param_cnt = list_val[0]
param1 .. = print_val(list_val[1], base, key)
operator .= list_val[2]
param2 .. = print_val(list_val[3], base, key)
total ... = print_val(list_val[5], base, key)

The -1 character in list_val[4] (the “=”‘s place) is just a dummy placeholder.
Testing operator, we see the value is 2, which tells us to do a multiplication.

if operator == 2: # Multiplication
... hold = param1 * param2

Then we return a boolean showing whether our “hold” variable equals our proposed total.

... return hold == total

If list_val[0] is 3, then we have three arguments for addition or subtraction, and we need to look at the operators in list_val[2] and list_val[4] to see if they’re each either a 0 or a 1.

Hillclimbing:
The theory behind hillclimbing is that we can start with a random key and try to solve the equations immediately. We swap two digits at random to see if that gets us closer to the correct answer, and hope that eventually we get the solution that way. The pseudo-code is:

1) Set the inner and outer loop counters to 0. Make the inner max limit 500, and the outer max limit 5000.
2) Randomize the key. Apply the key to the parsed equations and calculate the total absolute errors for all of the equations. That is, if we have a + b = c, a + d = e and b + c = f, set diff_original to abs(a + b – c) + abs(a + d – e) + abs(b + c – f).
3) Swap two of the digits in the key, apply the key to the equations, and calculate the absolute differences again (diff_new).
4) If diff_new = 0, quit with “solution found.”
5) If diff_new < diff_original, keep the swap, (diff_original = diff_new), set the inner counter to 0 and go to step 3.
6) Otherwise, swap the digits back and increment the inner counter.
7) If the inner counter is less than the max limit, go to step 3.
8) Otherwise, set the inner counter to 0 and increment the outer counter.
9) If the outer counter is less than the max limit, go to step 2.
10) Otherwise, quit with a “solution not found” error.

Quite often, hillclimbing will get stuck in a local minimum and the only option is to randomize the key and start over. The problem is that the maximum limits for the inner and outer loops may need to be tweaked based on the complexity of the equations, and the size of the number base. Generally, I’ve found for cryptarithms an inner loop of 500, and an outer loop of 5000 is good enough.

To avoid “program not responding errors,” start a timer when we enter the function, and check it when we increment the outer loop. If 1 second has elapsed, store the outer counter value to key_stat_data.key and exit solve_with_hillclimber(). The use of after() in solve_normal_cryptarithm() is the same as described above.

The functions in this file are:
init_normal_cryptarithm(eqns, key_order, notes, hill_settings)
solve_with_incrementer()
solve_with_hillclimber()
get_base(s) parse(msg, letters)
ret_operation(op)
has_leading_zero(list_val, key)
do_operation(list_val, base, key)
do_operation_for_val(list_val, base, key)

Classes:
SolverKeyClass()

Global variables:
test_leading_zero = True
key_stat_data = SolverKeyClass()

################################
# cryptarithm_normal_solver.py #
################################

# Imports

from inc_utils import inc_perm, make_key, print_key
from cryptarithm_utils import (show_keyword, key_to_string, make_string, print_val)
import time
import random

test_leading_zero = True

# Key stats class definition

class SolverKeyClass():
... key = make_key(10)
... master_list = []
... done = False
... crypt_type = 0
... base = 10
... letters = ''
... key_order = ''
... hill_settings = ''
... solution = ''
... keyword = ''

# Define member initialization method.

... def reinit(self, c_type, mlist, mbase, mletters, mkey_order,
.............. hsettings):
....... self.crypt_type = c_type

....... if c_type == 0: self.key = make_key(mbase)
....... else: self.key = 0

....... self.master_list = mlist
....... self.done = False
....... self.base = mbase
....... self.letters = mletters
....... self.key_order = mkey_order
....... self.hill_settings = hsettings
....... self.solution = ''
....... self.keyword = ''

# Define method for pretty printing the member data for debug

... def print_stats(self):
....... print('\nkey: %s\nType : %s (0 = Bruteforce, 1 = ' +
............. 'Hillclimb)\nDone: %s' %
............. (self.key, self.crypt_type, self.done))

....... print('Master List: %s\nBase: %s\nLetters: %s\nKey Order: ' +
............. '%s\nsolution: %s\nKeyword: %s' %
............. (self.master_list, self.base, self.letters,
.............. self.key_order, self.solution, self.keyword))

# Create our key stats object

key_stat_data = SolverKeyClass()

"""
init_normal_cryptarithm() is called by solve_con() in cryptarithm_gui.py. It receives the parsed mini-equations, key order (0-9 or 1-0, etc.), the base value from the notes field, and the hillclimber settings. We'll perform the letter count test to make sure the CON is not malformed, then call reinit() to store our data to key_stat_data.
"""

def init_normal_cryptarithm(eqns, key_order, notes, hill_settings):
... global key_stat_data

... # Debugging statements

... print('---> %s <---' % (eqns))
... print('---> %s <---' % (key_order))
... print('---> %s <---' % (notes))

... # Use get_base() to return the integer value of the counting base

... base = get_base(notes)

... # Get the letter string for the CON

... letters = make_string(eqns)

... # Prepare to build up the equation lists as described above.

... master_list = []
... eqn_ary = eqns.split('\n')
... for e in eqn_ary:

....... if len(e.strip()) > 0:

........... # Call parse() to parse each mini-equation.

........... master_list.append(parse(e, letters))

... print(master_list)

... if len(letters) != base:
....... return False, '%s: Letters not equal base %s' %

.......... (letters, base)

... # Are we using the incrementer or hillclimbing?

... attack_type = 0
... if base >= hill_settings[0]: attack_type = 1

# Load the stats object via reinit(), and print it out for debugging.

... key_stat_data.reinit(attack_type, master_list, base, letters,
........................ key_order, hill_settings)
... key_stat_data.print_stats()

... return True, ''

"""
solve_with_incrementer()

Use inc_perm() to run through the full keyspace in a bruteforce attack. We'll run in 1 second bursts to avoid the "not responding" errors.

Note that accessing key_stat_data directly is slow. Load the stats data to local variables for speed.
"""

def solve_with_incrementer():
... global key_stat_data

... # Load the local variables

... key = key_stat_data.key
... master_list = key_stat_data.master_list
... key_order = key_stat_data.key_order
... base = key_stat_data.base
... letters = key_stat_data.letters

... # Initialization
... # Use stop_on_hit = False to catch multiple solutions, if any.

... stop_on_hit = True
... done_test_all_eqns = False
... start_time = time.time()

... solution_msg = 'No solution found.'

... while not done_test_all_eqns:
....... done_test_eqn = False

....... # work_ptr points to the mini-equation being processed.

....... work_ptr = 0

....... # Loop until proved bad key, or solved CON.

....... while not done_test_eqn:

........... # Normally, leading 0s in digits are disallowed.

........... if test_leading_zero and
............... has_leading_zero(master_list[work_ptr], key):

............... # Got leading 0. Quit this loop.

............... done_test_eqn = True

........... else:

............... # Perform the mini-equation.

............... if not do_operation(master_list[work_ptr], base, key):

# Mini-equation gives wrong answer. Quit this loop.

................... done_test_eqn = True

............... else:

# Passed current mini-equation. Go to next one.

................... work_ptr += 1
................... if work_ptr >= len(master_list):

# No more mini-equations. We've solved the CON.
# Store the recovered key.

....................... key_stat_data.solution = '%s\n%s\n' %
.......................... (letters, print_key(key))
....................... print('%s\n%s\n' % (letters, print_key(key)))

# Build up full solution to display in sol_area widget.

....................... for work_list in master_list:

# For mini-equations with 2 arguments.

........................... if work_list[0] == 2:
............................... disp_str = '%s %s %s = %s (%s)' %

................................. (print_val(work_list[1], base, key),
................................. ret_operation(work_list[2]),

................................. print_val(work_list[3], base, key),
................................. print_val(work_list[5], base, key),

................................. do_operation_for_val(work_list,
.................................... base, key))

............................... print(disp_str)

# Store full solution to var solution.

............................... key_stat_data.solution += '\n%s' %
.................................. disp_str

........................... else:

# For mini-equations with 3 arguments.

............................... disp_str = '%s %s %s %s %s = %s (%s)'
.................................. % (print_val(work_list[1], base,
..................................... key),
..................................... ret_operation(work_list[2]),
..................................... print_val(work_list[3], base,

..................................... key),
..................................... ret_operation(work_list[4]),

..................................... print_val(work_list[5], base,
..................................... key),
..................................... print_val(work_list[7], base,

..................................... key),
..................................... do_operation_for_val(work_list,
..................................... base, key))

............................... print(disp_str)

# Store full solution to var solution.

............................... key_stat_data.solution += '\n%s' %
.................................. disp_str

# Recover the keyword and store to stat data object.

....................... key_stat_data.keyword = '%s' %
.......................... (show_keyword(key, letters, key_order))
....................... print('\nkeyword: %s' % (show_keyword(key,

.......................... letters, key_order)))

# Exit this function with success.

....................... done_test_eqn = True

# Got one solution, quit testing.

....................... if stop_on_hit: done_test_all_eqns = True

# If we've solved the CON, we'll fall through the loops here and
# inc_perm() will have no effect. Otherwise, get the next key value.

....... finished, key = inc_perm(key, base - 1, base - 1, base)

# Check the time. If 1 second passed, store the stats and exit the
# loops.

....... if time.time() - start_time > 1.0:
........... print(key)
........... key_stat_data.key = key
........... key_stat_data.done = False
........... return

# If we've reached the end of the key space, quit and show an error.

....... if finished:
........... print('Failed test')
........... key_stat_data.solution = 'Failed test'

........... key_stat_data.done = True
........... return

# Store the current key list object and stop testing.

... key_stat_data.key = key
... key_stat_data.done = True

... return

"""
solve_with_hillclimber()

Use the hillclimbing settings to control our inner and outer loop maximums. Otherwise, the function works as described above.
"""

def solve_with_hillclimber():
... global key_stat_data

# Load our test status values to local variables for speed.

... looper = key_stat_data.key
... master_list = key_stat_data.master_list
... key_order = key_stat_data.key_order
... base = key_stat_data.base
... letters = key_stat_data.letters

... start_time = time.time()
... done_hillclimbing = False
... key = make_key(base)

... loop_cnt_max = key_stat_data.hill_settings[1]
... hill_cnt_max = key_stat_data.hill_settings[2]

... solution_msg = 'No solution found.'

... key = make_key(base)

# Enter the outer test loop.

... while looper in range(loop_cnt_max):

# Pick a ridiculous starting difference error.

....... hill_cnt = 0
....... total_diff_max = 1000000000

# Enter the inner test loop.

....... done_hillclimbing = False
....... while not done_hillclimbing:

# Default the done flag just in case, and randomize the key.

........... done_hillclimbing = True
........... random.shuffle(key)

# If any of the arguments have leading zeros, try again.

........... for work_list in master_list:
............... if has_leading_zero(work_list, key):
................... done_hillclimbing = False
................... break

# Got a good starting key. Enter the inner loop.

....... while hill_cnt < hill_cnt_max:
........... d1 = 0
........... d2 = d1

........... # Decide which 2 DIFFERENT digits to swap.
........... while d1 == d2:
............... d1, d2 = random.randint(0, base - 1),
........................ random.randint(0, base - 1)

........... #Swap them.
........... key[d1], key[d2] = key[d2], key[d1]
........... skip_loop = False

........... for work_list in master_list:
............... # Test for leading zeros again. If any, try again.
............... if has_leading_zero(work_list, key):
................... key[d1], key[d2] = key[d2], key[d1]
................... skip_loop = True
................... continue

........... if skip_loop:
............... continue

# No leading zeros. Perform calculations and build up error
# differences.

........... diff_max = 0

........... for work_list in master_list:
............... hold = do_operation_for_val(work_list, base, key)
............... if work_list[0] == 2:
................... total = print_val(work_list[5], base, key)
............... else: total = print_val(work_list[7], base, key)

............... diff_max += abs(total - hold)
........... if diff_max == 0:
............... # No errors, we solved the CON. Exit the inner loop.
............... total_diff_max = 0
............... print('---------> Got hit!!! <----------') ............... break

........... if diff_max < total_diff_max:

# The differences are getting smaller. Keep the swap,
# zero the inner counter and keep going.

............... total_diff_max = diff_max
............... hill_cnt = 0

........... else:
# Unuseful swap. Swap the digits back, increment the inner counter
# and try a different swap.

............... key[d1], key[d2] = key[d2], key[d1]
............... hill_cnt += 1

# If we solved the CON, exit the outer loop.

....... if total_diff_max == 0:
........... break

# Increment the outer loop.

....... looper += 1

....... if time.time() - start_time > 1:

# 1 second is up. Save the current stats and leave the function.

........... key_stat_data.key = looper
........... key_stat_data.done = False
........... print(looper)
........... return

... if total_diff_max == 0:

# We're outside all of the loops. Save the solution, set done to True,
# and prep the solutions to the mini-equations for display in the
# sol_area widget.

....... key_stat_data.done = True
....... key_stat_data.keyword = '%s' %

.......... (show_keyword(key, letters, key_order))
....... solution_msg = '%s\n%s\n' % (letters, print_key(key))
....... print('%s\n%s\n' % (letters, print_key(key)))

....... for work_list in master_list:

........... if work_list[0] == 2:

# Display for 2 arguments

............... disp_str = '%s %s %s = %s (%s)' %
.................. (print_val(work_list[1], base, key),
................... ret_operation(work_list[2]),

................... print_val(work_list[3], base, key),
................... print_val(work_list[5], base, key),

................... do_operation_for_val(work_list, base, key))
............... print(disp_str)

............... solution_msg += '\n%s' % disp_str

........... else:

............... # Display for 3 arguments.

............... disp_str = '%s %s %s %s %s = %s (%s)' % (
................... print_val(work_list[1], base, key),

................... ret_operation(work_list[2]),
................... print_val(work_list[3], base, key),

................... ret_operation(work_list[4]),
................... print_val(work_list[5], base, key),
................... print_val(work_list[7], base, key),
................... do_operation_for_val(work_list, base, key))
............... print(disp_str)

............... solution_msg += '\n%s' % disp_str

....... print('\nkeyword: %s' % (show_keyword(key, letters,
.......... key_order)))

# Save the finished solution text to solution.

....... key_stat_data.solution = solution_msg

... else:

# Too bad, so sad. Hit the outer loop max limit without
# solving the CON. Quit out and let the user decide if
# they want to change the hillclimbing settings and try again.

....... print('Failed test')
....... print('\nTime: %s\n\n=========================\n'

.............. % (time.time() - start_time))

....... key_stat_data.solution = 'Failed Test.'
....... key_stat_data.done = True

... return

"""
get_base()

Read the text string from the notes field and return the integer value of the base. I generally just put "BASE 10" or whatever in that field, but there could be other notes there as well. Split the string on spaces, then search the list for the first occurrence of the word "BASE". The actual base number will be +1 from there.
"""

def get_base(s):
... sAry = s.split(' ')
... pos = s.index('BASE') + 1

... return int(sAry[pos])

"""
parse()

I realize now that I could have chosen a better name for this function, given all of the other parsing I've been doing in all of the other files. But, I don't feel like changing the name now.

Take the parsed strings of mini-equations and turn them into lists in the format:
[2, [0, 1, 2, 3, 4], 2, [7], -1, [1, 8, 0, 9, 8, 9]]

for 2 or 3 arguments.

Note that I've chosen the below enums for the operators so that regular operations, powers, and subtraction of powers can be handled in groups of similar things.
"""

def parse(msg, letters):
... msg_list = msg.split('|')

# Start building up ret_list[] with the integer number of arguments.

... ret_list = [int(msg_list[0])]
... op_val = -1

# Look in each list element for an operator.

... for m in msg_list[1:]:
....... if m == '+': op_val = 0
....... elif m == '-': op_val = 1
....... elif m == '*': op_val = 2
....... elif m == '/': op_val = 3
....... elif m == '^2': op_val = 12
....... elif m == '^3': op_val = 13
....... elif m == '^4': op_val = 14
....... elif m == '^5': op_val = 15
....... elif m == '^6': op_val = 16
....... elif m == '-2': op_val = 22
....... elif m == '-3': op_val = 23
....... elif m == '-4': op_val = 24
....... elif m == '-5': op_val = 25
....... elif m == '-6': op_val = 26

# Found an operator. Append it to ret_list.

....... if op_val >= 0:
........... ret_list.append(op_val)
........... op_val = -1

....... elif m == '=':

# Got the equals sign. Null it and keep it as a placeholder.

........... ret_list.append(-1)

....... else:

# Got an argument. Convert the characters to position values in
# the letters variable. Make a new list for each argument.

........... temp = []
........... for ch in m:
............... temp.append(letters.find(ch))

# Append the assembled argument list to ret_list.

........... ret_list.append(temp)

# Done. Return the parsed mini-equation.

... return ret_list

"""
ret_operation() is used when we want to print out the full solution in the sol_area widget. Convert the numeric versions of the arithmetic operations back to characters.
"""

def ret_operation(op):

... ret = '?'
... if op == 0: ret = '+'
... elif op == 1: ret = '-'
... elif op == 2: ret = '*'
... elif op == 3: ret = '/'
... elif op >= 10 and op < 20: ret = '^%s' % (op % 10)

... elif op >= 20 and op < 30: ret = '-%s' % (op % 20)
... return ret

"""
has_leading_zero()
Test if any of the "variables" in the current mini-equation has a leading 0. If so, return True to force the caller to get the next key value. Otherwise, return False to show that we have a "reasonable" key.
"""

def has_leading_zero(list_val, key):
... l1 = key[list_val[1][0]]
... l2 = key[list_val[3][0]]
... l3 = key[list_val[5][0]]

# Assume 2 arguments to start with.

... if l1 == 0 or l2 == 0 or l3 == 0: return True

... if list_val[0] == 3:
# We actually have 3 arguments. Test the total variable in this case.
....... if key[list_val[7][0]] == 0: return True

# We got this far, the key is good.

... return False

"""
do_operation()
This is where we take the list version of the mini-equation and the current key, build up the mini-equation AS an equation, and check whether the left and right sides balance. Return True if they balance, False otherwise.
"""

def do_operation(list_val, base, key):

# Assume we have only 2 arguments, which is the normal case.

... param1 = print_val(list_val[1], base, key)
... param2 = print_val(list_val[3], base, key)
... total = print_val(list_val[5], base, key)
... hold = 0
... ret = False

... if list_val[2] == 0: # Addition
....... hold = param1 + param2
... elif list_val[2] == 1: # Subtraction
....... hold = param1 - param2
... elif list_val[2] == 2: # Multiplication
....... hold = param1 * param2
... elif list_val[2] >= 10 and list_val[2] < 20: # Raised to a power
....... exp = list_val[2] - 10
....... hold = param1 ** exp
... elif list_val[2] >= 20 and list_val[2] < 30:
....... # Subtraction, raised to a power
....... exp = list_val[2] - 20
....... hold = param1 - (param2 ** exp)

... if list_val[0] == 2:
....... # We actually have only 2 arguments. Exit with results of
....... # balance test.
....... return hold == total

... # Nope, got 3 arguments. Keep going.
... # Get argument 3.
... param3 = print_val(list_val[5], base, key)

... if list_val[4] == 0: # Addition
....... hold += param3
... elif list_val[4] == 1: # Subtraction
....... hold -= param3

... # Now exit with the balance test
... return hold == total

"""
do_operation_for_val()
do_operation() lets us do a boolean test on whether the left side of the equation equals the right. When we print out the numeric values of both sides of the equations in the sol_area widget for the full solution, or when we do hillclimbing, we need the numeric results of the left-side operations. do_operation_for_val() returns the results of the mini-equation.
"""

def do_operation_for_val(list_val, base, key):

... # Again, assume we only have 2 arguments.
... param1 = print_val(list_val[1], base, key)
... param2 = print_val(list_val[3], base, key)
... ret = 0

... if list_val[2] == 0: # Addition
....... ret = param1 + param2
... elif list_val[2] == 1: # Subtraction
....... ret = param1 - param2
... elif list_val[2] == 2: # Multiplication
....... ret = param1 * param2
... elif list_val[2] >= 10 and list_val[2] < 20: # Raised to a power
....... exp = list_val[2] - 10
....... ret = param1 ** exp
... elif list_val[2] >= 20 and list_val[2] < 30:
....... # Subtraction, raised to a power.
....... exp = list_val[2] - 20
....... ret = param1 - (param2 ** exp)

... if list_val[0] == 2:
....... # We actually have only 2 arguments. Exit with the results. ....... return ret

... # Nope, got 3 arguments. Keep going.
... # Get argument 3.
... param3 = print_val(list_val[5], base, key)
... if list_val[4] == 0: # Addition
....... ret += param3
... elif list_val[4] == 1: # Subtraction
....... ret -= param3

... # Exit with the results
... return ret

======

Next up, Cons Parser Solutions Exporter.

Published by The Chief

Who wants to know?

Leave a comment

Design a site like this with WordPress.com
Get started