24
\$\begingroup\$

Given an expression like 4 / 2 + 3, determine whether the expression has the same value in Python 2 and Python 3. Recall that in Python 2 division rounds down (so that 5 / 2 -> 2), whereas in Python 3 division returns a float (so that 5 / 2 -> 2.5).

For simplicity's sake, we will only consider expressions of the form:

a o1 b o2 c

Where

  • a, b, and c are numbers between 1 and 5 inclusive; and
  • o1 and o2 are either the addition, multiplication or division operators (+, *, /)

Note that the restrictions mean the expression will always be the same length and will never have any zero division issues.

Operator precedence is the same as in Python (* and / have equal and higher precedence to +, evaluated left to right).

Since this is in part a parsing challenge, you must take the input exactly as given including spacing in a string-like form appropriate for your language (and not, for example, a preparsed AST).

You can print or return any two distinct consistent values to indicate true or false.

Test cases

1 + 2 + 3 -> true
2 * 3 + 5 -> true
3 / 3 + 3 -> true
4 / 2 * 2 -> true
2 * 2 / 4 -> true
4 / 2 / 1 -> true
5 + 4 / 2 -> true

1 / 2 + 3 -> false
5 / 2 * 2 -> false
3 / 2 * 4 -> false
2 + 3 / 5 -> false
4 / 4 / 4 -> false

Scoring

This is code golf, shortest code in bytes wins.

\$\endgroup\$
7
  • 2
    \$\begingroup\$ I think character codepoints would be acceptable. @AidenChow \$\endgroup\$
    – Sisyphus
    Apr 27, 2022 at 5:16
  • 4
    \$\begingroup\$ But 4/2 still gives different types in Python 2 vs Python 3. For example, you can write x = 4 / 2; print('string'[x]); in Python 2, but not Python 3. \$\endgroup\$
    – tsh
    Apr 27, 2022 at 5:40
  • 8
    \$\begingroup\$ I'm waiting for a competitive solution to this that isnt simply "eval the string as a python expression" \$\endgroup\$
    – des54321
    Apr 27, 2022 at 6:02
  • 2
    \$\begingroup\$ You should specify the operator precedence in python. \$\endgroup\$
    – qwr
    Apr 28, 2022 at 4:54
  • 3
    \$\begingroup\$ Almost all the answers to this question appear to have accrued at least one downvote. Could the (presumably always the same) downvoter please explain? \$\endgroup\$ May 1, 2022 at 7:46

20 Answers 20

10
\$\begingroup\$

C (clang), 121 \$\cdots\$ 119 117 bytes

#define P(v)v=*x-47?*x-43?v*a:t?a:v:v/a
float d;b;t;a;f(*x){for(t=b=d=*x-48;*++x;t=!++x)a=x[1]-48,P(d),P(b);*x=d==b;}

Try it online!

Inputs the Python expression as a wide string.
Parses the string and returns (at the end of the input string) \$1\$ if the expression is the same in both Python 2 and 3 or \$0\$ otherwise.

Commented code

#define P(v)                              // Macros are type-less so will    
                                          // calculate both floating point   
                                          // and integral values  
            v=                            // set the parameter to:   
              *x-47?                      // is the char at *x not '/' ?    
                    *x-42?                // is the char at *x not '+' ?     
                          v*a:            // then it's '*' so multiply      
                              t?          // here it's '+' so check t   
                                a:        // t is only truthy at the start   
                                          // so set parameter to a   
                                  v:      // after that t's 0 so leave    
                                          // parameter as is   
                                    v/a   // here it's '/' so divide     
float d;                                  // floating point type for Python 3    
                                          // calculations   
        b;                                // integral type for Python 2    
                                          // calculations   
          t;                              // toggle for signalling first and   
                                          // second parts   
            a;                            // value in Python expression   
f(*x) {                                   // function taking wide string    
                                          // parameter
       for(                               // loop over first and  
                                          // second part   
           t=                             // set t to non-zero value   
             b=d=*x-48;                   // set d and b to first number   
                       *++x;              // move pointer to next char and   
                                          // finish looping at the end of the    
                                          // input string   
                            t=!           // after first loop set t=0   
                               ++x)       // bump x again   
            a=x[1]-48,                    // set a to next number   
                      P(d),               // parse with floating point type   
                           P(b);          // parse with integral type   
        *x=d==b;                          // set end of string to 1 if the      
                                          // floating point value is equal to    
                                          // integral or 0 otherwise      
}     
\$\endgroup\$
1
  • \$\begingroup\$ Wow, I've never used wide strings in my life! \$\endgroup\$
    – qwr
    Apr 29, 2022 at 23:58
7
\$\begingroup\$

Python 2, 28 bytes

lambda s:eval("1.*%s.>"%s+s)

Try it online!

This saves a byte using that Python 2 value will always be less or equal within the constraints of this challenge. Inverted output: False means safe, True unsafe.

Old Python 2, 29 bytes

lambda s:eval("1.*%s.=="%s+s)

Try it online!

Forces Python 3 semantics in Python 2 by converting terms 1 and 3 to float, evaluates and compares to the original.

\$\endgroup\$
6
\$\begingroup\$

Python 3.8 (pre-release), 33 bytes

lambda s:eval(s)%1>eval(s[:5])%-1

Try it online!

Port of tsh's Javascript answer.

Old version

Python 3.8 (pre-release), 41 bytes

Thanks @Unrelated String for -2 bytes through some eval wizardry

lambda s:eval(s+'=='+s.replace("/","//"))

Try it online!

\$\endgroup\$
7
  • 1
    \$\begingroup\$ :) \$\endgroup\$ Apr 27, 2022 at 5:31
  • 1
    \$\begingroup\$ 35 porting tsh's answer: lambda s:eval(s)%1>eval(s+'**0')%-1 \$\endgroup\$ Apr 27, 2022 at 7:15
  • 2
    \$\begingroup\$ @dingledooper slicing is shorter than +'**0' in Python. So it is 33 bytes lambda s:eval(s)%1>eval(s[:5])%-1 \$\endgroup\$
    – tsh
    Apr 27, 2022 at 7:33
  • 1
    \$\begingroup\$ why pre-release? \$\endgroup\$
    – gerrit
    Apr 27, 2022 at 15:09
  • 1
    \$\begingroup\$ TIO is pretty out of date in terms of recent releases \$\endgroup\$
    – qwr
    Apr 29, 2022 at 23:59
6
\$\begingroup\$

Desmos, 122 113 bytes

L=l-48
g(a,b,o,k)=\{o=-6:ab,o=-1:a/b,k=0:b,a\}
h(k)=mod(g(g(L[1],L[5],L[3],0),k,L[7],1),1)
F(l)=\{h(L[9])>-h(1)\}

Basically just a port of tsh's Javascript answer, though a bit more complicated (when was using Desmos in a string parsing challenge ever a good idea??).

Takes in input as a list of character codepoints. Returns undefined for truthy cases, and 1 for falsey cases.

Use this TIO link to convert from string input (2 * 3 + 5) to list of codepoints wrapped in the output function (F\left([50, 32, 42, 32, 51, 32, 43, 32, 53]\right)) which can be copy pasted into Desmos directly.

Try It On Desmos!

Try It On Desmos! - Prettified

\$\endgroup\$
5
\$\begingroup\$

JavaScript (Node.js), 29 bytes

s=>eval(s)%1>-eval(s+'**0')%1

Try it online!
Try all testcases!

Basic idea: The expression is "Python 2 / 3 safe" iff both a o1 b o2 c and a o1 b o2 1 is integer after evaluate.

\$\endgroup\$
3
  • \$\begingroup\$ Another (longer) approach: s=>eval(s+'>'+s.replace(/\d+/g,'$&n')) \$\endgroup\$
    – Arnauld
    Apr 27, 2022 at 7:19
  • \$\begingroup\$ I had mentioned it on the Python answer, but you can save a byte: s=>eval(s)%1>-eval(s+'**0')%1 \$\endgroup\$ Apr 27, 2022 at 7:20
  • \$\begingroup\$ @Arnauld You don't need + in the regexp, as we only support numbers between 1 to 5. \$\endgroup\$
    – tsh
    Apr 27, 2022 at 7:58
4
\$\begingroup\$

x86-64 machine code, 34 33 bytes

B8 66 41 AD AC AC 24 07 9E 92 66 98 7A 0A 72 03 F6 E2 92 FF CF 7B EA B8 F6 F2 84 E4 74 F4 24 80 C3

Try it online!

Following the standard calling convention for Unix-like systems (from the System V AMD64 ABI), this takes the length of the string (9) in RDI and its address in RSI, and returns a value in AL, 128 if the expression is Python 2/3 safe and 0 if not.

In assembly:

f:  .byte 0xB8

This will be explained later.

n:  .byte 0x66, 0x41, 0xAD

When jumping to n, the current number will be in DL.

This is a lodsw instruction, encoded in a strange way for reasons to be explained later. This loads two bytes from the string into AX, advancing the pointer; in particular, the operation symbol goes into AH.

    lodsb

Load another byte (a space) from the string into AL.

    lodsb

Load another byte (a digit) from the string into AL.

    and al, 7

Take the low three bits, obtaining the numerical value of the digit in AL.

    sahf

Set certain flags based on certain bits of AH, which contains the operation symbol. In particular:

      PF CF
+ 00101011
* 00101010
/ 00101111
    xchg edx, eax

Exchange registers.

    cbw

Sign-extend AL (previous number); this makes AH 0.

    jp d

Jump if PF=1, which is true only for /.

    jc t

Jump if CF is 1, which is true for + but not for *. An addition breaks a sequence of * and /, making the previous number irrelevant; the current number is correctly in DL.

Only * is left here.

    mul dl

Multiply AL (previous number) by DL (new digit).

s:  xchg edx, eax

Exchange registers. The product goes into DL.

t:  dec edi

(+ joins here.) Subtract 1 from EDI.

    jpo n

Jump if the sum of the low 8 bits is odd. This is true the first two times, with 8 (1000₂) and 7 (111₂), but not true the third time, with 6 (110₂).

    .byte 0xB8

If we get here, the expression is Python 2/3 safe.

This subsumes the next two instructions into mov eax, 0xE484F2F6.

d:  div dl

(Recall that / jumps here.)

Divide AX by DL, putting the quotient in AL and the remainder in AH.

    test ah, ah

Set flags based on the value of AH.

    jz s

If the remainder is 0, jump (and next, exchange the quotient into DL and proceed).

Also, the success case joins here. In that case, the zero flag was last set by dec edi, which gave a nonzero result, so the jump is not taken.

    and al, 0x80

Keep the high bit of AL.

In the success case, the high bit is 1 from the mov.

In the failure case, AL is the quotient, which is at most 12 (from 25/2), so the high bit is 0.

    ret

Return.


Now, the start can be explained. The first byte 0xB8 subsumes the next four bytes into mov eax, 0xACAD4166. Those bytes include a lodsw instruction, which was encoded with a nonfunctional REX.B prefix (0x41) so that that value goes into AH. After that, the first digit is read by lodsb. The 0x41 value, which is in the same place as an operation symbol would be in later iterations, has a 1 in bit 0 and a 0 in bit 2, so it gets treated in the same way as a +, making the first digit alone become the current number.

\$\endgroup\$
3
\$\begingroup\$

Haskell, 118 bytes

f.words
r=read
f(a:"/":b:t)|(q,m)<-r a`divMod`r b=m<1&&f(show q:t)
f(a:"*":b:t)=f$show(r a*r b):t
f(_:_:t)=f t
f _=1>0

Try it online!

Can certainly be outdone, but it's at least something. Core observation is simply that addition doesn't matter at all.

\$\endgroup\$
2
\$\begingroup\$

Jelly, 9 8 bytes

ḣƬ5ŒV€ĊƑ

Try it online!

Port of tsh's (old) JavaScript answer.

ḣ 5         Take the first 5 characters
 Ƭ          repeatedly while unique.
   ŒV€      Eval each result as Python.
      ĊƑ    Are they the same if ceiled?

Jelly, 10 bytes

Ḥẹ¦”/ŒV=ŒV

Try it online!

Have been trying to think of an interesting solution, but a Python eval builtin is a Python eval builtin. Was at least interesting to golf, starting from ”/=kj”/,¹ŒV€E.

Ḥ             Double the characters of the input
  ¦           at indices
 ẹ ”/         of slash.
     ŒV       Concatenate and eval as Python.
       =ŒV    Is it equal to the input evaluated as Python?
\$\endgroup\$
4
  • \$\begingroup\$ You are missing the 3 / 2 * 4 -> false test case in your TIO link. \$\endgroup\$
    – Aiden Chow
    Apr 27, 2022 at 5:55
  • \$\begingroup\$ Pretty sure ḣƬ5ŒV€ĊƑ is 16 bytes. \$\endgroup\$
    – Dan M.
    Apr 29, 2022 at 14:45
  • \$\begingroup\$ @DanM. Jelly uses a custom codepage, not UTF-8. \$\endgroup\$ Apr 29, 2022 at 14:59
  • \$\begingroup\$ @SuperStormer wow, didn't know that interesting \$\endgroup\$
    – Dan M.
    Apr 29, 2022 at 15:06
2
\$\begingroup\$

Perl 5 -p, 38 34 bytes

/. [\/*] ./;$_=(eval.eval$&)!~/\./

Try it online!

Simplified regex since the input format is very strict to save bytes.

\$\endgroup\$
2
\$\begingroup\$

Bash, 52 bytes

Inspired by des54321's comment that said:

I'm waiting for a competitive solution to this that isnt simply "eval the string as a python expression"

I decided to "cheat" even further by actually running both Python versions:

f(){ p=python;r=print\($1;$p\3<<<$r==`$p<<<$r\)`\);}

Try it online!

Simplified, it's essentially just doing:

f() { python3 <<< "print\($0 == `python <<< print\($0\)`\)"; }
\$\endgroup\$
2
\$\begingroup\$

R, 69 68 bytes

Or R>=4.1, 54 bytes by replacing two function occurrences with \s.

Edit: -1 byte thanks to @Dominic van Essen.

function(s,`-`=function(x)eval(parse(t=x)))-s>-paste("`/`=`%/%`;",s)

Try it online!

Outputs inversed TRUE/FALSE.

Compares results of evaluating the expression and evaluating the expression with `/`=`%/%`; prepended (which redefines division to be integer division). My first approach to simply substitute / with %/% in the expression fails due to %/% having higher precedence than / and *.

\$\endgroup\$
2
  • \$\begingroup\$ -1 byte by reversing output... \$\endgroup\$ May 1, 2022 at 14:10
  • \$\begingroup\$ @DominicvanEssen thanks! I thought of using - for truthy/falsey (which I didn't like), but didn't think of the >. \$\endgroup\$
    – pajonk
    May 1, 2022 at 17:58
1
\$\begingroup\$

Wolfram Language (Mathematica): 24 65 Bytes

Realized my old code doesn't work, had to replace it with something more complicated. Also doesn't work on TIO. e="Python"~ExternalEvaluate~#&;e@StringReplace[#,"/"->"//"]==e@#&

Uses the same trick as the 10-byte Jelly answer, but is significantly longer due to Mathematica's ExternalEvaluate taking almost 30 bytes instead of 2.

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

05AB1E (legacy), 9 bytes

'/x.:‚.EË

Try it online or verify all test cases.

Explanation:

Uses the legacy version of 05AB1E for two reasons:

  1. It's build in Python 3, and .E is therefore an 'Eval as Python' builtin, whereas the new 05AB1E version is build in Elixir and .E is an 'Eval as Elixir' builtin.
  2. Double works on strings in the legacy version of 05AB1E, so we can use x instead of „// to save 2 bytes.

Explanation:

'/        '# Push string "/"
  x        # Double it (without popping): "//"
   .:      # Replace all "/" for "//" in the (implicit) input-string
     ‚     # Pair it with the implicit input-string
      .E   # Evaluate both as Python
        Ë  # Check if the results in the pair are the same
           # (after which the result is output implicitly)
\$\endgroup\$
1
\$\begingroup\$

R, 61 59 bytes

(or 52 bytes in R≥4.1 using \ instead of function)

function(s,t=48-s,u=t<2)!(t[1]*t[5]^(-1)^u[3]/t[9]^u[7])%%1

Try it online!

Checks 'by hand' without using eval. Input is list of codepoints.


R, 64 bytes

(or 57 bytes in R≥4.1 using \ instead of function)

function(s,`$`=sapply)any(substring(s,1,3*2:3)$str2lang$eval%%1)

Attempt This Online!

eval approach, but different to that used by pajonk's R answer: here we check that both "a o1 b " and "a o1 b o2 c" are integers when evaluated as expressions in R, which uses / for floating-point division. Output is reversed (TRUE indicates NOT python 2/3 safe).

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

TI-Basic, 44 bytes

Input Str1
"0
For(I,1,9,2
Ans+sub(Str1,I,1
End
fPart(expr(Ans)) or fPart(expr(Ans+"^0
  • uses the same approach than tsh's answer: The expression is "Python 2 / 3 safe" iff both a o1 b o2 c and a o1 b o2 1 is integer after evaluate
  • spaces are significant in TI-Basic, they produce a Syntax Error if evaluated, so the loop keeps only the relevant characters (starting with an extra zero because an empty string is not possible
  • fPart keeps the fractional part of a number
  • outputs 1 for not safe and 0 for safe
\$\endgroup\$
1
\$\begingroup\$

Julia 1.0, 46 40 bytes

~x=eval(Meta.parse(x))%1>0
!s=~s|~s[1:5]

Try it online!

port of tsh's answer

\$\endgroup\$
0
\$\begingroup\$

PARI/GP, 29 bytes

s->!(eval([s,Str(s"^0")])%1.)

Attempt This Online!

A port of tsh's JavaScript answer.


PARI/GP, 48 bytes

s->eval(Str(s"=="strjoin(strsplit(s,"/"),"\\")))

Attempt This Online!

A port of Aiden Chow's Python answer.

\$\endgroup\$
0
\$\begingroup\$

Retina 0.8.2, 73 bytes

 

\d
$*
^1+\+|[+*]1+$

1(?=1*\*(1+))|\*1+
$1
+r`^(\2)+/(1+)\b
$#1$*
^1+$

Try it online! Link includes test cases. Edit: After reading the question more carefully, code now parses according to input rules, saving 1 byte because only digits 1..5 need to be supported (code actually supports 1..9) but adding 3 bytes to delete spaces. Explanation:

 

Delete spaces.

\d
$*

Convert to unary.

^1+\+|[+*]1+$

Delete any additions and trailing multiplications as they have no effect.

1(?=1*\*(1+))|\*1+
$1

If there's still a multiplication left then perform it now.

+r`^(\2)+/(1+)\b
$#1$*

Perform divisions from left to right where possible.

^1+$

Check that all the divisions succeeded.

\$\endgroup\$
0
\$\begingroup\$

Charcoal, 34 bytes

¹Nθ≡S/«Nζ¿﹪θζ⎚≧÷ζθ»+Nθ≧×Nθ≡S/¿﹪θN⎚

Try it online! Link is to verbose version of code. Allows any sized integer inputs but only two space-separated operations and outputs a Charcoal boolean i.e. - for safe, nothing for unsafe. Explanation:

¹

Assume that the expression is safe.

Nθ

Input the first integer.

≡S

Switch over the first operator.

If it's a /, then:

Nζ

Input the second integer.

¿﹪θζ⎚

If the second integer does not divide the first then clear the canvas.

≧÷ζθ»

Otherwise divide the second integer by the first.

+Nθ

If the first operator is a + then ignore the first integer and input the second integer.

≧×Nθ

Otherwise input the second integer and multiply the first integer by it. (This case is chosen as the default case because it can't parse as an expression.)

≡S/

If the second operator is a /, then...

¿﹪θN⎚

... if the third integer does not divide the current total then clear the canvas.

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

MATLAB, 72 bytes

function p(i),eval(i);eval(regexprep(['x=' i],'(\d)','int8($1)'));ans==x

pretty boring, eval the string as doubles and then again as integers. If the expression is different as an integer, then yeah. I don't know python, but I can guess how it works.

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ can you show in your TIO that this works? It's not clear how to use it \$\endgroup\$
    – MarcMush
    May 2, 2022 at 22:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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