What is the most efficient or elegant method for matching brackets in a string such as:
\"f @ g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]] // z\"
<
Other answers have made it moot, I think, but here's a more Mathematica-idiomatic version of yoda's first solution. For a long enough string, parts of it may be a bit more efficient, besides.
str = "f @ g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]] // z";
charCode = ToCharacterCode@str;
openBracket = Boole@Thread[charCode == 91];
closeBracket = -Boole@Thread[charCode == 93];
doubleOpenBracket = openBracket RotateLeft@openBracket;
posClose = Flatten@Position[closeBracket, -1, {1}];
doubleCloseBracket = 0*openBracket;
openBracketDupe = openBracket + doubleOpenBracket;
Do[
tmp = Last@DeleteCases[Range@i*Sign@openBracketDupe[[1 ;; i]], 0];
doubleCloseBracket[[i - 1]] =
closeBracket[[i]] + openBracketDupe[[tmp]];
openBracketDupe[[tmp]] = 0, {i, posClose}]
counter = Range@Length@charCode;
changeOpen = DeleteCases[doubleOpenBracket*counter, 0];
changeClosed = DeleteCases[doubleCloseBracket*counter, 0];
charCode[[changeOpen]] = First@ToCharacterCode["\[LeftDoubleBracket]"];
charCode[[changeClosed]] =
First@ToCharacterCode["\[RightDoubleBracket]"];
FromCharacterCode@Delete[charCode, List /@ Flatten@{1 + changeOpen, 1 + changeClosed}]
This way of setting "tmp" may be LESS efficient, but I think it's interesting.
Here's another one with pattern matching, probably similar to what Sjoerd C. de Vries does, but this one operates on a nested-list structure that is created first, procedurally:
FirstStringPosition[s_String, pat_] :=
Module[{f = StringPosition[s, pat, 1]},
If[Length@f > 0, First@First@f, Infinity]
];
FirstStringPosition[s_String, ""] = Infinity;
$TokenizeNestedBracePairsBraces = {"[" -> "]", "{" -> "}", "(" -> ")"(*,
"<"\[Rule]">"*)};
(*nest substrings based on parentheses {([*) (* TODO consider something like http://stackoverflow.com/a/5784082/524504, though non procedural potentially slower*)
TokenizeNestedBracePairs[x_String, closeparen_String] :=
Module[{opString, cpString, op, cp, result = {}, innerResult,
rest = x},
While[rest != "",
op = FirstStringPosition[rest,
Keys@$TokenizeNestedBracePairsBraces];
cp = FirstStringPosition[rest, closeparen];
Assert[op > 0 && cp > 0];
Which[
(*has opening parenthesis*)
op < cp
,(*find next block of [] *)
result~AppendTo~StringTake[rest, op - 1];
opString = StringTake[rest, {op}];
cpString = opString /. $TokenizeNestedBracePairsBraces;
rest = StringTake[rest, {op + 1, -1}];
{innerResult, rest} = TokenizeNestedBracePairs[rest, cpString];
rest = StringDrop[rest, 1];
result~AppendTo~{opString, innerResult, cpString};
, cp < Infinity
,(*found searched closing parenthesis and no further opening one \
earlier*)
result~AppendTo~StringTake[rest, cp - 1];
rest = StringTake[rest, {cp, -1}];
Return@{result, rest}
, True
,(*done*)
Return@{result~Append~rest, ""}
]
]
];
(* TODO might want to get rid of empty strings "", { generated here:
TokenizeNestedBracePairs@"f @ g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]] \
// z"
*)
TokenizeNestedBracePairs[s_String] :=
First@TokenizeNestedBracePairs[s, ""]
and with these definitions then
StringJoin @@
Flatten[TokenizeNestedBracePairs@
"f @ g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]] // z" //. {"[", {"", \
{"[", Longest[x___], "]"}, ""}, "]"} :> {"\[LeftDoubleBracket]", {x},
"\[RightDoubleBracket]"}]
gives
Ok, here is another answer, a bit shorter:
Clear[replaceDoubleBrackets];
replaceDoubleBrackets[str_String, openSym_String, closeSym_String] :=
Module[{n = 0},
Apply[StringJoin,
Characters[str] /. {"[" :> {"[", ++n},
"]" :> {"]", n--}} //. {left___, {"[", m_}, {"[", mp1_},
middle___, {"]", mp1_}, {"]", m_}, right___} /;
mp1 == m + 1 :> {left, openSym, middle,
closeSym, right} /. {br : "[" | "]", _Integer} :> br]]
Example:
In[100]:= replaceDoubleBrackets["f[g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]]]", "(", ")"]
Out[100]= "f[g[h(i(j[2], k(1, m(1, n[2]))))]]"
EDIT
You can also use Mathematica built-in facilities, if you want to replace double brackets specifically with the symbols you indicated:
Clear[replaceDoubleBracketsAlt];
replaceDoubleBracketsAlt[str_String] :=
StringJoin @@ Cases[ToBoxes@ToExpression[str, InputForm, HoldForm],
_String, Infinity]
In[117]:= replaceDoubleBracketsAlt["f[g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]]]"]
Out[117]= f[g[h[[i[[j[2],k[[1,m[[1,n[2]]]]]]]]]]]
The result would not show here properly, but it is a Unicode string with the symbols you requested.
When I wrote my first solution, I hadn't noticed that you just wanted to replace the [[
with 〚
in a string, and not an expression. You can always use HoldForm
or Defer
as
but I think you already knew that, and you want the expression as a string, just like the input (ToString@
on the above doesn't work)
As all the answers so far focus on string manipulations, I'll take a numeric approach instead of wrestling with strings, which is more natural to me. The character code for [
is 91 and ]
is 93. So doing the following
gives the locations of the brackets as a 0/1
vector. I've negated the closing brackets, just to aid the thought process and for use later on.
NOTE: I have only checked for divisibility by 91 and 93, as I certainly don't expect you to be entering any of the following characters, but if, for some reason you choose to, you can easily AND
the result above with a boolean list of equality with 91 or 93.
From this, the positions of the first of Part
's double bracket pair can be found as
The fact that in mma, expressions do not start with [
and that more than two [
cannot appear consecutively as [[[...
has been implicitly assumed in the above calculation.
Now the closing pair is trickier to implement, but simple to understand. The idea is the following:
closeBracket
, say i
, go to the corresponding position in openBracket
and find the first non-zero position to the left of it (say j
). doubleCloseBrackets[[i-1]]=closeBracket[[i]]+openBracket[[j]]+doubleOpenBrackets[[j]]
. doubleCloseBrackets
is the counterpart of doubleOpenBrackets
and is non-zero at the position of the first of Part
's ]]
pair.So now we have a set of Boolean positions for the first open bracket. We simply have to replace the corresponding element in charCode
with the equivalent of 〚
and similarly, with the Boolean positions for the first close bracket, we replace the corresponding element in charCode
with the equivalent of 〛
.
Finally, by deleting the element next to the ones that were changed, you can get your modified string with [[]]
replaced by 〚 〛
NOTE 2:
A lot of my MATLAB habits have crept in the above code, and is not entirely idiomatic in Mathematica. However, I think the logic is correct, and it works. I'll leave it to you to optimize it (me thinks you can do away with Do[]
) and make it a module, as it would take me a lot longer to do it.
Code as text
Clear["Global`*"]
str = "f[g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]]]";
charCode = ToCharacterCode@str;
openBracket = Boole@Divisible[charCode, First@ToCharacterCode["["]];
closeBracket = -Boole@
Divisible[charCode, First@ToCharacterCode["]"]];
doubleOpenBracket =
Append[Differences@Accumulate[openBracket], 0] openBracket;
posClose = Flatten@Drop[Position[closeBracket, Except@0, {1}], 1];
doubleCloseBracket = ConstantArray[0, Dimensions@doubleOpenBracket];
openBracketDupe = openBracket + doubleOpenBracket;
Do[
tmp = Last@
Flatten@Position[openBracketDupe[[1 ;; i]], Except@0, {1}];
doubleCloseBracket[[i - 1]] =
closeBracket[[i]] + openBracketDupe[[tmp]];
openBracketDupe[[tmp]] = 0;,
{i, posClose}];
changeOpen =
Cases[Range[First@Dimensions@charCode] doubleOpenBracket, Except@0];
changeClosed =
Cases[Range[First@Dimensions@charCode] doubleCloseBracket,
Except@0];
charCode[[changeOpen]] = ToCharacterCode["\[LeftDoubleBracket]"];
charCode[[changeClosed]] = ToCharacterCode["\[RightDoubleBracket]"];
FromCharacterCode@
Delete[Flatten@charCode,
List /@ (Riffle[changeOpen, changeClosed] + 1)]
Edited (there was an error there)
Is this too naïve?
doubleB[x_String] :=
StringReplace[
ToString@StandardForm@
ToExpression["Hold[" <> x <> "]"],
{"Hold[" -> "", RegularExpression["\]\)$"] -> "\)"}];
doubleB["f[g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]]]"]
ToExpression@doubleB["f[g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]]]"]
->
Just trying to exploit Mma's own parser ...
You need a stack to do this right; there's no way to do it correctly using regular expressions.
You need to recognize [[
as well as the depth of those brackets, and match them with a ]]
which has the same depth. (Stacks do this very nicely. As long as they don't overflow :P)
Without using some sort of a counter, this is not possible. Without having some maximum depth defined, it's not possible to represent this with a finite state automata, so it's not possible to do this with a regular expression.
Note: here's an example of a string that would not be parsed correctly by a regular expression:
[1+[[2+3]*4]] = 21
This would be turned into
[1 + 2 + 3] * 4 = 24
Here is some java-like pseudocode:
public String minimizeBrackets(String input){
Stack s = new Stack();
boolean prevWasPopped = false;
for(char c : input){
if(c=='['){
s.push(i);
prevWasPopped = false;
}
else if(c==']'){
//if the previous step was to pop a '[', then we have two in a row, so delete an open/close pair
if(prevWasPopped){
input.setChar(i, " ");
input.setChar(s.pop(), " ");
}
else s.pop();
prevWasPopped = true;
}
else prevWasPopped = false;
}
input = input.stripSpaces();
return input;
}
Note that I cheated a bit by simply turning them into spaces, then removing spaces afterwards... this will NOT do what I advertised, it will destroy all spaces in the original string as well. You could simply log all of the locations instead of changing them to spaces, and then copy over the original string without the logged locations.
Also note that I didn't check the state of the stack at the end. It is assumed to be empty, because every [
character in the input string is assumed to have its unique ]
character, and vice versa. If the stack throws a "you tried to pop me when i'm empty" exception at any point, or is not empty at the end of the run, you know that your string was not well formed.