【问题标题】:Perl - simple calculator that bends the rules of mathsPerl - 打破数学规则的简单计算器
【发布时间】:2020-11-19 08:31:31
【问题描述】:

我正在尝试开发一个打破数学规则的简单计算器。我希望它忽略通常的数学规则并从右到左执行。用户输入整个字符串作为数学问题。 例如:

输入:123 - 10 + 4 * 10

应该这样解决:

123 - 10 + 4 * 10 = 123 - ( 10 + ( 4 * 10 ) ) = 73。

这是我目前拥有的:

use strict;
use warnings;
use feature 'say';

while (<>) {                                     # while we get input
my ($main, @ops) = reverse /[\d+\-*\/]+/g;   # extract the ops
while (@ops) {                               # while the list is not empty
    $main = calc($main, splice @ops, 0, 2);  # take 2 items off the list and process
}
say $main;                                   # print result
}

sub calc {
my %proc = (
    "+" => sub { $_[0] + $_[1] },
    "-" => sub { $_[0] - $_[1] },
    "/" => sub { $_[0] / $_[1] },
    "*" => sub { $_[0] * $_[1] }
);
return $proc{$_[1]}($_[0], $_[2]);
}

这是我得到的输出: 123 - 10 + 4 * 10 = ((123 - 10) + 4) * 10 = 1170

如您所见 - 它从左到右解决了问题。我的问题是 - 我怎样才能扭转这种局面?我希望它从右到左解决。任何帮助将不胜感激,谢谢。

【问题讨论】:

  • 实际上,您的代码确实会根据您提供的输入返回 -73
  • 您的问题是基于错误的前提。计算是从右到左进行的,但操作也是相反的。当你应该得到73时,你得到了-73。我在回答中解决了这个问题

标签: function perl math calculator


【解决方案1】:

这似乎是你想要的。

#!/usr/bin/perl

use strict;
use warnings;
use feature 'say';

my @tokens = @ARGV ? @ARGV : split /\s+/, '123 - 10 + 4 * 10';

if (grep { !/^[-+*i%]$/ and /\D/ } @tokens) {
  die "Invalid input: @tokens\n";
}

while (@tokens >= 3) {
  my $expr = join ' ', @tokens[-3 .. -1];

  splice @tokens, -3, 3, eval $expr;
}

say "@tokens";

我将输入拆分为标记,然后一次处理三个元素的标记数组,从最后开始工作。每次,我将最后三个标记替换为计算表达式的结果。

我在这里使用了eval,而不是您使用的调度表计算器。在使用 eval 时,您总是希望非常确定您的输入,因此我还包含了一个快速而肮脏的验证步骤。

【讨论】:

    【解决方案2】:

    从 postfix/RPN 评估策略中获取一个页面,并保留两个堆栈,一个用于操作,一个用于数字,可以实现简单的实现:

    #!/usr/bin/env perl
    use warnings;
    use strict;
    use feature 'say';
    
    {
        my %proc = (
            "+" => sub { $_[0] + $_[1] },
            "-" => sub { $_[0] - $_[1] },
            "/" => sub { $_[0] / $_[1] },
            "*" => sub { $_[0] * $_[1] }
            );
    
        sub calc {
            my @tokens = reverse split ' ', $_[0];
            my @opstack = grep { m!^[+-/*]$! } @tokens;
            my @numstack = grep { $_ !~ m!^[+-/*]$! } @tokens;
    
            for my $op (@opstack) {
                splice @numstack, 0, 2, $proc{$op}->(@numstack[1,0]);
            }
            return $numstack[0];
        }
    }
    
    say calc("123 - 10 + 4 * 10");
    

    当然,更健壮的版本会在每对数字之间强制使用运算符,并进行其他错误/健全性检查。

    【讨论】:

      【解决方案3】:

      为什么不使用 Perl 中最有趣的部分?

      如果您输入给定的测试用例,这将返回 73:

      #!/usr/bin/env perl
      use warnings;
      use strict;
      use feature 'say';
      
      while (<>) {                                     # while we get input
      chomp;s/ //g;
      1 while s/\d+[+\-*\/]\d+$/$&/ee;
      say;                                         # print result
      }
      

      如果您想了解它是如何工作的,只需将 no-op "1" 替换为一些 STDERR 输出即可:

      while (<>) {
      chomp;s/ //g;
      print STDERR "eval'd ($&) -> $_" while s/\d+[+\-*\/]\d+$/$&/ee;
      say;
      }
      
      > ./test.pl
      123 - 10 + 4 * 10
      eval'd (4*10) -> 123-10+40
      eval'd (10+40) -> 123-50
      eval'd (123-50) -> 73
      73
      

      【讨论】:

        【解决方案4】:

        my answer 对您之前的问题的评论中,我说您可以使用reverse 反转计算,我看到您已经实现了该代码。

        正如您所注意到的,我认为这不是真的,因为它也会反转操作。 IE。 123 - 50 将变为 50 - 123。我在那个评论中有点粗心。但是,如果您只是恢复 calc() 调用中操作数的顺序并再次使用 reverse,则可以达到相同的效果。

        $main = calc(reverse($main, splice @ops, 0, 2));  # take 2 items off the list and process
        

        这意味着您的字符串 123 - 10 + 4 * 10 将首先成为一个列表

        10 * 4 + 10 - 123
        

        然后它会被调用

        calc(4, '*', 10)    # 40
        calc(10, '+', 40)   # 50
        calc(123, '-', 50)  # 73
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-30
          • 2019-04-10
          • 2012-09-03
          • 2018-08-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多