Matching brackets in a string

后端 未结 9 1607
小蘑菇
小蘑菇 2020-12-03 03:47

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\"
<
相关标签:
9条回答
  • 2020-12-03 04:07

    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.

    0 讨论(0)
  • 2020-12-03 04:11

    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

    0 讨论(0)
  • 2020-12-03 04:15

    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.

    0 讨论(0)
  • 2020-12-03 04:18

    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

    enter image description here

    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

    enter image description here

    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.

    enter image description here

    From this, the positions of the first of Part's double bracket pair can be found as

    enter image description here

    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:

    • For each non-zero position in closeBracket, say i, go to the corresponding position in openBracket and find the first non-zero position to the left of it (say j).
    • Set doubleCloseBrackets[[i-1]]=closeBracket[[i]]+openBracket[[j]]+doubleOpenBrackets[[j]].
    • You can see that doubleCloseBrackets is the counterpart of doubleOpenBrackets and is non-zero at the position of the first of Part's ]] pair.

    enter image description here

    enter image description here

    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 .

    enter image description here

    Finally, by deleting the element next to the ones that were changed, you can get your modified string with [[]] replaced by 〚 〛

    enter image description here

    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)]
    
    0 讨论(0)
  • 2020-12-03 04:23

    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]]]]]]]]]]]"]
    

    ->

    enter image description here

    Just trying to exploit Mma's own parser ...

    0 讨论(0)
  • 2020-12-03 04:24

    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.

    0 讨论(0)
提交回复
热议问题