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"
for the purpose of identifying and replacing [[ Part ]]
brackets with the single character forms?
I want to get:
With everything else intact, such as the prefix @
and postfix //
forms intact
An explanation of Mathematica syntax for those unfamiliar:
Functions use single square brackets for arguments: func[1, 2, 3]
Part indexing is done with double square brackets: list[[6]]
or with single-character Unicode double brackets: list〚6〛
My intent is to identify the matching [[ ]]
form in a string of ASCII text, and replace it with the Unicode characters 〚 〛
I can offer a heavy approach (not too elegant). Below is my implementation of the bare-bones Mathematica parser (it will only work for strings containing Fullform of the code, with the possible exception for double brackets - which I will use here), based on rather general functionality of breadth-first parser that I developed mostly to implement an HTML parser:
(note the use of
MapAll
in the last function). Now, here is how you can use it :)Now you can use
parseToCode
:EDIT
Here is an addition needed to make only the character-replacement, as requested:
Here is how we can use it:
Edited (there was an error there)
Is this too naïve?
->
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:
This would be turned into
Here is some java-like pseudocode:
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.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.
This way of setting "tmp" may be LESS efficient, but I think it's interesting.
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 useHoldForm
orDefer
asbut 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 followinggives 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 asThe 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
, sayi
, go to the corresponding position inopenBracket
and find the first non-zero position to the left of it (sayj
).doubleCloseBrackets[[i-1]]=closeBracket[[i]]+openBracket[[j]]+doubleOpenBrackets[[j]]
.doubleCloseBrackets
is the counterpart ofdoubleOpenBrackets
and is non-zero at the position of the first ofPart
'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 incharCode
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
Edit
tl;dr version:
I'm on track for inadvertently solving the base problem, but regular expressions can't count brackets so use a stack implementation.
Longer version:
My esteemed colleagues are correct, the best way to approach this problem is a stack implementation. Regular expressions may be able to change
[[
and]]
into[
and]
respectively if the same number of[[
exist within the string as the number of]]
, however if the whole point of the exercise is to use the text within matching[]
then regex isn't the way to go. Regular expressions cannot count brackets, nesting logic is just too complex for a simple regex to account for. So in a nutshell I believe that regular expressions can be used to address the basic requirement, which was to change matching[[]]
into matching[]
, however you should really be using a stack because it allows easier manipulation of the resultant string.And sorry, I completely missed the mathematica tag! I'll leave my answer in here though just in case someone gets excited and jumps the gun like I did.
End Edit
A regular expression utilising reluctant quantifiers should be able to progressively determine where
[[
and]]
tokens are in a String, and ensure that matches are only made if the number of[[
equals the number of]]
.The required regex would be along the lines of
[[{1}?(?!]])*?]]{1}?
, which in plain English is:[[{1}?
, progress one character at a time from the start of the string until one instance of[[
is encountered(?!]])*?
if any characters exist that don't match]]
, progress through them one at a time]]{1}?
match the closing bracketTo change the double-square-brackets into single-square-brackets, identify groups within the regex by adding brackets around the first and third particles:
This allows you to select the
[[
and]]
tokens, and then replace them with[
or]
.