Соответствующие скобки в строке - PullRequest
13 голосов
/ 25 апреля 2011

Какой самый эффективный или элегантный метод сопоставления скобок в строке, такой как:

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

5 голосов
/ 25 апреля 2011

Когда я писал свое первое решение, я не заметил, что вы просто хотели заменить [[ на в строке, а не на выражение.Вы всегда можете использовать HoldForm или Defer как

enter image description here

, но я думаю, что вы уже знали об этом, и вы хотите, чтобы выражение представляло собой строку, как ввод (ToString@ на вышесказанном не работает)

Поскольку все ответы пока сосредоточены на манипуляциях со строками, я буду использовать числовой подход вместо борьбы со строками, что для меня более естественно.Код символа для [ равен 91, а ] равен 93. Таким образом, следующие действия

enter image description here

дают расположение скобок в виде вектора 0/1.Я отменил закрывающие скобки, просто чтобы помочь мыслительному процессу и использовать его позже.

ПРИМЕЧАНИЕ: Я проверял на делимость только на 91 и 93, поскольку я, конечно, неЯ не ожидаю, что вы введете любой из следующих символов, но если по какой-то причине вы решите, вы можете легко AND результат выше с логическим списком равенства с 91 или 93.

enter image description here

Исходя из этого, позиции первой пары двойных скобок Part можно найти как

enter image description here

Тот факт, что в MMA выраженийне начинайте с [ и что более двух [ не могут появляться последовательно, поскольку [[[... неявно предполагалось в приведенных выше вычислениях.

Теперь закрывающую пару сложнее реализовать, но легко понять,Идея заключается в следующем:

  • Для каждой ненулевой позиции в closeBracket, скажем i, перейдите в соответствующую позицию в openBracket и найдите первую ненулевую позицию дляслева от него (скажем j).
  • Набор doubleCloseBrackets[[i-1]]=closeBracket[[i]]+openBracket[[j]]+doubleOpenBrackets[[j]].
  • Вы можете видеть, что doubleCloseBrackets является эквивалентом doubleOpenBrackets и отличен от нуля в позиции первой пары из Part * ]].

enter image description here

enter image description here

Итак, теперь у нас есть набор логических позиций для первой открытой скобки.Мы просто должны заменить соответствующий элемент в charCode эквивалентом и, аналогично, с булевыми позициями для первой закрывающей скобки, мы заменим соответствующий элемент в charCode на эквивалент .

enter image description here

Наконец, удалив элемент рядом с теми, которые были изменены, вы можете получить измененную строку с [[]], замененным на 〚 〛

enter image description here

ПРИМЕЧАНИЕ 2:

Многие из моих привычек MATLAB проникли в вышеприведенный код и не совсем идиоматичны в Mathematica.Тем не менее, я думаю, что логика верна, и она работает.Я оставлю это вам, чтобы оптимизировать его (я думаю, вы можете покончить с Do[]) и сделать его модулем, так как это займет у меня намного больше времени.

Кодкак текст

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)]
5 голосов
/ 25 апреля 2011

Хорошо, вот еще один ответ, немного короче:

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

EDIT

Вы также можете использовать встроенные средства 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 с символами, которые вы запрашивали.

4 голосов
/ 26 апреля 2011

Вот моя попытка.Вставленный код 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 голосов
/ 25 апреля 2011

Вам нужен стек, чтобы сделать это правильно;нет способа сделать это правильно с помощью регулярных выражений.

Вам необходимо распознать [[, а также глубину этих скобок, и сопоставить их с ]], который имеет такую ​​же глубину.(Стеки делают это очень хорошо. Пока они не переполняются: 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;
}

Обратите внимание, что я обманулнемного, просто превратив их в пробелы, затем удалив пробелы ... это НЕ будет делать то, что я объявил, это также уничтожит все пробелы в исходной строке.Вы можете просто записать все местоположения вместо того, чтобы заменить их пробелами, а затем скопировать исходную строку без зарегистрированных местоположений.

Также обратите внимание, что в конце я не проверял состояние стека.Предполагается, что он пустой, поскольку предполагается, что каждый символ [ во входной строке имеет свой уникальный символ ], и наоборот.Если в какой-то момент в стеке возникнет исключение «вы пытались выдать меня, когда я пустой», или оно не пусто в конце цикла, вы знаете, что ваша строка была сформирована неправильно.

2 голосов
/ 26 апреля 2011

Другие ответы сделали это спорным, я думаю, но вот более Mathematica-идиоматическая версия первого решения yoda. Для достаточно длинной строки его части могут быть немного более эффективными, кроме того.

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" может быть МЕНЬШЕ эффективным, но я думаю, что это интересно.

1 голос
/ 10 августа 2016

Вот еще один с сопоставлением с образцом, вероятно, похожий на то, что делает Sjoerd C. de Vries, но этот действует на структуру вложенного списка, которая создается сначала, процедурно:

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

и с этимиопределения тогда

StringJoin @@ 
 Flatten[TokenizeNestedBracePairs@
    "f @ g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]] // z" //. {"[", {"", \
{"[", Longest[x___], "]"}, ""}, "]"} :> {"\[LeftDoubleBracket]", {x}, 
     "\[RightDoubleBracket]"}]

дает

enter image description here

1 голос
/ 25 апреля 2011

Отредактировано (там была ошибка)

Это слишком наивно?

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

Просто пытаюсь использовать собственный синтаксический анализатор мамы ...

1 голос
/ 25 апреля 2011

Я могу предложить тяжелый подход (не слишком элегантный). Ниже приведена моя реализация синтаксического анализатора Mathematica (он будет работать только для строк, содержащих Fullform кода, с возможным исключением для двойных скобок - которые я буду использовать здесь), основанных на довольно общей функциональности синтаксического анализатора первой ширины, который Я разработал в основном для реализации HTML-парсер :

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

EDIT

Вот дополнение, необходимое для выполнения замены персонажа, как и было запрошено:

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]]]"
1 голос
/ 25 апреля 2011

Edit

tl; dr version:

Я нахожусь на пути к непреднамеренному решению базовой проблемы, но регулярные выражения не могут считать скобки, поэтому используйтереализация стека.

Более длинная версия:

Мои уважаемые коллеги правы, лучший способ решения этой проблемы - реализация стека.Регулярные выражения могут быть в состоянии изменить [[ и ]] на [ и ] соответственно, если в строке присутствует то же число [[, что и число ]], однако, если вся точкаупражнение состоит в том, чтобы использовать текст в соответствии [], тогда регулярное выражение - не тот путь.Регулярные выражения не могут считать скобки, логика вложения слишком сложна для простого регулярного выражения.Итак, в двух словах, я полагаю, что регулярные выражения могут использоваться для удовлетворения основного требования, которое заключалось в том, чтобы изменить соответствие [[]] на соответствие [], однако вам действительно следует использовать стек, так как это упрощает манипулирование результирующей строкой.

И извините, я полностью пропустил тег mathematica !Я оставлю здесь свой ответ, хотя на всякий случай кто-то взволнован и прыгнет, как я.

Конец редактирования

Регулярное выражение, использующее неохотные кванторыуметь постепенно определять, где токены [[ и ]] находятся в строке, и обеспечивать совпадение только в том случае, если число [[ равно числу ]].

необходимое регулярное выражениебудет выглядеть следующим образом: [[{1}?(?!]])*?]]{1}?, что на простом английском языке:

  • [[{1}?, прогрессировать по одному символу за раз от начала строки до одного экземпляра [[
  • (?!]])*? если существуют какие-либо символы, которые не соответствуют ]], проходите по одному по очереди
  • ]]{1}? соответствуют закрывающей скобке

Чтобы преобразовать двойные квадратные скобки в одинарные квадратные скобки, определите группы в регулярном выражении, добавив скобки вокруг первой и третьей частиц:

([[{1}?)(?!]])*?(]]{1}?)

Это позволяет вам выбрать [[ и]] токи затем замените их на [ или ].

...