Re: How would you solve a user-inputted simple one-variable Linear Equation
by bitingduck (Deacon) on Apr 25, 2015 at 17:21 UTC
|
For a simple linear equation with an arbitrarily complex input stream, e.g. 2+3x+7(x+27)-6 you can probably do it with a minimum of conditionals. The hard part is writing a parser, but it's also entertaining. How I might do it, based on not spending much time on it and not having done one in a while is:
a) Write a parser that splits on the equal sign and multiplies everything out to eliminate parentheses, so my expression above would be 2+3x+7x+189-6.
b) do a second pass where you look at each term and add it into a pair of accumulators, one for the constants and one for the slopes. When you process the righthand side, multiply everything by -1 before you add it.
Now you have something like 3235643596 in the slope accumulator (corresponding to the "x" terms) and 998396 in the constant accumulator. Do a simple division of one by the other and you're done.
Writing the parser is the hard part, and it shouldn't be that hard for your case (you should be able to do it with a little recursive chunk of code)
| [reply] |
|
Okay, this makes a decent amount of sense to me, thanks. Where is the = sign in your equation?
| [reply] |
|
| [reply] |
|
Re: How would you solve a user-inputted simple one-variable Linear Equation
by Corion (Patriarch) on Apr 25, 2015 at 16:40 UTC
|
I think the traditional approach of implementing/solving linear equations is to convert them into matrix form and then repeatedly applying Gaussian Elimination until you solve the system or determine that it has no solution.
The easiest way of doing so is to either link to a solver library (maybe PDL has an equation system solver).
| [reply] |
|
| [reply] [d/l] [select] |
|
Gaussian Elimination on a single variable? Okay...
I don't want to use a pre-made library, I would like to do this all with my 'own' code, so to speak. You used the word "either" in your last sentence, but only gave one thing?
Thanks for the response!
| [reply] |
|
Sorry, the other part of the "either" would be to call any linear solver program yourself.
If you only have one linear equation, the solution is to simply restate the formula in the form
a*x +b = 0
From that, the solution is immediately clear. | [reply] [d/l] |
Re: How would you solve a user-inputted simple one-variable Linear Equation
by Laurent_R (Canon) on Apr 25, 2015 at 18:11 UTC
|
The first and perhaps hardest thing to do is to parse the input. You need to write some form of lexer to tokenize the input. This can be done with regexes eating up progressively the input (with the /g modifier).
But rather than trying to explain myself probably in a poor fashion, I would suggest that you take a look at chapter 8 ("Parsing") of Mark Jason Dominus' excellent book, Higher Order Perl (http://hop.perl.plover.com/). This chapter is available on-line here: http://hop.perl.plover.com/chap08.html.
You probably don't need to go in all the complexity of that chapter, but you should find a lot of ideas which you can then implement in your own simple way for what you need.
| [reply] [d/l] |
|
| [reply] [d/l] |
|
Yes, sure ++. And the first link I posted is the start page for the entire HOP download. But I pointed especially to chapter 8, Parsing, because of the general subject, parsing, because of the lexer examples and because of the calculator example, which goes a long way toward solving the OP problem. And also because I think this chapter can, to a large extent, be read without having read the previous chapters.
Having said that, I would really recommend reading the entire book, one of the best books about CS I've read in the last decade, it has definitely changed my way of programming, even in other languages than Perl. And BTW, I first read it on-line, but I quickly decided to buy a dead-tree version, it is really worth the money.
| [reply] |
|
|
Re: How would you solve a user-inputted simple one-variable Linear Equation
by BrowserUk (Patriarch) on Apr 25, 2015 at 20:55 UTC
|
If this is for less than 2000 non-commercial runs a month, I'd probably sign up for the Wolfram/Alpha API.
Example: 2+3x+7(x+27)-6= 23x-45-(145+62-5)x
Dunno what their commercial rates are like. The fact that they don't publish them is probably a bad sign.
(I'm not associated with them in any way!)
With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
| [reply] |
Re: How would you solve a user-inputted simple one-variable Linear Equation
by hdb (Monsignor) on Apr 25, 2015 at 19:51 UTC
|
use strict;
use warnings;
my $eq = '2+3x+7x(x+27)-6';
my @mult=(1);
my @pow=(0);
my @c;
# die "Not supported.\n" if /[^-+0-9x]/;
die "Not supported: '$1' \n" if $eq =~ /([^-+0-9x()]+)/; # as per Anom
+alousMonk's suggestion
print "Equation: $eq\n";
while($eq=~/([-+]?)(\d*)(x?)([)(]?)/g){
next unless "$1$2$3$4";
my $d = $2 || 1;
$d *= -1 if $1 eq '-';
if( $4 eq '(' ) {
push @mult, $mult[-1]*$d;
push @pow, $pow[-1]+1;
} else {
if( $3 ) {
$c[1+$pow[-1]] += $mult[-1]*$d;
} else {
$c[$pow[-1]] += $mult[-1]*$d;
}
}
if( $4 eq ')' ) {
pop @mult;
pop @pow;
}
}
print "Summary: @c\n";
| [reply] [d/l] |
|
| [reply] [d/l] [select] |
|
| [reply] |
Re: How would you solve a user-inputted simple one-variable Linear Equation
by Anonymous Monk on Apr 26, 2015 at 02:42 UTC
|
For the =, move it to the opposite side of the = by changing its sign and drop the =. Then everything is on one side, and the other side (no longer needed) is 0.
(see one of the tweaks: s/(.*)=(.*)/($1)-($2)/; )
Normally I'd use Parse::Yapp for the parsing, but you said no modules. (I just use Data::Dump for debugging.)
#!/usr/bin/perl
# http://perlmonks.org/?node_id=1124684
use strict; # hand rolled recursive descent parser using /\G/gc
use Data::Dump qw(pp);
# anon arrays are [x^0, x^1, x^2, ...]
my $variable;
@ARGV or @ARGV = split /\n/, <<END;
10x+4=7
3(x+9)=8x
3(x-1)=2x-1
oops = 42 ++ 23
(x-1)(x+1) = 0
2+3x+7(x+27)-6= 23x-45-(145+62-5)x
END
for ( @ARGV )
{
eval # for the die's
{
$variable = undef;
print "\n";
print " raw input: $_\n";
s/\d\K(?=[a-zA-Z])/*/g; # insert missing ops for consistent parse
s/(.*)=(.*)/($1)-($2)/;
s/ \w+\K (?=\() | \)\K (?=\w) | \)\K (?=\() /*/gx;
print "tweaked input: $_\n"; # added default operators
my $answer = expr();
/\G\s*\z/gc or error(); # verify complete parse
pp "raw answer for $variable ", $answer;
defined $variable or die "no variable given";
pop @$answer while @$answer > 2 && $answer->[-1] == 0; # trim trai
+ling 0's
$answer->[1] or die "variable $variable cancels out";
@$answer > 2 and die "quadratic or higher equation";
print "\n $variable = ", -$answer->[0] / $answer->[1], "\n";
};
$@ and print $@;
}
sub expr # left associative => term ([+-] term)*
{
my $left = term();
while( /\G\s*((\+)|-)/gc )
{
my $add = $2;
my $right = term();
if($add)
{
$left->[$_] += $right->[$_] for 0..$#$right;
}
else
{
$left->[$_] -= $right->[$_] for 0..$#$right;
}
}
return $left;
}
sub term # left associative => factor (* factor)*
{
my $left = item();
while( /\G\s*\*/gc )
{
my $right = item();
my @result = (0) x (@$left + @$right - 1);
my $n = 0;
for my $vl (@$left) # cross multiply
{
my $pos = $n;
$result[$pos++] += $vl * $_ for @$right;
$n++;
}
pop @result while @result > 2 && $result[-1] == 0; # trim trailing
+ 0's
$left = [ @result ];
}
return $left;
}
sub item # parens or number or variable or unary minus
{
if( /\G\s*\(/gc )
{
my $val = expr();
return /\G\s*\)/gc ? $val : error(); # must have closing paren
}
/\G\s*((\d+(\.\d*)?|\.\d+))/gc and return [ $1, 0 ];
if( /\G\s*(\w+)/gc )
{
defined $variable && $variable ne $1 and
die "more than one variable used '$variable' and '$1'";
$variable = $1;
return [ 0, 1 ];
}
if( /\G\s*-/gc ) # unary minus
{
my $right = item();
return [ map -$_, @$right ];
}
error();
}
sub error
{
s/\G/ SYNTAX ERROR->/;
die "$_\n";
}
| [reply] [d/l] |
|
| [reply] |
|
Wow, good job! Thanks! Its going to take me a little while to understand all of your code!
| [reply] |
|
| [reply] |