Browse Source

add solving

Kyle 1 month ago
parent
commit
f16f142e66
1 changed files with 113 additions and 9 deletions
  1. 113 9
      main.js

+ 113 - 9
main.js

@@ -1,8 +1,4 @@
1
-function split(line) {
2
-  return line.split(' ').filter(t => t);
3
-}
4
-
5
-var ops = ['+', '-', '*', '/'];
1
+var ops = ['=', '+', '-', '*', '/'];
6 2
 
7 3
 var op_funcs = {
8 4
   '+': (a, b) => a + b,
@@ -11,9 +7,110 @@ var op_funcs = {
11 7
   '/': (a, b) => a / b,
12 8
 };
13 9
 
10
+var onlyUnique = (v, i, a) => a.indexOf(v) === i;
11
+
12
+function calcExpression(expression) {
13
+  if (typeof expression === 'string') return parseFloat(expression);
14
+  return op_funcs[expression.action](
15
+    calcExpression(expression.args[0]),
16
+    calcExpression(expression.args[1])
17
+  );
18
+}
19
+
20
+function substituteVariable(expression, variable, substitute) {
21
+  if (expression === variable) return substitute;
22
+  if (typeof expression === 'string') return expression;
23
+  return {
24
+    ...expression,
25
+    args: expression.args.map(a => substituteVariable(a, variable, substitute))
26
+  };
27
+}
28
+
29
+function expressionFor(variable, state) {
30
+  var varState = state.find(x => x.name === variable);
31
+  var expression = varState.depends.reduce(
32
+    (r, d) => substituteVariable(r, d, expressionFor(d, state)),
33
+    varState.solved
34
+  );
35
+  return expression;
36
+}
37
+
38
+function mergeState(a, b) {
39
+  var both = a.concat(b)
40
+  var vars = both.map(x => x.name).filter(onlyUnique);
41
+  return vars.map(v => {
42
+    var defs = both.filter(x => x.name === v);
43
+    if (defs.length === 1) return defs[0];
44
+    var chosen = defs.reduce((min_dep, d) => {
45
+      if (!min_dep || min_dep.depends.length > d.depends.length) return d;
46
+      return min_dep;
47
+    }, null);
48
+    return chosen;
49
+  });
50
+}
51
+
52
+function solve(lines, state=[]) {
53
+  if (lines.length === 0) return state;
54
+  var new_state = extractSolutions(lines[0]);
55
+  return solve(lines.slice(1), mergeState(new_state, state));
56
+}
57
+
58
+function render(tree) {
59
+  if (typeof tree === 'string') {
60
+    return tree;
61
+  }
62
+  return '(' + render(tree.args[0]) + ' ' + tree.action + ' ' + render(tree.args[1]) + ')';
63
+}
64
+
65
+function getFlip(variable, tree, inside) {
66
+  if (tree === variable) {
67
+    return inside;
68
+  }
69
+  if (typeof tree === 'string') {
70
+    return null;
71
+  }
72
+  var sides = tree.args.map(t => getFlip(variable, t, inside));
73
+  if (sides[0]) {
74
+    return {
75
+      action: { '+': '-', '*': '/', '-': '+', '/': '*' }[tree.action],
76
+      args: [sides[0], tree.args[1]]
77
+    };
78
+  }
79
+  if (sides[1]) {
80
+    var flip_ops = ['/', '-'].includes(tree.action);
81
+    return {
82
+      action: { '+': '-', '*': '/', '-': '-', '/': '/' }[tree.action],
83
+      args:  flip_ops ?  [tree.args[0], sides[1]] : [sides[1], tree.args[0]]
84
+    };
85
+  }
86
+  return null;
87
+}
88
+
89
+function solveFor(variable, tree) {
90
+  var left = tree.args[0];
91
+  var right = tree.args[1];
92
+  var flipRight = getFlip(variable, right, left);
93
+  var flipLeft = getFlip(variable, left, right);
94
+  return flipRight || flipLeft;
95
+}
96
+
97
+function extractSolutions(expression) {
98
+  var vars = expression.filter(p => !ops.includes(p) && isNaN(parseFloat(p)));
99
+  var tree = lex(expression);
100
+  return vars.map(v => ({
101
+    name: v,
102
+    solved: solveFor(v, tree),
103
+    depends: vars.filter(x => x != v)
104
+  }));
105
+}
106
+
107
+function split(line) {
108
+  return line.split(' ').filter(t => t);
109
+}
110
+
14 111
 function lex (pieces, untried_ops=ops) {
15 112
   if (pieces.length === 1) {
16
-    return parseFloat(pieces[0]);
113
+    return pieces[0];
17 114
   }
18 115
   if (untried_ops.length === 0) { return null; }
19 116
   var op = untried_ops[0];
@@ -30,7 +127,10 @@ function tryOpSplit(pieces, op) {
30 127
   if (op_index === -1) return null;
31 128
   var left = pieces.slice(0, op_index);
32 129
   var right = pieces.slice(op_index + 1);
33
-  return op_funcs[op](lex(left), lex(right));
130
+  return {
131
+    action: pieces[op_index],
132
+    args: [lex(left), lex(right)]
133
+  }
34 134
 }
35 135
 
36 136
 window.onload = function () {
@@ -40,8 +140,12 @@ window.onload = function () {
40 140
   function onchange(e) {
41 141
     var lines = input.value.match(/^.*([\n\r]+|$)/gm).map(
42 142
       l => l.replace(/\n/g,'')
43
-    ).map(split).map(pieces => lex(pieces)).map(r => isNaN(r) ? '': r);
44
-    results.textContent = lines.join('\n');
143
+    ).map(split)
144
+    var state = solve(lines)
145
+    result = state.map(s => [s.name, expressionFor(s.name, state)]).map(
146
+      x => `${x[0]} = ${calcExpression(x[1])} = ${render(x[1])}`
147
+    )
148
+    results.textContent = result.join('\n');
45 149
   }
46 150
 
47 151
   input.onchange = onchange;