Skip to content

Instantly share code, notes, and snippets.

@barnden
Last active June 17, 2019 23:43
Show Gist options
  • Save barnden/8ca892f7cf713ecdbee48942669a92f3 to your computer and use it in GitHub Desktop.
Save barnden/8ca892f7cf713ecdbee48942669a92f3 to your computer and use it in GitHub Desktop.
Dynamically generate expressions that evaluate to a desired result.
#define ADD '+'
#define SUB '-'
#define MUL '*'
#define DIV '/'
#define MAX_FACTORS 5
#define MAX_OP_DOUBLE 2
#define DEBUG true
#ifdef NULL
#undef NULL
#define NULL 255
#endif
static byte const primes[] = {
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 90, 97
};
class Expression {
public:
char op;
bool left_b;
bool right_b;
union {
byte val;
struct Expression* expr;
} left_u;
union {
byte val;
class Expression* expr;
} right_u;
Expression() {
left_u.val = NULL;
right_u.val = NULL;
left_b = NULL;
right_b = NULL;
op = NULL;
}
void set_left(byte val) {
left_b = true;
left_u.val = val;
}
void set_left(Expression* val) {
left_b = false;
left_u.expr = val;
}
void set_right(byte val) {
right_b = true;
right_u.val = val;
}
void set_right(Expression* val) {
right_b = false;
right_u.expr = val;
}
byte _eval(byte l, byte r, char o) {
switch(op) {
case ADD:
return l + r;
case SUB:
return l - r;
case MUL:
return l * r;
case DIV:
return l / r;
}
}
byte _eval_expr(Expression* expr) {
return _eval(expr->left_u.val, expr->right_u.val, expr->op);
}
byte eval() {
byte l_val, r_val;
if(left_b) {
l_val = left_u.val;
} else {
l_val = _eval_expr(left_u.expr);
}
if(right_b) {
r_val = right_u.val;
} else {
r_val = _eval_expr(right_u.expr);
}
return _eval(l_val, r_val, op);
}
String to_str() {
String l_str, r_str;
if(left_b) {
l_str = (String) left_u.val;
} else {
l_str = left_u.expr->to_str();
}
if(right_b) {
r_str = (String) right_u.val;
} else {
r_str = right_u.expr->to_str();
}
return l_str + op + r_str;
}
};
Expression *gen_mul(byte num, byte digits, Expression *expr);
Expression *gen_div(byte num, byte digits, Expression *expr);
Expression *gen_add(byte num, byte digits, Expression *expr);
Expression *gen_sub(byte num, byte digits, Expression *expr);
byte *get_pfactors(byte num, byte factors[MAX_FACTORS + 1]) {
byte x, pos = 0;
for(x = 0; x <= MAX_FACTORS; x++) {
factors[x] = NULL;
}
while (num > 1) {
if(pos > MAX_FACTORS) break;
for(x = 0; x < sizeof(primes); x++) {
if (num < primes[x]) continue;
if (num % primes[x] == 0) {
factors[pos] = primes[x];
num /= primes[x];
pos++;
}
}
}
factors[MAX_FACTORS + 1] = pos;
return factors;
}
Expression *gen_mul(byte num, byte digits, Expression *expr) {
byte factors[MAX_FACTORS + 1], limit = pow(10, digits);
get_pfactors(num, factors);
if(factors[MAX_FACTORS + 1] < 2) return nullptr;
if(factors[MAX_FACTORS + 1] == 2) {
expr->set_left(factors[0]);
expr->set_right(factors[1]);
expr->op = MUL;
return expr;
}
generate: {
byte a = NULL, b = NULL;
for(byte x = 0; x <= MAX_FACTORS; x++) {
if(factors[x] == NULL) continue;
if(a == NULL) {
a = factors[x];
continue;
}
if(b == NULL) {
b = factors[x];
continue;
}
if(random(0, 100) < 50) {
a *= factors[x];
} else {
b *= factors[x];
}
}
if (a < limit && b < limit) {
expr->set_left(a);
expr->set_right(b);
expr->op = MUL;
return expr;
}
goto generate;
}
}
Expression *gen_div(byte num, byte digits, Expression *expr) {
if(num == 0) return nullptr;
byte max_dividend = 100 / num, limit = pow(10, digits);
if(max_dividend > 1) {
byte ratio = random(2, max_dividend);
generate: {
if(ratio < limit && ratio * num < limit) {
expr->set_left(ratio * num);
expr->set_right(ratio);
expr->op = DIV;
return expr;
}
goto generate;
}
}
return nullptr;
}
Expression *gen_add(byte num, byte digits, Expression *expr) {
byte limit = pow(10, digits);
if(num < 2 || num > 2 * (limit - 1)) return nullptr;
generate: {
byte a = random(1, num), b = num - a;
if(a < limit && b < limit) {
expr->set_left(a);
expr->set_right(b);
expr->op = ADD;
return expr;
}
goto generate;
}
}
Expression *gen_sub(byte num, byte digits, Expression *expr) {
byte limit = pow(10, digits);
if(num >= limit) return nullptr;
expr->set_left(random(num + 1, limit));
expr->set_right(expr->left_u.val - num);
expr->op = SUB;
return expr;
}
void setup() {
Serial.begin(9600);
}
void loop() {
if (Serial.available() > 0) {
byte num = Serial.parseInt();
// TEST 1: Generate random single operator expressions.
Expression __test;
gen_mul(24, 2, &__test);
Serial.println(__test.to_str());
gen_div(24, 2, &__test);
Serial.println(__test.to_str());
gen_sub(24, 2, &__test);
Serial.println(__test.to_str());
gen_add(24, 2, &__test);
Serial.println(__test.to_str());
// TEST 2: Nested expressions.
Expression _t;
Expression _b;
_b.op = ADD;
_b.set_left(1);
_b.set_right(2);
_t.op = ADD;
_t.set_left(&_b);
_t.set_right(3);
Serial.println(_t.eval());
}
}
from __future__ import annotations
from random import shuffle, randint
from math import ceil, floor#, sqrt
from typing import Union, List
class Operators():
MULTIPLY = 0
DIVISION = 1
ADDITION = 2
SUBTRACT = 3
@staticmethod
def to_char(operator: int) -> str:
if operator == Operators.MULTIPLY:
return '*'
if operator == Operators.DIVISION:
return '/'
if operator == Operators.ADDITION:
return '+'
if operator == Operators.SUBTRACT:
return '-'
MAX_OP_DOUBLE = 2
primes = [ # Primes < 100
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
43, 47, 53, 59, 61, 67, 71, 73, 79, 73, 89, 97
]
products = {
6: [[2, 3]],
8: [[2, 4]],
10: [[2, 5]],
12: [[2, 6], [3, 4]],
14: [[2, 7]],
15: [[3, 5]],
16: [[2, 8]],
18: [[2, 9], [3, 6]],
20: [[4, 5]],
21: [[3, 7]],
24: [[3, 8], [4, 6]],
27: [[3, 9]],
28: [[4, 7]],
30: [[5, 6]],
32: [[4, 8]],
35: [[5, 7]],
36: [[4, 9]],
40: [[5, 8]],
42: [[6, 7]],
45: [[5, 9]],
48: [[6, 8]],
54: [[6, 9]],
56: [[7, 8]],
81: [[9, 9]]
}
class Expression():
def __init__(self, left: Union(int, Expression), right: Union(int, Expression), operator: int):
self.left = left
self.right = right
self.operator = operator
def value(self) -> int:
return eval(str(self))
def check(self, value: int) -> bool:
return self.value() == value
def shuffle(self) -> Expression:
val = self.value()
if isinstance(self.left, Expression):
self.left = self.left.shuffle()
if isinstance(self.right, Expression):
self.right = self.right.shuffle()
if randint(0, 100) < 50:
left = self.left
self.left = self.right
self.right = left
if not self.check(val):
self.right = self.left
self.left = left
return self
def __str__(self) -> str:
return f'{str(self.left)} {Operators.to_char(self.operator)} {str(self.right)}'
def __add__(self, other) -> Expression:
return Expression(self, other, Operators.ADDITION)
def __sub__(self, other) -> Expression:
return Expression(self, other, Operators.SUBTRACT)
def __mul__(self, other) -> Expression:
return Expression(self, other, Operators.MULTIPLY)
def __div__(self, other) -> Expression:
return Expression(self, other, Operators.DIVISION)
def get_prime_factors(number: int) -> List[int]:
if number in primes:
return [number]
factors = []
while number > 1:
for prime in primes:
if number < prime:
continue
if number % prime == 0:
factors.append(prime)
number //= prime
return factors
def generate_multiplication(number: int, digits: int) -> Expression:
factors = num_factors = limit = None
if digits is 1 and number in products:
factors = products[number]
shuffle(factors)
factors = factors.pop()
else:
factors = get_prime_factors(number)
limit = 10 ** digits
num_factors = len(factors)
if num_factors < 2:
return None
if num_factors == 2:
return Expression(factors[0], factors[1], Operators.MULTIPLY)
a = b = None
for _ in range(0, num_factors):
if a == None:
a = factors.pop()
continue
if b == None:
b = factors.pop()
continue
if randint(0, 100) < 50:
a *= factors.pop()
else:
b *= factors.pop()
if a >= limit or b >= limit:
return generate_multiplication(number, digits)
return Expression(a, b, Operators.MULTIPLY)
def generate_division(number: int, digits: int) -> Expression:
limit = 10 ** digits
single = 100 // number if not number == 0 else 0
if single > 1:
ratio = randint(2, single)
if ratio < limit and ratio * number < limit:
return Expression(ratio * number, ratio, Operators.DIVISION)
else:
return generate_division(number, digits)
return None
def generate_addition(number: int, digits: int) -> Expression:
limit = 10 ** digits
if number > 2 * (limit - 1):
return None
try:
a = randint(1, number)
b = number - a
if a < limit and b < limit:
return Expression(a, b, Operators.ADDITION)
return generate_addition(number, digits)
except:
return None
def generate_subtraction(number: int, digits: int) -> Expression:
limit = 10 ** digits
if number >= limit:
return None
a = randint(number + 1, limit)
b = a - number
return Expression(a, b, Operators.SUBTRACT)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment