Create an omnifix calculator

17

Inspiration. Inverse.

Evaluate a given omnifix expression.

Omnifix is like normal mathematics' infix notation, but with additional copies of each symbol surrounding the arguments. The outer symbols take the place of parentheses, and there is therefore no need for additional parentheses.

You must support addition, subtraction, multiplication, division, and positive real numbers (negative ones can be written -0-n-) within a reasonable range for your language.

Plus and minus must be + and -, but you may use * or × for times and / or ÷ for divide. Other reasonable symbols will be allowed upon request.

Brownie points for explanation and additional features (like additional operations, negative numbers, strings, etc.) Even if your answer does not have these features, feel free to show how it could.

Please provide a link to test your solution if at all possible.

Examples

For clarity, the explanations below use high minus (¯) to indicate negative numbers. You may return negative numbers using any reasonable format.

-5-2-3

+2+×3×2×+8 (+2+×3×2×++2+6+8)

-14--3-1--12 (-4--3-1---14-2-12)

+2.1+×3.5×2.2×+9.8 (+2.1+×3.5×2.2×++2.1+7.7+9.8)

×3×÷-0-6-÷2÷×-9 (×3×÷-0-6-÷2÷××3×÷¯6÷2÷××3ׯ3ׯ9)

÷4÷-3-÷1÷2÷-÷1.6 (÷4÷-3-÷1÷2÷-÷÷4÷-3-0.5-÷÷4÷2.5÷1.6)

Adám

Posted 2017-07-27T11:25:43.753

Reputation: 37 779

1The explanations below use high minus (\¯`) to indicate negative numbers.` You definitely love APL. – Erik the Outgolfer – 2017-07-27T13:16:41.047

@EriktheOutgolfer You have a better suggestion? Also, TI-BASIC uses high minus. – Adám – 2017-07-27T13:36:19.190

Actually not since -s can be confused with -s while ¯s can't be confused with -s. – Erik the Outgolfer – 2017-07-27T13:37:57.530

Bah, I just noticed the real number requirement. So much for my 290-byte integer arithmetic Retina solution... – Neil – 2017-07-27T13:43:36.877

@Neil Why don't you post it as an answer? – Adám – 2017-07-31T09:15:46.027

@Adám Your wish is my command! – Neil – 2017-07-31T10:07:12.443

Answers

4

Python 3, 159 158 152 144 136 135 132 bytes

def t(i,a=1):
 while'-'<l[i]!='/':i+=1;a=0
 if a:l[i]='(';i=t(t(i+1));l[i-1]=')'
 return-~i
*l,=input()
t(0)
print(eval(''.join(l)))

Try it online!

Doesn't allow negative numbers (though -0-5- works of course) and requires python operators.

Arfie

Posted 2017-07-27T11:25:43.753

Reputation: 1 230

Can you add a TIO link? – Adám – 2017-07-27T12:07:26.860

Sure, give me a minute to figure that out – Arfie – 2017-07-27T12:10:54.340

1while~-(l[i]in'+-*/'):i+=1;a=1 and *l,=input() for 152 bytes – Felipe Nardi Batista – 2017-07-27T12:36:19.237

1

with all the test cases: link

– Felipe Nardi Batista – 2017-07-27T12:40:47.437

1144 bytes – Felipe Nardi Batista – 2017-07-27T12:48:28.510

1if a:l[i]='(';i=t(t(i+1));l[i-1]=')' with return-~i for 135 bytes :P – Felipe Nardi Batista – 2017-07-27T13:03:44.643

4

C# (.NET Core), 198 197 188 bytes

float O(string s){try{return float.Parse(s);}catch{var f=s[0];int i=s.IndexOf(f,1);float a=O(s.Substring(1,i-1)),b=O(s.Substring(i+1,s.Length-i-2));return f<43?a*b:f<44?a+b:f<46?a-b:a/b;}}

Try it online!

Uses * and /.

A recursive function. It first tries to parse the input string as a float. If it fails, it calls itself recursively passing as arguments the first and second operands and then performs the selected operation on the results.

  • 1 byte saved thanks to Mr. Xcoder!
  • 9 bytes saved thanks to TheLethalCoder!

Charlie

Posted 2017-07-27T11:25:43.753

Reputation: 11 448

IndefOf(f, 1) can be IndexOf(f,1) – Mr. Xcoder – 2017-07-27T12:22:52.827

1Use floats instead, use the char codes, when you have them can probably shorten them with > and < in a couple of places. – TheLethalCoder – 2017-07-27T12:24:33.783

You can golf a byte changing i+1,s.Length-i-2 to ++i,s.Length+~i. – Kevin Cruijssen – 2018-02-05T12:17:49.287

3

Retina, 290 287 286 bytes

\d+
¦$&$*
¯¦
¯
{`\+([¯¦]1*)\+([¯¦]1*)\+
-$1-¯$2-
-(¯|¦)(1*)-([¯¦]+1*\2)-
-¯$3-¯$1$2-
(×|÷)¯(1*\1)([¯¦]1*\1)
$1¦$2¯$3
צ×[¯¦]1*×|¯¯¦?
¦
¯¦|¦¯
¯
+`-((¯|¦)1*)(1*)-\2\3-
$1
-([¯¦]1*)-[¯¦](1*)-
$1$2
צ1(1*)×([¯¦]1*)×
+צ$1×$2×+$2+
}`÷¦(?=1*÷(¯|¦)(1+)÷)(\2)*1*÷\1\2÷
$1$#3$*
((¯)|¦)(1*)
$2$.3

Try it online! Note: Only capable of integer arithmetic, so some of the test cases have been removed. Accepts and returns negative numbers using the ¯ prefix. Edit: Saved 3 4 bytes thanks to @Cowsquack. Explanation:

\d+
¦$&$*

I needed some way of handling zero, so I use ¦ as a positive number prefix. The numbers are then converted to unary.

¯¦
¯

But negative numbers only need a ¯ prefix.

{`\+([¯¦]1*)\+([¯¦]1*)\+
-$1-¯$2-

Quoting +s gets ugly, so I turn additions into subtractions.

-(¯|¦)(1*)-([¯¦]+1*\2)-
-¯$3-¯$1$2-

If the absolute value of the LHS of a subtraction is less than the RHS, switch them around and negate both sides.

(×|÷)¯(1*\1)([¯¦]1*\1)
$1¦$2¯$3

Also if the LHS of a multiplication or division is negative, negate both sides.

צ×[¯¦]1*×|¯¯¦?
¦

Also if the LHS of a multiplication is zero, then the result is zero. Also, two minuses make a plus.

¯¦|¦¯
¯

But a minus and a plus (or vice versa) make a minus.

+`-((¯|¦)1*)(1*)-\2\3-
$1

Subtract two numbers of the same sign. Do this repeatedly until there are no such subtractions left.

-([¯¦]1*)-[¯¦](1*)-
$1$2

If there's still a subtraction, the signs must be different, so add the numbers together. (But only do this once, since this may reveal a subtraction of two numbers of the same sign again.)

צ1(1*)×([¯¦]1*)×
+צ$1×$2×+$2+

Perform multiplication by repeated addition.

}`÷¦(?=1*÷(¯|¦)(1+)÷)(\2)*1*÷\1\2÷
$1$#3$*

Perform integer division. One of the above steps will have simplified the expression, so loop back until there are no operations left.

((¯)|¦)(1*)
$2$.3

Convert back to decimal.

Neil

Posted 2017-07-27T11:25:43.753

Reputation: 95 035

Wow, that is – epic. Largest Retina post on PPCG? However, usually QuadR and Retina solutions resemble each other closely. Can I maybe inspire?

– Adám – 2017-07-31T10:15:13.343

In this line +\-(([¯¦])1)(1)-\2\3-,[¯¦]can become¯|¦` – user41805 – 2017-07-31T10:18:33.223

@Cowsquack Happens three times in fact, thanks! – Neil – 2017-08-02T14:24:52.480

There's one you missed ([×÷]) ;) – user41805 – 2017-08-02T14:30:33.063

1@Cowsquack You'd better not find another one, otherwise I'll have to cross out 4... – Neil – 2017-08-02T15:09:48.057

2

PHP, 116 114 109 bytes

-5 thanks to Martin Ender

for($s=$argv[$o=1];$o!=$s;)$s=preg_replace('#([*+/-])(([\d.]+|(?R))\1(?3))\1#','($2)',$o=$s);eval("echo$s;");

Uses * for multiplication and / for division. Negative numbers happen to work despite me making no specific attempts for that to be the case.

Try it online!

Ungolfed and Explained

for($s=$argv[$o=1];   # Initialize with $s = input and $o = 1;
    $o!=$s;)          # While $o != $s
    # Set $o to $s and set $s to be $s after this regex replacement:
    $s=preg_replace('#([*+/-])(([\d.]+|(?R))\1(?3))\1#','($2)',$o=$s);
    # i.e., continue to do this replacement until the result is the same on two consecutive
    # steps (no replacement was made)
# Once $o == $s (i.e. no more replacement can be made), eval the result and print
eval("echo$s;"); 

I'll also explain the regex because it's a bit magical:

([*+/-])(([\d.]+|(?R))\1(?3))\1


([*+/-])

First, we want to match any of the four operators: *+/-

([\d.]+|(?R))

Then, we need to match either a number [\d.]+ or another valid omnifix expression (?R).

\1

Then, we match the same operator that was at the beginning.

(?3)

Then we do the same thing we did in group 3: match a number or an omnifix expression.

\1

Finally, match the initial operator again.

Whatever matches this is replaced with ($2). This takes the part inside the surrounding operators and puts it inside brackets, so it looks like normal infix notation.

Business Cat

Posted 2017-07-27T11:25:43.753

Reputation: 8 927

2

QuadR, 33 32 27 bytes

-1 thanks to Cows Quack. -5 thanks to Erik the Outgolfer.

((.)[\d¯\.]+){2}\2
⍎¯1↓1↓⍵M

with the argument/flag

Try it online!

This is equivalent to the 40-byte Dyalog APL solution:

'((.)[\d¯\.]+){2}\2'⎕R{⍕⍎1↓¯1↓⍵.Match}⍣≡

Try it online!

Explanation

(parenthesised text refers to Dyalog APL instead of QuadR)

(){2}\2 the following pattern twice, and the whole match twice too:
  (.) any character
  []+ followed by one or more of the following set of characters:
   \ddigits,
   ¯ high minus (negative sign)
   \. period

(⎕R is Replaced with:)

({} the result of the following anonymous function applied to the namespace ⍵:)

⍵M (⍵.Match) the text of the Match
¯1↓ drop the last character (the symbol + - × or ÷)
1↓ drop the first character (symbol)
 execute as APL code
 ( stringify)

 (⍣≡) repeat the replacement until no more changes happen

Adám

Posted 2017-07-27T11:25:43.753

Reputation: 37 779

I think you can drop – user41805 – 2017-07-31T10:23:41.393

@Cowsquack You are right about QuadR. ⎕R cannot operate on numeric data. Thanks. – Adám – 2017-07-31T10:24:23.977

Shorter regex; -5 bytes. – Erik the Outgolfer – 2018-02-05T13:41:49.010

1

Haskell, 132 chars

(134 bytes, because × and ÷ take two bytes in UTF-8)

f y|(n@(_:_),r)<-span(`elem`['.'..'9'])y=(read n,r)
f(c:d)|(l,_:s)<-f d,(m,_:r)<-f s=(o[c]l m,r)
o"+"=(+)
o"-"=(-)
o"×"=(*)
o"÷"=(/)

Try it online!

f parses as much of the input as it can and yields the result as well as the remaining string (which is empty in the test cases). If that's not rule-conformant, strip the unparseable remainder string with

Haskell, 139 chars

...
g=fst.f

ceased to turn counterclockwis

Posted 2017-07-27T11:25:43.753

Reputation: 5 200

0

Perl, 64 53 bytes

Include +1 for -p

perl -pe 's%([*-/])(([\d.]+|(?0))\1(?3))\1%($2)%&&redo;$_=eval' <<< "/4/-3-/1/2/-/"

Accidentally also implements , (throws away the first argument) and sometimes . (append the arguments together). . doesn't work very reliable though since it interferes with decimal point both at the parsing level and at the evaluation level

Ton Hospel

Posted 2017-07-27T11:25:43.753

Reputation: 14 114

0

Java 8, 205 200 bytes

float O(String s){try{return new Float(s);}catch(Exception e){int f=s.charAt(0),i=s.indexOf(f,1);float a=O(s.substring(1,i)),b=O(s.substring(i+1,s.length()-1));return f<43?a*b:f<44?a+b:f<46?a-b:a/b;}}

Port of @Charlie's C# answer.
-5 bytes thanks to @ceilingcat.

Try it online.

Kevin Cruijssen

Posted 2017-07-27T11:25:43.753

Reputation: 67 575