Last active
June 17, 2019 23:43
-
-
Save barnden/8ca892f7cf713ecdbee48942669a92f3 to your computer and use it in GitHub Desktop.
Dynamically generate expressions that evaluate to a desired result.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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