first commit!
This commit is contained in:
commit
9ff2d22e51
2 changed files with 340 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
simplifications.bin
|
||||
|
||||
337
main.py
Executable file
337
main.py
Executable file
|
|
@ -0,0 +1,337 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import readline
|
||||
|
||||
import pickle;
|
||||
|
||||
from heapq import heappush, heappop
|
||||
|
||||
W = 0b0101_0101_0101_0101
|
||||
X = 0b0011_0011_0011_0011
|
||||
Y = 0b0000_1111_0000_1111
|
||||
Z = 0b0000_0000_1111_1111
|
||||
|
||||
MASK = 0b1111_1111_1111_1111
|
||||
|
||||
def pretty(exp):
|
||||
match exp:
|
||||
case ("literal", x):
|
||||
return str(x);
|
||||
case ("variable", x):
|
||||
return x
|
||||
case ("not", inner):
|
||||
return f"(!{pretty(inner)})"
|
||||
case ("or", left, right):
|
||||
return f"({pretty(left)} || {pretty(right)})"
|
||||
case ("nor", left, right):
|
||||
return f"({pretty(left)} !| {pretty(right)})"
|
||||
case ("and", left, right):
|
||||
return f"({pretty(left)} && {pretty(right)})"
|
||||
case ("nand", left, right):
|
||||
return f"({pretty(left)} !& {pretty(right)})"
|
||||
case ("less-than", left, right):
|
||||
return f"({pretty(left)} < {pretty(right)})"
|
||||
case ("less-than-equal", left, right):
|
||||
return f"({pretty(left)} <= {pretty(right)})"
|
||||
case ("greater-than", left, right):
|
||||
return f"({pretty(left)} > {pretty(right)})"
|
||||
case ("greater-than-equal", left, right):
|
||||
return f"({pretty(left)} >= {pretty(right)})"
|
||||
case ("equal", left, right):
|
||||
return f"({pretty(left)} == {pretty(right)})"
|
||||
case ("not-equal", left, right):
|
||||
return f"({pretty(left)} != {pretty(right)})"
|
||||
case _:
|
||||
print(exp);
|
||||
assert(not "TODO");
|
||||
|
||||
def calculate_simplifications():
|
||||
lookup = dict() # truthtable -> expression
|
||||
|
||||
costs = dict() # truthtable -> cost
|
||||
|
||||
todo = [set() for _ in range(100)] # indexed by cost, set of truthtables.
|
||||
|
||||
def prequeue(truthtable, expression, cost):
|
||||
lookup[truthtable] = expression;
|
||||
costs[truthtable] = cost;
|
||||
todo[cost].add(truthtable);
|
||||
|
||||
# literals:
|
||||
prequeue(0b1111_1111_1111_1111, ("literal", 1), cost = 1);
|
||||
prequeue(0b0000_0000_0000_0000, ("literal", 0), cost = 1);
|
||||
|
||||
# variables:
|
||||
prequeue(W, ("variable", "w"), cost = 0);
|
||||
prequeue(X, ("variable", "x"), cost = 0);
|
||||
prequeue(Y, ("variable", "y"), cost = 0);
|
||||
prequeue(Z, ("variable", "z"), cost = 0);
|
||||
|
||||
# completely unnecessary critera, alphabetical variables is more
|
||||
# aesthetically pleasing, says Benson.
|
||||
def is_aesthetically_better(this, that):
|
||||
def extract_variables(exp):
|
||||
match exp:
|
||||
case ("literal", x):
|
||||
return ();
|
||||
case ("variable", x):
|
||||
return (x, )
|
||||
case ("not", inner):
|
||||
return extract_variables(inner);
|
||||
case (_, left, right):
|
||||
return extract_variables(left) + extract_variables(right);
|
||||
case _:
|
||||
print(exp);
|
||||
assert(not "TODO");
|
||||
|
||||
return extract_variables(this) < extract_variables(that);
|
||||
|
||||
unary_operators = {
|
||||
'not': lambda x: ~x,
|
||||
}
|
||||
|
||||
binary_operators = {
|
||||
'or': lambda x, y: x | y,
|
||||
'and': lambda x, y: x & y,
|
||||
|
||||
'nor': lambda x, y: ~(x | y) & MASK,
|
||||
'nand': lambda x, y: ~(x & y) & MASK,
|
||||
|
||||
'less-than': lambda x, y: ~x & y,
|
||||
'less-than-equal': lambda x, y: ~x | y,
|
||||
|
||||
'greater-than': lambda x, y: x & ~y,
|
||||
'greater-than-equal': lambda x, y: x | ~y,
|
||||
|
||||
'equal': lambda x, y: ~(x ^ y),
|
||||
'not-equal': lambda x, y: (x ^ y),
|
||||
}
|
||||
|
||||
min_cost = 0;
|
||||
|
||||
while sum(len(x) for x in todo):
|
||||
truthtables = todo[min_cost];
|
||||
|
||||
if not truthtables:
|
||||
min_cost += 1;
|
||||
continue;
|
||||
|
||||
todo_count = sum(len(x) for x in todo);
|
||||
|
||||
assert(todo_count <= 65536);
|
||||
|
||||
my_truthtable = min(truthtables);
|
||||
truthtables.discard(my_truthtable);
|
||||
my_cost = min_cost;
|
||||
my_expression = lookup[my_truthtable];
|
||||
|
||||
# print(f'{65536 - todo_count} of 65536 ({(65536 - todo_count) / 65536 * 100:.2f}%)');
|
||||
|
||||
print(f'{65536 - todo_count} of 65536 ({(65536 - todo_count) / 65536 * 100:.2f}%): [{my_cost}] {pretty(my_expression)}');
|
||||
|
||||
# print([len(x) for x in todo])
|
||||
|
||||
# print(f'{len(todo)}; {my_cost}; {len(lookup)}')
|
||||
|
||||
# print(f'{my_truthtable:016b}: {my_expression}')
|
||||
# print(f'{my_truthtable:016b}: {pretty(my_expression)}')
|
||||
|
||||
def consider(new_truthtable, new_expression, new_cost):
|
||||
if new_truthtable not in costs:
|
||||
todo[new_cost].add(new_truthtable);
|
||||
|
||||
costs[new_truthtable] = new_cost
|
||||
lookup[new_truthtable] = new_expression
|
||||
elif new_cost < costs[new_truthtable] or (new_cost == costs[new_truthtable] and is_aesthetically_better(new_expression, lookup[new_truthtable])):
|
||||
current_cost = costs[new_truthtable];
|
||||
|
||||
assert(new_cost >= min_cost);
|
||||
|
||||
todo[current_cost].discard(new_truthtable);
|
||||
todo[new_cost].add(new_truthtable);
|
||||
|
||||
costs[new_truthtable] = new_cost
|
||||
lookup[new_truthtable] = new_expression
|
||||
|
||||
# consider unary operators:
|
||||
for name, function in sorted(unary_operators.items()):
|
||||
unary_truthtable = function(my_truthtable) & MASK;
|
||||
unary_expression = (name, my_expression);
|
||||
unary_cost = my_cost + 1;
|
||||
|
||||
consider(unary_truthtable, unary_expression, unary_cost);
|
||||
|
||||
# consider binary operators:
|
||||
for name, function in sorted(binary_operators.items()):
|
||||
for other_truthtable, other_expression in sorted(lookup.items()):
|
||||
# x + y
|
||||
binary_truthtable = function(my_truthtable, other_truthtable);
|
||||
binary_truthtable = binary_truthtable & MASK
|
||||
binary_expression = (name, my_expression, other_expression);
|
||||
binary_cost = my_cost + 1 + costs[other_truthtable];
|
||||
|
||||
consider(binary_truthtable, binary_expression, binary_cost);
|
||||
|
||||
# y + x
|
||||
binary_truthtable = function(other_truthtable, my_truthtable);
|
||||
binary_truthtable = binary_truthtable & MASK
|
||||
binary_expression = (name, other_expression, my_expression);
|
||||
binary_cost = my_cost + 1 + costs[other_truthtable];
|
||||
|
||||
consider(binary_truthtable, binary_expression, binary_cost);
|
||||
|
||||
return costs, lookup
|
||||
|
||||
pathname = "simplifications.bin"
|
||||
|
||||
try:
|
||||
with open(pathname, "rb") as stream:
|
||||
costs, lookup = pickle.load(stream);
|
||||
except FileNotFoundError:
|
||||
print("Oh! looks like you're running this for the first time");
|
||||
print("I'll have to build up my cache of simplifications");
|
||||
print("This may take a while.");
|
||||
print("I'll only have to do this once.");
|
||||
|
||||
print();
|
||||
input("any input to start:");
|
||||
print();
|
||||
|
||||
costs, lookup = calculate_simplifications();
|
||||
|
||||
print();
|
||||
print("done!");
|
||||
print();
|
||||
|
||||
with open(pathname, "wb") as stream:
|
||||
pickle.dump((costs, lookup), stream);
|
||||
|
||||
print();
|
||||
print("saved!");
|
||||
print();
|
||||
|
||||
# for truthtable, exp in lookup.items():
|
||||
# print(f'{truthtable:016b}: {pretty(exp)}')
|
||||
|
||||
def create_parser():
|
||||
import pyparsing as pp
|
||||
|
||||
from pyparsing import Forward, Suppress, Keyword, Group, ZeroOrMore, Optional, Literal
|
||||
|
||||
root = Forward()
|
||||
|
||||
literal = pp.Word('01');
|
||||
|
||||
variable = pp.Word('wxyz')
|
||||
|
||||
highest = literal | variable | (Suppress('(') + root + Suppress(')'));
|
||||
|
||||
prefix_expression = Group(Literal("!") + highest) | highest
|
||||
|
||||
relational_expression = \
|
||||
Group(prefix_expression + Literal('<=') + prefix_expression) \
|
||||
| Group(prefix_expression + Literal('>=') + prefix_expression) \
|
||||
| Group(prefix_expression + Literal('>') + prefix_expression) \
|
||||
| Group(prefix_expression + Literal('<') + prefix_expression) \
|
||||
| Group(prefix_expression + Literal('==') + prefix_expression) \
|
||||
| Group(prefix_expression + Literal('!=') + prefix_expression) \
|
||||
| prefix_expression;
|
||||
|
||||
logical_and_expression = Forward();
|
||||
logical_and_expression <<= \
|
||||
Group(relational_expression + Literal('&&') + logical_and_expression) \
|
||||
| Group(relational_expression + Literal('!&') + logical_and_expression) \
|
||||
| relational_expression;
|
||||
|
||||
logical_or_expression = Forward()
|
||||
logical_or_expression <<= \
|
||||
Group(logical_and_expression + Literal('||') + logical_or_expression) \
|
||||
| Group(logical_and_expression + Literal('!|') + logical_or_expression) \
|
||||
| logical_and_expression;
|
||||
|
||||
root <<= logical_or_expression;
|
||||
return root;
|
||||
|
||||
parser = create_parser();
|
||||
|
||||
print("""
|
||||
Please give a C-style expression using only variables 'w', 'x', 'y' and 'z'.
|
||||
You can use any of the following operators: '!' (not), '&&' (and), '||' (or),
|
||||
'<' ('and' with left argument negated), '>' ('and' with right argument negated),
|
||||
'<=' ('or' with left argument negated), '>=' ('or' with right argument negated),
|
||||
'!=' (xor), '==' (negated xor), '!|' (nor), '!&' (nand).
|
||||
The "simpliest" expression is the one that uses the fewest number of operators.
|
||||
It should be noted that more than one expression could be considered the
|
||||
"simpliest". This program chooses one arbitrarily (alphabetical order).
|
||||
""");
|
||||
|
||||
def evaluate(exp):
|
||||
match exp:
|
||||
case "0":
|
||||
return 0;
|
||||
case "1":
|
||||
return MASK;
|
||||
case "w":
|
||||
return W;
|
||||
case "x":
|
||||
return X;
|
||||
case "y":
|
||||
return Y;
|
||||
case "z":
|
||||
return Z;
|
||||
case ("!", subexp):
|
||||
return ~evaluate(subexp) & MASK;
|
||||
case (left, "||", right):
|
||||
return evaluate(left) | evaluate(right);
|
||||
case (left, "!|", right):
|
||||
return ~(evaluate(left) | evaluate(right)) & MASK;
|
||||
case (left, "&&", right):
|
||||
return evaluate(left) & evaluate(right);
|
||||
case (left, "!&", right):
|
||||
return ~(evaluate(left) & evaluate(right)) & MASK;
|
||||
case (left, "<", right):
|
||||
return (~evaluate(left) & evaluate(right)) & MASK;
|
||||
case (left, "<=", right):
|
||||
return (~evaluate(left) | evaluate(right)) & MASK;
|
||||
case (left, ">", right):
|
||||
return (evaluate(left) & ~evaluate(right)) & MASK;
|
||||
case (left, ">=", right):
|
||||
return (evaluate(left) | ~evaluate(right)) & MASK;
|
||||
case (left, "==", right):
|
||||
return ~(evaluate(left) ^ evaluate(right)) & MASK;
|
||||
case (left, "!=", right):
|
||||
return evaluate(left) ^ evaluate(right);
|
||||
case _:
|
||||
print(exp);
|
||||
assert(not "TODO");
|
||||
|
||||
while True:
|
||||
try:
|
||||
raw_exp = input(">>> ");
|
||||
except EOFError:
|
||||
print("exit");
|
||||
break;
|
||||
|
||||
try:
|
||||
exp = parser.parseString(raw_exp, parseAll = True).asList()[0]
|
||||
except:
|
||||
print("syntax error");
|
||||
continue;
|
||||
|
||||
truthtable = evaluate(exp);
|
||||
|
||||
print(f"done in {costs[truthtable]} operations:");
|
||||
|
||||
print(f"0b{truthtable:016b}: {pretty(lookup[truthtable])}");
|
||||
|
||||
# print(pretty(lookup[truthtable]));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in a new issue