I am trying to create "truth tables" for use in a Karnaugh map solver. The Karnaugh Map solver takes in the settings of the variables ABCD as a string of 4 numbers representing what they are set to. For example, ABCD could be anything from "0000" to "1111." The solver takes in the minterms (settings of ABCD which equal 1 in the truth table), but it outputs "*000" or other examples with * to represent that the Kmap has removed the first variable in the resulting equation. I am using this for an iterative process, so I need to be able to convert lists of these asterix-filled strings to a list of fully expanded strings :
[*000, 0*00] -> [0000, 1000, 0100]
The way I am currently doing it (with while loops) works but is very slow for the application I am using it for. It takes at least 10 minutes to get through, because I need to do this process around 1000 times (one for each piece of the dataset I am using) It goes quickly until around 430, when it slows significantly because of how long it takes to expand these complicated sequences. I can also confirm that it is not the Karnaugh map implementation I am using, because that solves the same long sequences instantly.
I'm not sure of a more pythonic way to do this which also accounts for things like "*11*", which have multiple spots where the expression needs to be expanded:
[*11*] -> [011*, 111*] -> [0110, 0111, 1110, 1111]
Any ideas which are efficient and pythonic?
My Code:
ast = True
while ast:
new_string_result = copy.deepcopy(string_result)
for i in range(len(string_result)):
for c, char in enumerate(string_result[i]):
if char == "*":
# replace with 0 and 1
new_string_result.append(string_result[i][:c] + "0" + string_result[i][c+1:])
new_string_result.append(string_result[i][:c] + "1" + string_result[i][c+1:])
if "*" in string_result[i]:
# remove original (only once, even if multiple *)
# print("Removing ", string_result[i])
new_string_result.remove(string_result[i])
# print("Kmap result during fix iter: ", new_string_result)
ast_found = False
for i in range(len(new_string_result)):
if "*" in new_string_result[i]:
ast_found = True
# print(ast_found)
ast = ast_found
string_result = new_string_result
I found a couple of ideas that are 2x-3x faster.
Reference code
This is the code from your original question, with a
breakstatement added to remove duplicates from the output. (If you don't do this, then expanding****gives 384 results.)Some comments on this code:
list.remove()is slow. In order to remove an element from a list, it must search through each element of the list, and check if they are equal. Once it finds the match, it has to move every element of the list after that point down one space. Avoid this if possible.for i in range(len(string_result)):, but the variableiis not used for anything besidesstring_result[i]. This is usually slower thanfor elem in string_result:, and more complex.Test data
Here's the data that I used to test each of these. It is ten thousand random strings of
*,0, or1, each ten characters long.(Note: when checking the correctness of all of the solutions below, I ignored duplicates and order of solutions.)
Iterative solution
This one is about 3x faster, mostly from the removal of the inner
forloop.Other optimizations done:
string[:c]twice, do it once and save to a variable.ast_found. It is faster to keep track of whether any modifications were done to the list, and exit if no modifications were made. The downside is that it loops one more time than necessary.*in the string, replace it with a function from the standard library which does the same thing.copy.deepcopy()andlist.remove()by starting with an empty list, and adding elements fromstring_resultwhich don't have any asterisks.Recursive solution
This is about 20% slower than the iterative solution, but it is the shortest and (in my opinion) simplest solution.
Itertools solution
The idea behind this solution is that what you're asking for is a Cartesian product of [0, 1], done n times, where n is the number of asterisks. There's something in the standard library which can find this. This is also 20% slower than the iterative solution.
Benchmark results