Соответствующие скобки в строке
Каков наиболее эффективный или элегантный способ сопоставления скобок в строке, например:
"f @ g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]] // z"
с целью идентификации и замены скобок [[ Part ]]
с одиночными символьными формами?
Я хочу получить:
![enter image description here]()
Если все остальное нетронутым, например префикс @
и postfix //
, не имеет значения
Объяснение синтаксиса Mathematica для незнакомых людей:
Функции используют одиночные квадратные скобки для аргументов: func[1, 2, 3]
Индексация части выполняется с помощью двух квадратных скобок: list[[6]]
или с односимвольными двойными скобками Unicode: list〚6〛
Мое намерение состоит в том, чтобы идентифицировать соответствующую форму [[ ]]
в строке текста ASCII и заменить ее символами Unicode 〚 〛
Ответы
Ответ 1
Хорошо, вот еще один ответ, немного короче:
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[[email protected][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]]]]]]]]]]]
Результат не будет отображаться здесь правильно, но это строка Юникода с запрограммированными вами символами.
Ответ 2
Когда я написал свое первое решение, я не заметил, что вы просто хотели заменить [[
на 〚
в строке, а не на выражение. Вы всегда можете использовать HoldForm
или Defer
как
![enter image description here]()
но я думаю, что вы уже это знали, и вы хотите, чтобы выражение как строка, так же как и вход ([email protected]
по вышеописанному не работает)
Поскольку все ответы до сих пор сосредоточены на строковых манипуляциях, я возьму числовой подход вместо борьбы со строками, что для меня более естественно. Символьный код для [
равен 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 = [email protected];
openBracket = [email protected][charCode, [email protected]["["]];
closeBracket = [email protected]
Divisible[charCode, [email protected]["]"]];
doubleOpenBracket =
Append[[email protected][openBracket], 0] openBracket;
posClose = [email protected][Position[closeBracket, [email protected], {1}], 1];
doubleCloseBracket = ConstantArray[0, [email protected]];
openBracketDupe = openBracket + doubleOpenBracket;
Do[
tmp = [email protected]
[email protected][openBracketDupe[[1 ;; i]], [email protected], {1}];
doubleCloseBracket[[i - 1]] =
closeBracket[[i]] + openBracketDupe[[tmp]];
openBracketDupe[[tmp]] = 0;,
{i, posClose}];
changeOpen =
Cases[Range[[email protected]@charCode] doubleOpenBracket, [email protected]];
changeClosed =
Cases[Range[[email protected]@charCode] doubleCloseBracket,
[email protected]];
charCode[[changeOpen]] = ToCharacterCode["\[LeftDoubleBracket]"];
charCode[[changeClosed]] = ToCharacterCode["\[RightDoubleBracket]"];
[email protected]
Delete[[email protected],
List /@ (Riffle[changeOpen, changeClosed] + 1)]
Ответ 3
Вот моя попытка. Вставляемый код ASCII довольно нечитабелен из-за наличия специальных символов, поэтому я сначала предоставляю изображение того, как он выглядит в MMA.
В основном, это так: открывающие скобки всегда однозначно идентифицируются как одиночные или двойные. Проблема заключается в закрывающих скобках. В открываемых скобках всегда есть строка с символами-содержать-без-скобки + [или [[. Невозможно иметь либо [после [[или наоборот без других символов между ними (по крайней мере, не в коде без ошибок).
Итак, мы используем это как крючок и начать искать для некоторых пар совпадающих скобок, а именно те, которые не имеют каких-либо других скобок между ними. Так как мы знаем, типа, либо "[...]" или "[[...]]", мы можем заменить последние из них с символами двойного кронштейна и бывшим один с неиспользованными символами (я использую смайлики). Это делается для того, чтобы они больше не играли роли в следующей итерации процесса сопоставления шаблонов.
Мы повторяем, пока все скобки не обработаны, и, наконец, смайлики снова преобразуются в отдельные скобки.
Вы видите, что объяснение принимает символы нравов, чем код, -).
![введите описание изображения здесь]()
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]]
.
Ответ 4
Для этого вам нужен стек; там нет способа сделать это правильно, используя регулярные выражения.
Вам нужно распознать [[
, а также глубину этих скобок и сопоставить их с ]]
, который имеет ту же глубину. (Стеки делают это очень красиво. Пока они не переполняются: 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;
}
Обратите внимание, что я немного обманул, просто превратив их в пробелы, а затем удалив пробелы... это НЕ будет делать то, что я рекламировал, он уничтожит все пробелы в исходной строке. Вы можете просто занести в журнал все местоположения, а не изменять их в пространстве, а затем скопировать поверх исходной строки без зарегистрированных мест.
Также обратите внимание, что я не проверял состояние стека в конце. Предполагается, что он пуст, поскольку предполагается, что каждый символ [
во входной строке имеет свой уникальный символ ]
и наоборот. Если стек выкидывает "вы пытались вскрыть меня, когда я пуст", исключение в любой момент или не пусто в конце прогона, вы знаете, что ваша строка не была правильно сформирована.
Ответ 5
Другие ответы сделали это спорным, я думаю, но здесь больше Mathematica-идиоматическая версия первого решения yoda. Для достаточно длинной строки некоторые части могут быть немного более эффективными, кроме того.
str = "f @ g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]] // z";
charCode = [email protected];
openBracket = [email protected][charCode == 91];
closeBracket = [email protected][charCode == 93];
doubleOpenBracket = openBracket [email protected];
posClose = [email protected][closeBracket, -1, {1}];
doubleCloseBracket = 0*openBracket;
openBracketDupe = openBracket + doubleOpenBracket;
Do[
tmp = [email protected][[email protected]*[email protected][[1 ;; i]], 0];
doubleCloseBracket[[i - 1]] =
closeBracket[[i]] + openBracketDupe[[tmp]];
openBracketDupe[[tmp]] = 0, {i, posClose}]
counter = [email protected]@charCode;
changeOpen = DeleteCases[doubleOpenBracket*counter, 0];
changeClosed = DeleteCases[doubleCloseBracket*counter, 0];
charCode[[changeOpen]] = [email protected]["\[LeftDoubleBracket]"];
charCode[[changeClosed]] =
[email protected]["\[RightDoubleBracket]"];
[email protected][charCode, List /@ [email protected]{1 + changeOpen, 1 + changeClosed}]
Этот способ установки "tmp" может быть МЕНЬЕ эффективным, но я думаю, что это интересно.
Ответ 6
Edit
tl; dr версия:
Я нахожусь в пути для непреднамеренного решения базовой проблемы, но регулярные выражения не могут считать скобки, поэтому используйте реализацию стека.
Более длинная версия:
Мои уважаемые коллеги правильны, лучший способ подойти к этой проблеме - реализация стека. Регулярные выражения могут изменять [[
и ]]
в [
и ]
соответственно, если в строке есть такое же число [[
, что и число ]]
, однако, если вся точка упражнение - использовать текст в соответствии с []
, тогда регулярное выражение не способ. Регулярные выражения не могут считать скобки, логика вложенности слишком сложна для простого регулярного выражения для учета. Таким образом, в двух словах я считаю, что регулярные выражения могут использоваться для решения основного требования, которое заключалось в том, чтобы сменить соответствие [[]]
на соответствие []
, однако вы действительно должны использовать стек, потому что это позволяет легче манипулировать результирующей строкой.
И извините, я полностью пропустил тег математики! Я оставлю свой ответ здесь, хотя на всякий случай кто-то взволнован и прыгает с пистолета, как я.
Редактировать конец
Регулярное выражение, использующее неохотные кванторы, должно быть в состоянии постепенно определять, где токены [[
и ]]
находятся в строке, и убедитесь, что совпадения выполняются только в том случае, если число [[
равно числу ]]
.
Требуемое регулярное выражение будет по линии [[{1}?(?!]])*?]]{1}?
, которое на простом английском языке:
-
[[{1}?
, прогрессируйте по одному символу за раз от начала строки до тех пор, пока не встретится один экземпляр [[
-
(?!]])*?
, если существуют какие-либо символы, которые не соответствуют ]]
, проедьте их по одному за раз
-
]]{1}?
соответствуют закрывающей скобке
Чтобы сменить двойные квадратные скобки в однострочные скобки, определите группы внутри регулярного выражения, добавив скобки вокруг первой и третьей частиц:
([[{1}?)(?!]])*?(]]{1}?)
Это позволяет вам выбрать токены [[
и ]]
, а затем заменить их на [
или ]
.
Ответ 7
Я могу предложить тяжелый подход (не слишком элегантный). Ниже представлена моя реализация парсера 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]]]]]]]]]
ИЗМЕНИТЬ
Вот дополнение, необходимое для замены только символа:
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]]]"
Ответ 8
Отредактировано (там была ошибка)
Это слишком наивно?
doubleB[x_String] :=
StringReplace[
[email protected]@
ToExpression["Hold[" <> x <> "]"],
{"Hold[" -> "", RegularExpression["\]\)$"] -> "\)"}];
doubleB["f[g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]]]"]
[email protected]["f[g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]]]"]
- >
![enter image description here]()
Просто пытаюсь использовать собственный парсер Mma...
Ответ 9
Здесь еще один с сопоставлением с образцом, вероятно похожий на то, что делает Sjoerd C. de Vries, но он работает со структурой вложенного списка, которая создается сначала, процедурно:
FirstStringPosition[s_String, pat_] :=
Module[{f = StringPosition[s, pat, 1]},
If[[email protected] > 0, [email protected]@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,
[email protected]$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}];
[email protected]{result, rest}
, True
,(*done*)
[email protected]{result~Append~rest, ""}
]
]
];
(* TODO might want to get rid of empty strings "", { generated here:
[email protected]"f @ g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]] \
// z"
*)
TokenizeNestedBracePairs[s_String] :=
[email protected][s, ""]
и с этими определениями тогда
StringJoin @@
Flatten[[email protected]
"f @ g[h[[i[[j[2], k[[1, m[[1, n[2]]]]]]]]]] // z" //. {"[", {"", \
{"[", Longest[x___], "]"}, ""}, "]"} :> {"\[LeftDoubleBracket]", {x},
"\[RightDoubleBracket]"}]
дает
![введите описание изображения здесь]()