Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing multi-argument functions and fixing exponentiation issue #59

Merged
merged 2 commits into from
Jul 5, 2018

Conversation

jolyonb
Copy link
Collaborator

@jolyonb jolyonb commented Jul 5, 2018

Addresses issues #42 and #43 by rewriting calc.py to fix exponentiation problem (introduced "negation" operation) and allow multi-argument functions (introduced "arguments" operation). Also did general tidying of pyparsing script and some refactoring of calc.py, along with a little linting. Large changes to your get_number_of_args function were necessary to pull this off.

@jolyonb
Copy link
Collaborator Author

jolyonb commented Jul 5, 2018

I'm quite proud of this one. Let me know what you think!

@coveralls
Copy link

coveralls commented Jul 5, 2018

Coverage Status

Coverage remained the same at 100.0% when pulling fcd152a on multivar into adaab0c on master.

Copy link
Collaborator

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! I like your restructuring of the parser and evaluator! And its still pretty fast :).

I made a few comments, at least one of which is somewhat substantial.

>>> parser = FormulaParser("1", {"%": 0.01})
>>> parser.eval_negation([2])
2
>>> parser.eval_power(["-",2])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this (and the next few) be eval_negation? (Or, eval_sign if you take the suggestion above.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed it should. Amusing that eval_power satisfies all these tests too! (I could just point it to eval_power, but I think that's bad practice)

sum_term = Group(sum_term)("sum")
# Define sums and differences
# Note that leading - signs are treated by negation
sumdiff = Group(Optional(plus) + product +
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example Expression master parses? multivar parses?
A 1+2 yes yes
B 1+-2 yes yes
C 1-+2 yes no
D 1++2 yes^1 no
E 1+++2 no no
F ---2 no yes
G --+2 no no

footnote 1: '++2' behaves similarly, both are parsed by master roughly as:

<sum>
  <number>1</number>
  +
  <number>
   +1 
  </number>
</sum>

Examples C through G are certainly a bit contrived, but I don't think they're too ridiculus.

I'm actually pretty confused about why the ZeroOrMore in line #382 doesnt' permit 1+++2 (on either this branch or master).

Suggestion: Change negation to sign or unary_sign, and change eval_negation to eal_sign. (I can commit my local version of this if you want, or you can write it.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I deliberately treated + and - differently here, basing things on how google parses math input. I don't want to accept + as part of a number, like 1-+2 would require. It's ok for - to be part of a number, which is why 1-------2 works - you just keep on adding brackets around the 2 with an ever-increasing number of minus signs.

I always thought it was strange that the old parser allowed for two pluses in a row, but not three, also. The reason is that it allowed for a single plus to be a part of a number, and then a second plus to be a part of a sum.

I think we should discourage inputs like your D. I also don't like C, E and G for the same reason. I'm happy with B, and I'm willing to tolerate F.

Note that like 382 contains ZeroOrMore(plus_minus + product), so you must have an expression after a +/- sign.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to accept + as part of a number

Still thinking about what you said, but to be clear my suggestion was not to modify the number group, but:

# replace negation with this:
sign = Group(ZeroOrMore(plus_minus) + power)("sign")

# and later, replace eval_number with
def eval_sign(self, parse_result): 
        num_negations = len([char for char in parse_result[:-1] if char == '-' ])
        num = parse_result[-1]
        return num * (-1)**(num_negations)

It just seems unncessarily arbitrary to me that ---1 is allowed but +++1 isn't. (Not that either is common, presumably).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I understood the suggestion.

Do you agree with me that we shouldn't allow C/D/E/G and we need to allow B?

I'd be willing to get rid of F.

To add another case, I think 1--2 needs to be allowed, but 1---2 can probably go.

I think these can be accomplished by changing ZeroOrMore to Optional in the negation statement.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree those are bad. Thinking about it more—I like rejecting them as unparseable: I think it would be easy to miscount the number of signs.

I agree Optional is a good choice.

But really, I guess it doesn't matter. I just checked some 2015 data, and in 400,000 submissions I found about 25 +-, 10 -+, and 7 ++. (Not all of the 400,000 were FormulaResponse submissions, but I assume quite a lot were. So...rare.)

from mitxgraders.helpers import validatorfuncs
from mitxgraders.helpers.calc import evaluator, UnableToParse, UndefinedVariable
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bunch of stuff in test_base.py testing stuff from mitxgraders.helpers. Maybe we should just make test/helpers/test_calc.py and test/helpers/test_validatorfuncs.py?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored it out as suggested.

@@ -29,48 +26,44 @@
alphas,
nums,
stringEnd,
ParseException
ParseException,
delimitedList
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sweet :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know, right? I was quite happy not to have to construct it in terms of more low-level functionality. And it will make doing vectors/matrices quite straightforward, too!

@jolyonb
Copy link
Collaborator Author

jolyonb commented Jul 5, 2018

Ok, made some minor modifications, and responded to your more major point.

I'm pretty happy that that's all you were able to find, given my 4:30 am coding!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants