匹配字符串中的括号

时间:2011-04-25 07:17:36

标签: parsing string wolfram-mathematica

在字符串中匹配括号的最有效或最优雅的方法是什么,如:

"f @ g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]] // z"

是为了用单个字符形式识别和替换[[ Part ]]括号?

我想得到:

enter image description here

其他所有内容完好无损,例如前缀@和后缀//形式完整


对于那些不熟悉的人的Mathematica语法的解释:

函数使用单个方括号作为参数:func[1, 2, 3]

使用双方括号进行部分索引:list[[6]]或使用单字符Unicode双括号:list〚6〛

我的目的是在ASCII文本字符串中标识匹配的[[ ]]表单,并将其替换为Unicode字符〚 〛

9 个答案:

答案 0 :(得分:5)

好的,这是另一个答案,有点短:

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

示例:

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

修改

如果您想使用您指定的符号专门替换双括号,您也可以使用Mathematica内置工具:

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

结果不会在此处正确显示,但它是带有您请求的符号的Unicode字符串。

答案 1 :(得分:5)

当我编写第一个解决方案时,我没有注意到您只想在字符串中用[[替换,而不是表达式。您始终可以使用HoldFormDefer作为

enter image description here

但我认为你已经知道了,并且你想要将表达式作为字符串,就像输入(上面的ToString@不起作用)

由于到目前为止所有的答案都集中在字符串操作上,我将采用数字方法而不是用字符串进行摔跤,这对我来说更自然。 [的字符代码为91,]为93.请执行以下操作

enter image description here

将括号的位置指定为0/1向量。我已经否定了右括号,只是为了帮助思考过程并在以后使用。

注意:我只检查了91和93的可分性,因为我当然不希望您输入以下任何字符,但如果由于某种原因您选择了,您可以使用与91或93相等的布尔列表轻松AND上面的结果。

enter image description here

由此可以找到Part的双括号对的第一个位置

enter image description here

在mma中,表达式不是以[开头而且两个以上的[不能连续出现[[[...这一事实已在上述计算中隐式假设。

现在关闭对很难实现,但很容易理解。这个想法如下:

  • 对于closeBracket中的每个非零位置,例如i,转到openBracket中的相应位置,找到它左边的第一个非零位置(比如说{ {1}})。
  • 设置j
  • 您可以看到doubleCloseBrackets[[i-1]]=closeBracket[[i]]+openBracket[[j]]+doubleOpenBrackets[[j]]doubleCloseBrackets相对应,并且在doubleOpenBrackets的{​​{1}}对的第一个位置处为非零。

enter image description here

enter image description here

所以现在我们为第一个开放式括号设置了一组布尔位置。我们只需要将Part中的相应元素替换为]],并且类似地,使用第一个右括号的布尔位置,我们将charCode中的相应元素替换为等效

enter image description here

最后,通过删除已更改的元素旁边的元素,您可以将charCode替换为的修改后的字符串

enter image description here

注2:

我的很多MATLAB习惯都在上面的代码中悄悄地出现,并且在Mathematica中并不完全是惯用语。但是,我认为逻辑是正确的,并且它有效。我会留给你优化它(我认为你可以取消[[]])并将其作为一个模块,因为这需要我花费更长的时间。

代码为文字

〚 〛

答案 2 :(得分:4)

这是我的尝试。由于存在特殊字符,粘贴的ASCII代码非常难以理解,所以我首先提供它在MMA中的外观。

它的作用基本上是这样的:开始括号总是唯一可识别为单或双。问题在于结束括号。开始括号始终具有字符串包含无括号 + [或[[。不可能有[跟随[[或反之亦然而没有其他字符)(至少,没有错误的代码)。

因此,我们使用它作为一个钩子并开始寻找某些匹配的括号对,即中间没有任何其他括号的那些。由于我们知道类型,“[...]”或“[[...]]”,我们可以用双括号符号替换后者,而前者用未使用的字符替换(我使用表情符号)。这样做是为了在模式匹配过程的下一次迭代中不再发挥作用。

我们重复,直到处理完所有括号,最后再将笑脸转换为单个括号。

你知道,解释比代码更多地使用了字符; - )。

enter image description here

ASCII:

s = "f @ g[hh[[i[[jj[2], k[[1, m[[1, n[2]]]]]]]]]] // z";

myRep[s_String] :=
 StringReplace[s,
  {
   Longest[y : Except["[" | "]"] ..] ~~ "[" ~~ 
     Longest[x : Except["[" | "]"] ..] ~~ "]" :> 
    y <> "\[HappySmiley]" <> x <> "\[SadSmiley]",
   Longest[y : Except["[" | "]"] ..] ~~ "[" ~~ Whitespace ... ~~ "[" ~~
      Longest[x : Except["[" | "]"] ..] ~~ "]" ~~ Whitespace ... ~~ 
     "]" :> y <> "\[LeftDoubleBracket]" <> x <> "\[RightDoubleBracket]"
   }
  ]

StringReplace[FixedPoint[myRep, s], {"\[HappySmiley]" -> "[","\[SadSmiley]" -> "]"}]

哦,而Whitespace部分是因为在Mathematica中,双括号不一定要彼此相邻。 a[ [1] ]a[[1]]一样合法。

答案 3 :(得分:3)

你需要一个堆栈才能做到这一点;没有办法正确使用正则表达式。

您需要识别[[以及这些括号的深度,并将它们与具有相同深度的]]匹配。 (Stacks做得非常好。只要它们不溢出:P)

不使用某种计数器,这是不可能的。如果没有定义一些最大深度,则不可能用有限状态自动机来表示它,因此不可能使用正则表达式来执行此操作。

注意:这是一个正则表达式无法正确解析的字符串示例:

[1+[[2+3]*4]] = 21

这将变成

[1 + 2 + 3] * 4 = 24

这是一些类似java的伪代码:

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;
}

请注意,我通过简单地将它们变成空格来欺骗了一些,然后删除空格......这不会做我宣传的内容,它也会破坏原始字符串中的所有空格。您可以简单地记录所有位置,而不是将它们更改为空格,然后复制原始字符串而不记录位置。

另请注意,我最后没有检查堆栈的状态。它被假定为空,因为假定输入字符串中的每个[字符都有其唯一的]字符,反之亦然。如果堆栈在任何时候抛出“你试图在我空的时候弹出我”异常,或者在运行结束时不是空的,你知道你的字符串没有很好地形成。

答案 4 :(得分:2)

其他答案让我觉得没什么问题,但是这里是yoda第一个解决方案的Mathematica惯用版本。对于足够长的字符串,除此之外,部分字符串可能更有效。

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

这种设置“tmp”的方式可能效率不高,但我认为这很有意思。

答案 5 :(得分:1)

修改

tl; dr版本:

我无意中解决了基本问题,但正则表达式无法计算括号,因此请使用堆栈实现。

更长的版本:

我尊敬的同事是正确的,解决此问题的最佳方法是堆栈实现。如果字符串中存在相同数量的[[,则正则表达式可以分别将]][更改为][[ { {1}}但是,如果练习的全部内容是使用匹配]]内的文本,那么正则表达式不是可行的方法。正则表达式不能计算括号,嵌套逻辑对于简单的正则表达式来说太复杂了。所以简而言之,我相信正则表达式可用于满足基本要求,即将匹配[]更改为匹配[[]],但是您应该使用堆栈,因为它允许更容易操作结果字符串。

抱歉,我完全错过了 mathematica 标签!我会在这里留下我的答案,以防万一有人兴奋并像我一样跳枪。

结束修改

使用不情愿量词的正则表达式应该能够逐步确定字符串中[][[标记的位置,并确保仅在]]的数量等于时才进行匹配[[

的数量

所需的正则表达式将与]]一致,用简单的英语表示:

  • [[{1}?(?!]])*?]]{1}?,从字符串开头一次前进一个字符,直到遇到[[{1}?的一个实例
  • [[如果存在任何与(?!]])*?不匹配的字符,请逐个浏览
  • ]]匹配结束括号

要将双方括号更改为单方括号,请通过在第一个和第三个粒子周围添加括号来标识正则表达式中的组:

]]{1}?

这允许您选择([[{1}?)(?!]])*?(]]{1}?) [[令牌,然后将其替换为]][

答案 6 :(得分:1)

我可以提供一个沉重的方法(不太优雅)。下面是我对简单Mathematica解析器的实现(它只适用于包含代码的Fullform的字符串,可能有双括号的例外 - 我将在这里使用),基于广度优先解析器的相当一般的功能我开发的主要是实现HTML parser

ClearAll[listSplit, reconstructIntervals, groupElements, 
groupPositions, processPosList, groupElementsNested];

listSplit[x_List, lengthlist_List, headlist_List] := 
  MapThread[#1 @@ Take[x, #2] &, {headlist, 
    Transpose[{Most[#] + 1, Rest[#]} &[
      FoldList[Plus, 0, lengthlist]]]}];

reconstructIntervals[listlen_Integer, ints_List] := 
  Module[{missed, startint, lastint},
    startint  = If[ints[[1, 1]] == 1, {}, {1, ints[[1, 1]] - 1}];
    lastint = 
       If[ints[[-1, -1]] == listlen, {}, {ints[[-1, -1]] + 1, listlen}];
    missed = 
      Map[If[#[[2, 1]] - #[[1, 2]] > 1, {#[[1, 2]] + 1, #[[2, 1]] - 1}, {}] &, 
      Partition[ints, 2, 1]];
    missed = Join[missed, {lastint}];
    Prepend[Flatten[Transpose[{ints, missed}], 1], startint]];

groupElements[lst_List, poslist_List, headlist_List] /; 
 And[OrderedQ[Flatten[Sort[poslist]]], Length[headlist] == Length[poslist]] := 
  Module[{totalheadlist, allints, llist},
    totalheadlist = 
     Append[Flatten[Transpose[{Array[Sequence &, {Length[headlist]}], headlist}], 1], Sequence];
  allints = reconstructIntervals[Length[lst], poslist];
  llist = Map[If[# === {}, 0, 1 - Subtract @@ #] &, allints];
  listSplit[lst, llist, totalheadlist]];

  (* To work on general heads, we need this *)

groupElements[h_[x__], poslist_List, headlist_List] := 
   h[Sequence @@ groupElements[{x}, poslist, headlist]];

(* If we have a single head *)
groupElements[expr_, poslist_List, head_] := 
    groupElements[expr, poslist, Table[head, {Length[poslist]}]];


groupPositions[plist_List] :=
     Reap[Sow[Last[#], {Most[#]}] & /@ plist, _, List][[2]];


processPosList[{openlist_List, closelist_List}] :=
   Module[{opengroup, closegroup, poslist},
    {opengroup, closegroup} = groupPositions /@ {openlist, closelist} ;
    poslist =  Transpose[Transpose[Sort[#]] & /@ {opengroup, closegroup}];
    If[UnsameQ @@ poslist[[1]],
       Return[(Print["Unmatched lists!", {openlist, closelist}]; {})],
       poslist = Transpose[{poslist[[1, 1]], Transpose /@ Transpose[poslist[[2]]]}]
    ]
];

groupElementsNested[nested_, {openposlist_List, closeposlist_List}, head_] /; Head[head] =!= List := 
 Fold[
  Function[{x, y}, 
    MapAt[groupElements[#, y[[2]], head] &, x, {y[[1]]}]], 
  nested, 
  Sort[processPosList[{openposlist, closeposlist}], 
   Length[#2[[1]]] < Length[#1[[1]]] &]];

ClearAll[parse, parsedToCode, tokenize, Bracket ];

(* "tokenize" our string *)
tokenize[code_String] := 
 Module[{n = 0, tokenrules},
   tokenrules = {"[" :> {"Open", ++n}, "]" :> {"Close", n--}, 
       Whitespace | "" ~~ "," ~~ Whitespace | ""};
   DeleteCases[StringSplit[code, tokenrules], "", Infinity]];

(* parses the "tokenized" string in the breadth-first manner starting 
   with the outermost brackets, using Fold and  groupElementsNested*)

parse[preparsed_] := 
  Module[{maxdepth = Max[Cases[preparsed, _Integer, Infinity]], 
   popenlist, parsed, bracketPositions},
   bracketPositions[expr_, brdepth_Integer] := {Position[expr, {"Open", brdepth}], 
   Position[expr, {"Close", brdepth}]};  
   parsed = Fold[groupElementsNested[#1, bracketPositions[#1, #2], Bracket] &,
               preparsed, Range[maxdepth]];
   parsed =  DeleteCases[parsed, {"Open" | "Close", _}, Infinity];
   parsed = parsed //. h_[x___, y_, Bracket[z___], t___] :> h[x, y[z], t]];

 (* convert our parsed expression into a code that Mathematica can execute *)
 parsedToCode[parsed_] :=
 Module[{myHold},
   SetAttributes[myHold, HoldAll];   
   Hold[Evaluate[
     MapAll[# //. x_String :> ToExpression[x, InputForm, myHold] &, parsed] /.
      HoldPattern[Sequence[x__][y__]] :> x[y]]] //. myHold[x___] :> x

 ];

(注意在最后一个函数中使用MapAll)。现在,您可以使用它:)

In[27]:= parsed = parse[tokenize["f[g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]]]"]]

Out[27]= {"f"["g"["h"[Bracket[
 "i"[Bracket["j"["2"], 
   "k"[Bracket["1", "m"[Bracket["1", "n"["2"]]]]]]]]]]]}

In[28]:= parsed //. a_[Bracket[b__]] :> "Part"[a, b]


Out[28]= {"f"["g"["Part"["h", 
"Part"["i", "j"["2"], 
 "Part"["k", "1", "Part"["m", "1", "n"["2"]]]]]]]}

现在您可以使用parseToCode

In[35]:= parsedToCode[parsed//.a_[Bracket[b__]]:>"Part"[a,b]]//FullForm

Out[35]//FullForm= Hold[List[f[g[Part[h,Part[i,j[2],Part[k,1,Part[m,1,n[2]]]]]]]]]

修改

以下是根据要求仅进行字符替换所需的补充:

Clear[stringify, part, parsedToString];
stringify[x_String] := x;
stringify[part[open_, x___, close_]] := 
   part[open, Sequence @@ Riffle[Map[stringify, {x}], ","], close];
stringify[f_String[x___]] := {f, "[",Sequence @@ Riffle[Map[stringify, {x}], ","], "]"};

parsedToString[parsed_] := 
 StringJoin @@ Flatten[Apply[stringify, 
  parsed //. Bracket[x__] :> part["yourOpenChar", x, "yourCloseChar"]] //. 
    part[x__] :> x];

以下是我们如何使用它:

In[70]:= parsedToString[parsed]

Out[70]= "f[g[h[yourOpenChari[yourOpenCharj[2],k[yourOpenChar1,m[\
  yourOpenChar1,n[2]yourCloseChar]yourCloseChar]yourCloseChar]\
   yourCloseChar]]]"

答案 7 :(得分:1)

已修改(那里有错误)

这太天真了吗?

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

- &GT;

enter image description here

试图利用Mma自己的解析器......

答案 8 :(得分:1)

这是另一个模式匹配,可能类似于Sjoerd C. de Vries所做的,但是这个操作在嵌套列表结构上,首先是程序性地创建的:

instance.save()

然后使用这些定义

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

给出

enter image description here