33
\$\begingroup\$

Introduction:

enter image description here

Inspired by a discussion that is already going on for many years regarding the expression \$6÷2(1+2)\$.

With the expression \$6÷2(1+2)\$, mathematicians will quickly see that the correct answer is \$1\$, whereas people with a simple math background from school will quickly see that the correct answer is \$9\$. So where does this controversy and therefore different answers come from? There are two conflicting rules in how \$6÷2(1+2)\$ is written. One due to the part 2(, and one due to the division symbol ÷.

Although both mathematicians and 'ordinary people' will use PEMDAS (Parenthesis - Exponents - Division/Multiplication - Addition/Subtraction), for mathematicians the expression is evaluated like this below, because \$2(3)\$ is just like for example \$2x^2\$ a monomial a.k.a. "a single term due to implied multiplication by juxtaposition" (and therefore part of the P in PEMDAS), which will be evaluated differently than \$2×(3)\$ (a binomial a.k.a. two terms):

$$6÷2(1+2) → \frac{6}{2(3)} → \frac{6}{6} → 1$$

Whereas for 'ordinary people', \$2(3)\$ and \$2×(3)\$ will be the same (and therefore part of the MD in PEMDAS), so they'll use this instead:

$$6÷2(1+2) → 6/2×(1+2) → 6/2×3 → 3×3 → 9$$

However, even if we would have written the original expression as \$6÷2×(1+2)\$, there can still be some controversy due to the use of the division symbol ÷. In modern mathematics, the / and ÷ symbols have the exact same meaning: divide. Some rules pre-1918 regarding the division symbol ÷†† state that it had a different meaning than the division symbol /. This is because ÷ used to mean "divide the number/expression on the left with the number/expression on the right"†††. So \$a ÷ b\$ then, would be \$(a) / (b)\$ or \$\frac{a}{b}\$ now. In which case \$6÷2×(1+2)\$ would be evaluated like this by people pre-1918:

$$6÷2×(1+2) → \frac{6}{2×(1+2)} → \frac{6}{2×3} → \frac{6}{6} → 1$$

†: Although I have found multiple sources explaining how ÷ was used in the past (see ††† below), I haven't been able to find definitive prove this changed somewhere around 1918. But for the sake of this challenge we assume 1918 was the turning point where ÷ and / starting to mean the same thing, where they differed in the past.

††: Other symbols have also been used in the past for division, like : in 1633 (or now still in The Netherlands and other European non-English speaking countries, since this is what I've personally learned in primary school xD) or ) in the 1540s. But for this challenge we only focus on the pre-1918 meaning of the obelus symbol ÷.
†††: Sources: this article in general. And the pre-1918 rules regarding ÷ are mentioned in: this The American Mathematical Monthly article from February 1917; this German Teutsche Algebra book from 1659 page 9 and page 76; this A First Book in Algebra from 1895 page 46 [48/189].

Slightly off-topic: regarding the actual discussion about this expression: It should never be written like this in the first place! The correct answer is irrelevant, if the question is unclear. *Clicks the "close because it's unclear what you're asking" button*.
And for the record, even different versions of Casio calculators don't know how to properly deal with this expression:
enter image description here

Challenge:

You are given two inputs:

  • A (valid) mathematical expression consisting only of the symbols 0123456789+-×/÷()
  • A year

And you output the result of the mathematical expression, based on the year (where ÷ is used differently when \$year<1918\$, but is used exactly the same as / when \$year\ge1918\$).

Challenge rules:

  • You can assume the mathematical expression is valid and only uses the symbols 0123456789+-×/÷(). This also means you won't have to deal with exponentiation. (You are also allowed to use a different symbols for × or ÷ (i.e. * or %), if it helps the golfing or if your language only supports ASCII.)
  • You are allowed to add space-delimiters to the input-expression if this helps the (perhaps manual) evaluation of the expression.
  • I/O is flexible. Input can be as a string, character-array, etc. Year can be as an integer, date-object, string, etc. Output will be a decimal number.
  • You can assume there won't be any division by 0 test cases.
  • You can assume the numbers in the input-expression will be non-negative (so you won't have to deal with differentiating the - as negative symbol vs - as subtraction symbol). The output can however still be negative!
  • You can assume N( will always be written as N×( instead. We'll only focus on the second controversy of the division symbols / vs ÷ in this challenge.
  • Decimal output-values should have a precision of at least three decimal digits.
  • If the input-expression contains multiple ÷ (i.e. \$4÷2÷2\$) with \$year<1918\$, they are evaluated like this: \$4÷2÷2 → \frac{4}{\frac{2}{2}} → \frac{4}{1} → 4\$. (Or in words: number \$4\$ is divided by expression \$2 ÷2\$, where expression \$2 ÷2\$ in turn means number \$2\$ is divided by number \$2\$.)
  • Note that the way ÷ works implicitly means it has operator precedence over × and / (see test case \$4÷2×2÷3\$).
  • You can assume the input-year is within the range \$[0000, 9999]\$.

General rules:

  • This is , so shortest answer in bytes wins.
    Don't let code-golf languages discourage you from posting answers with non-codegolfing languages. Try to come up with an as short as possible answer for 'any' programming language.
  • Standard rules apply for your answer with default I/O rules, so you are allowed to use STDIN/STDOUT, functions/method with the proper parameters and return-type, full programs. Your call.
  • Default Loopholes are forbidden.
  • If possible, please add a link with a test for your code (i.e. TIO).
  • Also, adding an explanation for your answer is highly recommended.

Test cases:

Input-expression:   Input-year:   Output:      Expression interpretation with parenthesis:

6÷2×(1+2)           2018          9            (6/2)×(1+2)
6÷2×(1+2)           1917          1            6/(2×(1+2))
9+6÷3-3+15/3        2000          13           ((9+(6/3))-3)+(15/3)
9+6÷3-3+15/3        1800          3            (9+6)/((3-3)+(15/3))
4÷2÷2               1918          1            (4/2)/2
4÷2÷2               1900          4            4/(2/2)
(1÷6-3)×5÷2/2       2400          -3.541...    ((((1/6)-3)×5)/2)/2
(1÷6-3)×5÷2/2       1400          1.666...     ((1/(6-3))×5)/(2/2)
1×2÷5×5-15          2015          -13          (((1×2)/5)×5)-15
1×2÷5×5-15          1719          0.2          (1×2)/((5×5)-15)
10/2+3×7            1991          26           (10/2)+(3×7)
10/2+3×7            1911          26           (10/2)+(3×7)
10÷2+3×7            1991          26           (10/2)+(3×7)
10÷2+3×7            1911          0.434...     10/(2+(3×7))
4÷2+2÷2             2000          3            (4/2)+(2/2)
4÷2+2÷2             1900          2            4/((2+2)/2)
4÷2×2÷3             9999          1.333...     ((4/2)×2)/3
4÷2×2÷3             0000          3            4/((2×2)/3)
((10÷2)÷2)+3÷7      2000          2.928...     ((10/2)/2)+(3/7)
((10÷2)÷2)+3÷7      1900          0.785...     (((10/2)/2)+3)/7
(10÷(2÷2))+3×7+(10÷(2÷2))+3×7
                    1920          62           (10/(2/2))+(3×7)+(10/(2/2))+(3×7)
(10÷(2÷2))+3×7+(10÷(2÷2))+3×7
                    1750          62           (10/(2/2))+(3×7)+(10/(2/2))+(3×7)
10÷2/2+4            2000          6.5          ((10/2)/2)+4
10÷2/2+4            0100          2            10/((2/2)+4)
9+6÷3-3+15/3        9630          13           9+(6/3)-3+(15/3)
9+6÷3-3+15/3        0369          3            (9+6)/(3-3+(15/3))
\$\endgroup\$
25
+250
\$\begingroup\$

R, 68 66 bytes

function(x,y,`=`=`/`)eval(parse(t=`if`(y<1918,x,gsub('=','/',x))))

Try it online!

Expects equality sign = instead of ÷ and * instead of ×.

The code makes use of some nasty operator overloading, making advantage of the fact that = is a right-to-left operator with very low precedence (the exact behavior that we want from pre-1918 ÷), and R retains its original precedence when it is overloaded. The rest is automatically done for us by eval.

As a bonus, here is the same exact approach implemented in terser syntax. This time our special division operator is tilde (~):

Julia 0.7, 51 bytes

~=/;f(x,y)=eval(parse(y<1918?x:replace(x,'~','/')))

Try it online!

\$\endgroup\$
  • 3
    \$\begingroup\$ `=`=`/` is diabolical! Great solution! \$\endgroup\$ – Gregor Feb 28 at 14:02
  • \$\begingroup\$ uuugggghhh I had thoughts on the same lines. Alas, you beat me to it by quite a lot. Try it online \$\endgroup\$ – Giuseppe Mar 1 at 17:17
  • \$\begingroup\$ Although there are no answers in codegolf languages yet, I'm accepting your Julia answer as the shortest for now. It is possible this will change in the future of course if a shorter answer is posted. \$\endgroup\$ – Kevin Cruijssen Apr 5 at 5:47
6
\$\begingroup\$

JavaScript (ES6),  130 129  120 bytes

Saved 9 bytes thanks to @ScottHamper

Takes input as (year)(expr). Expects % and * instead of ÷ and ×.

y=>g=e=>(e!=(e=e.replace(/\([^()]*\)/,h=e=>eval(e.split`%`.reduceRight((a,c)=>y<1918?`(${c})/(${a})`:c+'/'+a))))?g:h)(e)

Try it online!

How?

Processing leaf expressions

The helper function \$h\$ expects a leaf expression \$e\$ as input, processes all % symbols according to the rules of the year \$y\$ (defined in the parent scope) and evaluates the resulting string.

If \$y<1918\$, we transform X%Y into (X)/(Y), to enforce low precedence and repeat this process for the entire string from right to left to enforce right-to-left associativity.

Examples:

  • 8%2 becomes (8)/(2), whose simplified form is 8/2
  • 2+3%3+2 becomes (2+3)/(3+2)
  • 8%2%2 becomes (8)/((2)/(2)), whose simplified form is 8/(2/2)

If \$y\ge 1918\$, each % is simply turned into a /.

h = e =>                    // e = input string
  eval(                     // evaluate as JS code:
    e.split`%`              //   split e on '%'
    .reduceRight((a, c) =>  //   for each element 'c', starting from the right and
                            //   using 'a' as the accumulator:
      y < 1918 ?            //     if y is less than 1918:
        `(${c})/(${a})`     //       transform 'X%Y' into '(X)/(Y)'
      :                     //     else:
        c + '/' + a         //       just replace '%' with '/'
    )                       //   end of reduceRight()
  )                         // end of eval()

Dealing with nested expressions

As mentioned above, the function \$h\$ is designed to operate on a leaf expression, i.e. an expression without any other sub-expression enclosed in parentheses.

That's why we use the helper function \$g\$ to recursively identify and process such leaf expressions.

g = e => (            // e = input
  e !=                // compare the current expression with
    ( e = e.replace(  // the updated expression where:
        /\([^()]*\)/, //   each leaf expression '(A)'
        h             //   is processed with h
      )               // end of replace()
    ) ?               // if the new expression is different from the original one:
      g               //   do a recursive call to g
    :                 // else:
      h               //   invoke h on the final string
)(e)                  // invoke either g(e) or h(e)
\$\endgroup\$
  • \$\begingroup\$ Here's a version of h that's 9 bytes shorter: h=e=>eval(e.split`%`.reduceRight((a,c)=>y<1918?`(${c})/(${a})`:c+'/'+a)) \$\endgroup\$ – Scott Hamper Mar 1 at 4:07
  • \$\begingroup\$ @ScottHamper Very nice. 'Right to left' should have ring a bell ... but it didn't. \$\endgroup\$ – Arnauld Mar 1 at 12:22
5
\$\begingroup\$

Python 3.8 (pre-release), 324 310 306 bytes

lambda s,y:eval((g(s*(y<1918))or s).replace('%','/'))
def g(s):
 if'%'not in s:return s
 l=r=j=J=i=s.find('%');x=y=0
 while j>-1and(x:=x+~-')('.find(s[j])%3-1)>-1:l=[l,j][x<1];j-=1
 while s[J:]and(y:=y+~-'()'.find(s[J])%3-1)>-1:r=[r,J+1][y<1];J+=1
 return g(s[:l]+'('+g(s[l:i])+')/('+g(s[i+1:r])+')'+s[r:])

Try it online!

Takes % instead of ÷ and * instead of ×

\$\endgroup\$
1
\$\begingroup\$

Perl 5, 47 97 95 bytes

/ /;$_="($`)";$'<1918?s-%-)/(-g:y-%-/-;$_=eval

$_="($F[0])";1while$F[1]<1918&&s-\([^()]+\)-local$_=$&;s,%,)/((,rg.")"x y,%,,-ee;y-%-/-;$_=eval

TIO

\$\endgroup\$
  • 3
    \$\begingroup\$ Very nice idea. However, you have an issue with 4%2%2 which returns 1 in both cases. (whereas it should return 4 pre-1918) \$\endgroup\$ – Dada Feb 28 at 11:23
  • \$\begingroup\$ it's true, i can't look anymore for the moment \$\endgroup\$ – Nahuel Fouilleul Feb 28 at 11:28
  • 1
    \$\begingroup\$ @Dada, fixed (+50bytes) \$\endgroup\$ – Nahuel Fouilleul Feb 28 at 20:49
1
\$\begingroup\$

Rust - 1066 860 783 755 740 bytes

macro_rules! p{($x:expr)=>{$x.pop().unwrap()}}fn t(s:&str,n:i64)->f64{let (mut m,mut o)=(vec![],vec![]);let l=|v:&Vec<char>|*v.last().unwrap();let z=|s:&str|s.chars().nth(0).unwrap();let u=|c:char|->(i64,fn(f64,f64)->f64){match c{'÷'=>(if n<1918{-1}else{6},|x,y|y/x),'×'|'*'=>(4,|x,y|y*x),'-'=>(2,|x,y|y-x),'+'=>(2,|x,y|y+x),'/'=>(5,|x,y|y/x),_=>(0,|_,_|0.),}};macro_rules! c{($o:expr,$m:expr)=>{let x=(u(p!($o)).1)(p!($m),p!($m));$m.push(x);};};for k in s.split(" "){match z(k){'0'..='9'=>m.push(k.parse::<i64>().unwrap() as f64),'('=>o.push('('),')'=>{while l(&o)!='('{c!(o,m);}p!(o);}_=>{let j=u(z(k));while o.len()>0&&(u(l(&o)).0.abs()>=j.0.abs()){if j.0<0&&u(l(&o)).0<0{break;};c!(o,m);}o.push(z(k));}}}while o.len()>0{c!(o,m);}p!(m)}

Rust does not have anything like 'eval' so this is a bit tough. Basically, this is a bog-standard Djisktra shunting-yard infix evaluator with a minor modification. ÷ is an operator with a variable precedence: lower than everything else (but parenthesis) in <1918 mode, higher than everything else in >=1918 mode. It is also 'right associated' (or left?) for <1918 to meet the 4÷2÷2 specification, and association is 'faked' by making ÷ precedence negative, then during evaluation treating any precedence <0 as associated. There's more room for golfing but this is a good draft i think.

Ungolfed at play.rust-lang.org

\$\endgroup\$
  • \$\begingroup\$ Do you really need that much whitespace? I think most of it could be removed. \$\endgroup\$ – ivzem Mar 9 at 12:55
  • \$\begingroup\$ @ivzem good point, but still its 3 times bigger than python \$\endgroup\$ – don bright Mar 9 at 19:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.