Regex (Perl / PCRE / Pythonregex
), 48 47 45 44 43 bytes
s~([*-/]( *(\d++|\((?1)\))){2})(?!\))~($1)~
Try it online! - Perl
Try it online! - PCRE1
Try it online! - PCRE2
Try it online! - Python import regex
This is a single regex substitution to be repeatedly applied until it has nothing to match (or until there is no change, where necessary or convenient).
In the following explanation, ␣
represents a space:
s~ # Begin substitution - match the following:
( # Define subroutine (?1); $1 = the following (the entire match):
[*-/] # Character class of the four arithmetic operators. This also
# includes "," and ".", but those are guaranteed not to be in the
# input.
(
␣* # Any number of spaces, minimum zero.
( # Define subroutine (?2) as an argument to an operator:
\d++ # Any number of digit characters, minimum one; force all of
# them to be consumed (prevent backtracking).
| # or...
\( # An opening parenthesis
(?1) # Recursively call (?1)
\) # A closing parenthesis
)
){2} # Do the above twice (for two arguments).
)
(?!\)) # Assert there is no closing parenthesis following this, as that
# would indicate that this expression has already been parenthesized.
~ # Replace with the following:
($1) # Preserve $1 (the entire match), and surround it with parentheses.
~ # Flags:
# No global flag. For better efficiency, adding the "g" flag
# would allow the substitution loop to end sooner, but it's
# not needed.
Saved 1 byte by using {2}
instead of a subroutine call for the second argument (shamelessly stolen from Neil's regex).
Bonus: Convert Polish notation to infix notation, 58 52 bytes
s~([*-/]) *(\d++|\((?2)(?1)(?2)\)) *((?2))~($2$1$3)~
Try it online! - PCRE2
s~ # Begin substitution - match the following:
([*-/]) # Define subroutine (?1): Character class of the four arithmetic
# operators. This also includes "," and ".", but those are guaranteed
# not to be in the input.
␣* # Any number of spaces, minimum zero.
( # Define subroutine (?2) as an argument to an operator:
\d++ # Any number of digit characters, minimum one; force all of
# them to be consumed (prevent backtracking).
| # or...
\( # An opening parenthesis
(?2) # An argument
(?1) # An operator
(?2) # An argument
\) # A closing parenthesis
)
␣* # Any number of spaces, minimum zero.
( # $3 = the following:
(?2) # second argument
)
# No need for "(?!\))", because parenthesized expressions won't be in
# ARGUMENT ARGUMENT OPERATOR format.
~ # Replace with the following:
($2$1$3) # ( argument1 operator argument2 )
~ # No flags
See also Convert from postfix notation to infix notation, which adds parsing of operator precedence.
Regex (Perl / Boost), 47 46 bytes
s~([*-/] *(\d++|\((?1)\)) *(?2))(?!\))~\($1\)~
Try it online! - Perl
Try it online! - Boost
Boost needs parentheses escaped in its replacement argument, because its conditional-replacement syntax uses parentheses. This isn't compatible with PCRE or Pythonregex
, as they will interpret it as a literal \(
and \)
.
This exposes a bug in Boost's subroutine call processing. If {2}
is used to match two arguments, instead of (?2)
to match the second argument, it makes an incorrect full match. Example:
+(+ 1 2) 3
If the regex ([*-/]( *(\d++|\((?1)\))){2})(?!\))
is used, it matches +(+ 1 2)
instead of the entire expression, so the closing parenthesis is added in the wrong place.
But if the regex ([*-/]( *(\d++|\(([*-/]( *\d++){2})\))){2})(?!\))
(with (?1)
replaced with itself to one level of depth) is used instead, it correctly matches the full string.
The bug is still present in the latest version of Boost. I have reported it.
Regex (PCRE / Ruby), 47 46 44 bytes
s~([*-/]( *(\d++|\(\g<1>\))){2})(?!\))~(\1)~
Try it online! - PCRE1
Try it online! - PCRE2
Try it online! - Ruby
Ported to Ruby's subroutine syntax.
\$\large\textit{Functions}\$
Ruby, 75 74 69 67 66 bytes
-5 bytes thanks to Steffan
-1 byte thanks to Dingus
->s{0while s[/([*-\/]( *(\d++|\(\g<1>\))){2})(?!\))/]&&="(#$1)";s}
Try it online!
Python (with regex
), 107 106 105 bytes
import regex
f=lambda s,p=0:s==p and s or f(regex.sub('([*-/]( *(\d++|\((?1)\))){2})(?!\))',r'(\1)',s),s)
Try it online!
lambda s:[s:=regex.sub('([*-/]( *(\d++|\((?1)\))){2})(?!\))',r'(\1)',s)for i in s][-1]
import regex
Can't Try it online! - Confirmed to work on my machine, but regex
is not installed on TIO or ATO.
Just like in Remove redundant parentheses, it's guaranteed that a fewer number of substitution iterations will be needed than the number of characters in the input, so this "do this for each character of s
" trick works. The maximum number of substitutions approaches \$1/2\$ of the number of characters in the input string:
$$\lim_{n\to\infty}{n\over {2n+2}}= {1\over 2}$$
[char[]]($p="$args")|%{$p=$p-replace'[*-/](?> *(\d+|\(((\()|[^)]|(?<-3>.))+\))){2}(?!\))','($&)'};$p
Try it online!
Uses Neil's regex with an additional -8 byte golf. Applies the substitution the same number of times as the length of the input in characters.
PHP, 104 bytes
function($s){while($p!=$s=preg_replace('~([*-/]( *(\d++|\((?1)\))){2})(?!\))~','($1)',$p=$s));return$s;}
Try it online!
Thanks to Steffan for pointing out a 1 byte golf that has the added bonus of making this anonymous rather than recursive. Knocking off an additional 2 bytes from that has the further bonus of removing the "Undefined variable" warnings from this non-recursive function.
\$\large\textit{Full programs}\$
MATL, 54 48 bytes
-6 bytes thanks to Luis Mendo
t"'([*-/]( *(\d++|\((?1)\))){2})(?!\))' '($1)'YX
Try it online!
This too applies the substitution the same number of times as the length of the input in characters.
Perl -p
, 51 50 49 bytes
1while s;([*-/]( *(\d++|\((?1)\))){2})(?!\));($1)
Try it online!
PHP -F
, 98 bytes
<?for($s=$argn;$p!=$s=preg_replace('~([*-/]( *(\d++|\((?1)\))){2})(?!\))~','($1)',$p=$s););echo$s;
Try it online!
/
and30
right? \$\endgroup\$flex
. \$\endgroup\$/////1 2 3 4 5 6
– a regex 4 bytes shorter passes all the current test cases but breaks on this one. And I'd also suggest/1/2/3/4/5/6/7/8 9
because this pattern approaches the maximum ratio of {number of parentheses pairs added} / {string length}. \$\endgroup\$