/Paramaker

Adds parenthesis to your equasions.

Primary LanguageRust

Have you ever been given an equation and been told to balance it only by adding
brackets to the left hand side? No? Uhhh? Ok, go away then!

We split up each side into a set of "Tokens", each token can be one of the
following: 
 - A number (e.g. `2121.23`)
 - A power operation (i.e. `^`)
 - A division operation (i.e. `/`)
 - A multiplication operation (i.e. `*`)
 - A addition operation (i.e. `+`)
 - A subtraction operation (i.e. `-`)
 - A start bracket. (i.e. `(`)
 - A end bracket (i.e. `)`)

For example, let us consider the following equation: `2 + 2 ^ 2 = 16`, this
would become: `[Number(2), Add, Number(2), Power, Number(2)]` and
`[Number(16)]`.

An other example: `(123 + 2323 * 1135374 / (2323 * 232 + 122 + (343548 / 2 +
10))) + 230 / 2 = 4999 / 2` becomes `[BracketStart, Number(123), Add,
Number(2323), Multiply, Number(1135374), Divide, BracketStart, Number(2323),
Multiply, Number(232), Add, Number(122), Add, BracketStart, Number(343548),
Divide, Number(2), Add, Number(10), BracketEnd, BracketEnd, BracketEnd, Add,
Number(230), Divide, Number(2)]` and `[Number(4999), Divide, Number(2)]`.

We start by solving the right hand side. In the former case, `16 = 16`, or in
the latter case `4999 / 2 = 2499.5`.

We then generate the `TokenData` for each non-bracket and non-numeric token. The
`TokenData` structure is as follows:

```
struct TokenData {
	pre_chunks:         Option<Vec<Range<usize>>>,
	post_chunks:        Option<Vec<Range<usize>>>,
	pre_divide_factor:  Option<usize>,
	post_divide_factor: Option<usize>,
}
```

Let's first explain what a chunk is. A chunk is either a single number or the
range between and including two matching brackets. So, for `(2 + (3 + 4 + (5 +
6)))`, the single numeric chunks are `[1..2, 4..5, 6..7, 9..10, 10..11]`, and
the bracket range chunks are `[0..15, 3..14, 8..13]`. Two chunks are "adjacent"
if they both have the same "depth" (how many matching bracket pairs they are
    in).

So, `1..2` is adjacent to `3..14`, `4..5` is adjacent to `6..7` which is
adjacent to `8..13` and finally `9..10` is adjacent to `10..11`.

1..2 4..5 6..7 9..10 
  V    V   V    V   V-10..11
`(2 + (3 + 4 + (5 + 6)))`
 ^---------------------^
  0..15
      ^---------------^
        3..14
               ^-----^
                 8..13

`TokenData`'s `pre_chunks` might contain the first chunk before the token and
all the chunks adjacent to that chunk, while `post_chunk` might contain the
first chunk after the token and all the chunks adjacent to that chunk.  Emphasis
on "might", as it might just instead be `None`.

If `pre_chunks` is not `None`, then the `pre_divide_factor` will be equal to the
last divide factor and the last divide factor will be multiplied by
|`pre_chunks`|.  Otherwise, the `pre_divide_factor` will be set to `None`. A
similar thing will happen to the `post_divide_factor`.

Currently the program then prints out the `TokenData` for you to examine.

After this we iterate over `0..next_divide_factor`, each value will represent a
unique combination of the chunks in `TokenData`. We decide which chunk to use by
dividing the value by the divide factor then getting the modulo of the |chunks|. 

For pre-chunks, the end will always be overridden to the last pre-chunk's end,
while for post-chunks, the start will always be overridden to the first
    post-chunk's start.

Consider the following:
9 + 9 + 9
9: None
+: pre: [0..1] post [2..3, 4..5] pre_divide_factor 1 post_divide_factor 1
9: None
+: pre: [0..1, 2..3] post [4..5] pre_divide_factor 2 post_divide_factor 4
9: None

If we iterate over 0..4, here is the four states we will get:

0
((9) + (9)) + (9)
9: None
+: 0..1 2..3
9: None
+: 0..3 4..5
9: None

1
((9) + (9) + (9))
9: None
+: 0..1 2..5
9: None
+: 0..3 4..5
9: None

2
(9) + ((9)) + (9)
9: None
+: 0..1 2..3
9: None
+: 2..3 4..5
9: None

3
(9) + ((9) + (9))
9: None
+: 0..1 2..5
9: None
+: 2..3 4..5
9: None

Note how we have now iterated over every possible state. 

Also note that we often iterate over the same state multiple times. We can stop
this from happening by insuring that when adding bracket range R, that for every
already inserted bracket range Q, R is either completely contained within or
completely outside of Q. Of course, such a test will probably be slower than
just trying a couple states more than once.

Also note that this method allows us to iterate over subsets of the range
`0..next_divide_factor` in parallel. Thanks to the powers of the Rust programing
language and Rayon, this only took like 10 lines of code changed :)

Anyways, for every possible state, we just solve it and see if it matches the
right hand side.

Now, as you might recall, I said we *might* have `pre_chunks` and `post_chunks`.
This is because it's often not necessary to compute the brackets around some
operations. Consider the example above: `9 + 9 + 9`. Due to the associative
property of addition, we would not have to compute brackets for any of the
operators above. For the exact rules we use, please refer to the documentation
above the function `is_compatible`.

Possible Future Optimizations: 
 - A better `is_compatible` method.
 - Actually profiling and optimizing the code (think of all the branches
   OrenAudeles could get rid of if he tried).