160 lines
No EOL
6.3 KiB
Python
160 lines
No EOL
6.3 KiB
Python
# Extruded by claude.ai using these prompts:
|
|
# Consider arithmetic formulas with addition and multiplication. Write a python program that iterats all structurally different formulas for $n$ operands where each operand occurs exactly once. "Structurally different" here means that two formulas are considered equivalent if and only if one can be changed into the other only by applying the commutative law.
|
|
# Refactor your code into a single function that outputs a list of (operator, left, right) tuples where operator is either '+' or '*', and left and right are either integers containing a variable number, or similar (op, l, r) tuples. Move all imports to the top of the file. Remove unused code.
|
|
# Make the generator output a program for a stack machine as a flat list instead of a nested structure. Each entry in the list describes one operation. Integers push the operand with that number onto the stack. The strings '*' and '+' take the top two entries off the stack, apply the named operation, and push the result back on to the stack. At the end of the program, the stack should contain only one value, the result.
|
|
# Modify the code to natively generate the stack program instead of generating the structured representation first then converting between representations.
|
|
|
|
"""
|
|
Generate all structurally different arithmetic formulas with n operands.
|
|
Two formulas are equivalent if one can be obtained from the other by applying
|
|
the commutative law (a+b = b+a, a*b = b*a).
|
|
|
|
Each formula is represented as a program for a stack machine:
|
|
- Integers push the operand with that number onto the stack
|
|
- The strings '+' and '*' pop two values, apply the operation, and push the result
|
|
|
|
Example: [0, 1, '+', 2, '*'] means:
|
|
1. Push x0
|
|
2. Push x1
|
|
3. Pop x1 and x0, push x0 + x1
|
|
4. Push x2
|
|
5. Pop x2 and (x0+x1), push (x0+x1) * x2
|
|
Result: (x0 + x1) * x2
|
|
"""
|
|
|
|
from itertools import combinations
|
|
|
|
|
|
def canonical_form(program):
|
|
"""
|
|
Return a canonical form for a stack program that's identical for equivalent programs.
|
|
Reconstructs the tree to normalize by sorting operands of commutative operations.
|
|
"""
|
|
stack = []
|
|
|
|
for item in program:
|
|
if isinstance(item, int):
|
|
stack.append(('leaf', item))
|
|
else: # item is '+' or '*'
|
|
right = stack.pop()
|
|
left = stack.pop()
|
|
# Sort to handle commutativity
|
|
args = tuple(sorted([left, right]))
|
|
stack.append((item, args))
|
|
|
|
return stack[0] if stack else None
|
|
|
|
|
|
def generate_formulas(n):
|
|
"""
|
|
Generate all structurally different formulas with n operands.
|
|
Each operand (0, 1, ..., n-1) appears exactly once.
|
|
|
|
Returns a list of stack machine programs where each program is a list
|
|
containing integers (push operand) and strings '+' or '*' (apply operation).
|
|
"""
|
|
if n <= 0:
|
|
return []
|
|
|
|
operands = list(range(n))
|
|
return _generate_recursive(operands)
|
|
|
|
|
|
def _generate_recursive(operands):
|
|
"""Generate all stack programs using the given operands."""
|
|
if len(operands) == 1:
|
|
return [[operands[0]]]
|
|
|
|
if len(operands) == 2:
|
|
i, j = sorted(operands)
|
|
return [
|
|
[i, j, '+'],
|
|
[i, j, '*']
|
|
]
|
|
|
|
programs = []
|
|
seen_canonical = set()
|
|
n = len(operands)
|
|
|
|
# Try all ways to split operands into two non-empty groups
|
|
for left_size in range(1, n):
|
|
for left_indices in combinations(range(n), left_size):
|
|
left_operands = [operands[i] for i in left_indices]
|
|
right_operands = [operands[i] for i in range(n) if i not in left_indices]
|
|
|
|
# To avoid duplicates from commutativity, ensure left <= right
|
|
if sorted(left_operands) > sorted(right_operands):
|
|
continue
|
|
|
|
# Generate all programs for left and right subsets
|
|
left_programs = _generate_recursive(left_operands)
|
|
right_programs = _generate_recursive(right_operands)
|
|
|
|
# Combine with both operators
|
|
for left_prog in left_programs:
|
|
for right_prog in right_programs:
|
|
for op in ['+', '*']:
|
|
# Build stack program: left_prog, right_prog, operator
|
|
program = left_prog + right_prog + [op]
|
|
canon = canonical_form(program)
|
|
|
|
if canon not in seen_canonical:
|
|
seen_canonical.add(canon)
|
|
programs.append(program)
|
|
|
|
return programs
|
|
|
|
|
|
def stack_program_to_string(program):
|
|
"""Convert a stack program to a human-readable infix string."""
|
|
stack = []
|
|
|
|
for item in program:
|
|
if isinstance(item, int):
|
|
stack.append(f"x{item}")
|
|
else: # item is '+' or '*'
|
|
right = stack.pop()
|
|
left = stack.pop()
|
|
# Add parentheses if operands contain operators
|
|
if ' ' in left:
|
|
left = f"({left})"
|
|
if ' ' in right:
|
|
right = f"({right})"
|
|
stack.append(f"{left} {item} {right}")
|
|
|
|
return stack[0] if stack else ""
|
|
|
|
|
|
def main():
|
|
"""Test the formula generator."""
|
|
for n in range(1, 5):
|
|
programs = generate_formulas(n)
|
|
print(f"\n=== Stack programs with {n} operand(s): {len(programs)} total ===")
|
|
for i, program in enumerate(programs, 1):
|
|
print(f"{i}. {program}")
|
|
print(f" Evaluates to: {stack_program_to_string(program)}")
|
|
|
|
# Detailed example for n=3
|
|
print("\n" + "="*60)
|
|
print("Detailed example for n=3:")
|
|
print("="*60)
|
|
programs = generate_formulas(3)
|
|
for i, program in enumerate(programs[:3], 1):
|
|
print(f"\nProgram {i}: {program}")
|
|
print(f"Formula: {stack_program_to_string(program)}")
|
|
print("Execution trace:")
|
|
stack = []
|
|
for j, item in enumerate(program):
|
|
if isinstance(item, int):
|
|
stack.append(f"x{item}")
|
|
print(f" Step {j+1}: Push x{item} → Stack: {stack}")
|
|
else:
|
|
right = stack.pop()
|
|
left = stack.pop()
|
|
result = f"({left} {item} {right})"
|
|
stack.append(result)
|
|
print(f" Step {j+1}: Apply {item} → Stack: {stack}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |