I use NSRegularExpression to find matched by a certain patter, which is visible in the snippet:
- (NSString *)functionPattern
{
return #"[A-Za-z]{1,}\\([A-Za-z0-9,\\(\\)]{1,}\\)";
}
- (void)test
{
NSString *formula = #"AVERAGE(G17,G18,AVERAGE(G20,G21,MIN(G30,G31)))";
NSError *error;
NSRegularExpression *functionRegex = [NSRegularExpression regularExpressionWithPattern:[self functionPattern]
options:NSRegularExpressionCaseInsensitive
error:&error];
if (error) {
NSLog(#"error");
return;
}
NSArray *matches = [functionRegex matchesInString:formula options:0 range:NSMakeRange(0, formula.length)];
for (NSUInteger i = 0; i < matches.count; i++) {
NSTextCheckingResult *result = matches[i];
NSString *match = [formula substringWithRange:result.range];
NSLog(#"%#", match);
}
}
My natural expectation was to get 3 matches: AVERAGE(...), AERAGE(...) and MIN(...). Surprisingly for me, i only get one match: AVERAGE(G17,G18,AVERAGE(G20,G21,MIN(G30,G31))).
If the formula is AVERAGE(G17,G18)+AVERAGE(G20,G21,MIN(G30,G31)), i'll get 2 matches: AVERAGE(G17,G18) and AVERAGE(G20,G21,MIN(G30,G31)). In other words, after a match is found, a search for the pattern is not performed in the range of the matched string.
Please advice how to overcome this, and find all possible matches. Am i missing something simple here?
What i'm doing is parsing and evaluating math expressions. All works fine, except for the cases of nested functions. If i know all possible function names in advance, can that be utilised somehow?
I'm hoping to fine more or less elegant approach; if i can i'd like to avoid things like "stripping off function name and parentheses'"
Help is much appreciated.
You cannot do what you want, at least the way you want to.
Regular expressions are technically a type 3 grammar and cannot describe recursive languages; your math expressions can contain other math expressions.
You could do something along the line of what you say you don't want to do. For example you could match an expression containing just a single pair of balanced parentheses, so in AVERAGE(G20,G21,MIN(G30,G31)) you could match MIN(G30,G31). If you then replaced the match by a marker and matched again you could match the next level, etc. But this not a good way to do it.
General math expressions can be described by a type 2 grammar, and can be easily parsed using a recursive descent parser. Such parsers are very easy to write. Essentially you write down the grammar you wish to parse and then write a function for each production. Google will get you started down this route.
If you don't want to write the parser yourself you can use a parser generator, in which case you still need to write the grammar, or search for one of the math expression libraries.
HTH
Related
Is a regex of the following form legit in Obj C?
"<(img|a|div).*?>.*?</$1>"
I know it's valid in JS with a \1 instead of $1, but I'm having little luck in Obj C.
NSRegularExpression uses ICU Regular Expressions which uses \n syntax for back references where n is the nth capture group.
<(img|a|div).*?>.*?</\\1>
Yes, I do believe you can work with capture groups. I had to work with them a bit a little while ago and I have an example in:
-(NSString *) extractMediaLink:(NSString *)link withRegex:(NSString *)regex{
NSString * utf8Link = [link stringByRemovingPercentEncoding];
NSError * regexError = nil;
NSRegularExpression * regexParser = [NSRegularExpression regularExpressionWithPattern:regex
options:NSRegularExpressionCaseInsensitive|NSRegularExpressionUseUnixLineSeparators
error:®exError];
NSTextCheckingResult * regexResults = [regexParser firstMatchInString:utf8Link
options:0
range:NSMakeRange(0, [utf8Link length])];
NSString * matchedResults = [utf8Link substringWithRange:[regexResults rangeAtIndex:1]]; // the second capture group will always have the ID
return matchedResults.length ? matchedResults : #"";
}
When you use an instance of NSRegularExpression to generate an NSTextCheckingResult, the NSTextCheckingResult has a property of numberOfRanges which is documented with:
A result must have at least one range, but may optionally have more (for example, to represent regular expression capture groups).
In my example above (Note: I happen to be parsing HTML, but using an addition pod that traverses HTML by XPath queries, TFHpple -- a lifesaver if you absolutely have to parse HTML), I use the -[NSRegularExpression firstMatchInString:options:range:] to check for the first instance of the tag that matches my regex pattern. From that NSTextCheckingResult I pull out the proper index of the capture group I'm interested in (in this case, [regexResults rangeAtIndex:1])
But, getting to this point was a huge pain in the ass. But to make sure you're getting the right expressions I would highly recommend using Regex101 with the Python setting, and then passing the refined regex into Patterns (Mac App Store)
If you want the full look, I have a fairly detailed project here, but keep in mind it's still a WIP.
I am using DDMathParser to parse the string expressions. I am working on one expression which is "(2+x)6+2x+2y=5y+2x", I am trying to parse it and evaluate it using DDMathParser. It can be considered as first degree function that I need to solve using Objective C.
Guys any ideas?
Help would be highly appreciated.
DDExpression * e = [DDExpression expressionFromString:expression error:error];
NSNumber *num = [e evaluateWithSubstitutions:substitutions evaluator:nil error:error];
I don't know much about DDMathParser (OK, I don't know anything about it), but taking a look at the documentation gives some clues about what is going wrong with your code.
Based on the documentation, I could find not example of where DDMathParser could handle symbolic expressions. All of the examples on the Usage page show numeric expressions.
For symbolic math, see Symbolic Math Library in C/C++/Obj-C .
In addition, it looks like you are trying to solve for two variables with a single equation, which is certainly not going to result in a numeric solution.
However, if you are just trying to substitute values in for x and y, you should use the $ sign before them as the documentation says:
If you don't know what the value of a particular term should be when
the string is constructed, that's ok; simply use a variable:
NSString *math = #"6 * $a";
So if you just want to substitute values in your expression, try using:
string expression = #"(2+$x)6+2*$x+2*$y";
Then fill in with values.
NSDictionary *s = #{#"x" : 42, #"y" : 43};
And evaluate.
DDMathEvaluator *eval = [DDMathEvaluator sharedMathEvaluator];
NSLog(#"%#", [eval evaluateString:expression withSubstitutions:s]);
Backgroud:
I have a response like so (please note the new lines):
HTTP/1.1 200 OK
CACHE-CONTROL: max-age = 180
EXT:
LOCATION: http://172.16.16.16:80/upnp.jsp
SERVER: Linux/2.6.32.24 UPNP/1.0 ZD3025/9.5.1.0
ST: upnp:rootdevice
USN: uuid:6e4bb543-fff6-4384-a4be-::upnp:rootdevice
I would like to match the Server line which is:
Linux/2.6.32.24 UPNP/1.0 ZD3025/9.5.1.0
Implementation:
I don't care about any character in the above given response EXCEPT for the characters Linux, UPNP and ZD (in that order).
So I am using .* to match any number of characters, numbers, special chars.
-(void) regexCompareForUPnP:(NSString *) string {
NSError * err = nil;
//Building expression
NSString *expression = #"^.*Linux.*UPNP.*ZD.*$";
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:expression options:0 error:&err];
NSUInteger numberOfMatches = [regex numberOfMatchesInString:string
options:0
range:NSMakeRange(0, [string length])];
NSLog(#"Matches found: %d", numberOfMatches);
}
So far, Matches found: 0.
Question:
So, in short does this:
NSString *expression = #"^.*Linux.*UPNP.*ZD.*$";
Match this:
Linux/2.6.32.24 UPNP/1.0 ZD3025/9.5.1.0
EDIT:
Thank you omz that worked perfectly.
Now I am trying to match the upnp.jsp (in the LOCATION line) alongwith what I was trying to match before. So I tried this:
NSString *expression = #"^.*upnp.jsp\n.*Linux.*UPNP.ZD.$";
That does not work. Appreciate your help..
I'm not familiar with this regex library. Based on what you're trying to match, however, it may be to do with newlines between OS's and individual programs.
Try this: *expression = #"upnp.jsp\r?\n[^\n]*?Linux[^\n]*?UPNP[^\n]*?ZD"
A few other changes made from what you're trying as well;
I've replaced .* with [^\n]*? for two reasons:
I'm not sure how this library deals with .*, but using that is typically inefficient in comparison to something more speicific such as looking for a non-greedy set of characters that aren't a new line. The difference between .* and .*? depends on the regex engine. In most regex engines I work with at least, .* is by default greedy; i.e. it will capture as much data as it can while still having the expression find a match. Using the non-greedy version, .*?, means that the regex will capture only as much as it needs to to satisfy the whole expression. This is particularly evident in an example I'll expand on at the end.
I removed the surrounding ^$ because when they are appended and prepended with .* they only work towards increasing processing time (unless you were using matching groups, which you aren't). If you were doing ^(.*)upnp or ^(.*upnp) and then using the contents of the matched group, there could be purpose in having the initial ^.*.
As for greedy/non-greedy, the difference becomes evident when trying to match data between a set of double quotes ". For the sake of simplicity, I won't deal with having escaped double quotes in the middle of a string you also want to capture.
Given the string: I said, "Hi." She responded with, "Hello!"
Using the greedy regex "(.*)", the matched group would be Hi." She responded with, "Hello!
Using the non-greedy regex "(.*?)", the matched group would be Hi.
I've got this regex
([0-9]+)\(([0-9]+),([0-9]+)\)
that I'm using to construct a NSRegularExpression with no options (0). That expression should match strings like
1(135,252)
and yield three matches: 1, 135, 252. Now, I've confirmed with debuggex.com that the expression is correct and does what I want. However, iOS refuses to acknowledge my efforts and the following code
NSString *nodeString = #"1(135,252)";
NSArray *r = [nodeRegex matchesInString:nodeString options:0 range:NSMakeRange(0, nodeString.length)];
NSLog(#"--- %#", nodeString);
for(NSTextCheckingResult *t in r) {
for(int i = 0; i < t.numberOfRanges; i++) {
NSLog(#"%#", [nodeString substringWithRange:[t rangeAtIndex:i]]);
}
}
insists in saying
--- 1(135,252)
135,252
13
5,252
5
252
which is clearly wrong.
Thoughts?
Your regex should look like this
[NSRegularExpression regularExpressionWithPattern:#"([0-9]+)\\(([0-9]+),([0-9]+)\\)"
options:0
error:NULL];
Note the double backslashes in the pattern. They are needed because the backslash is used to escape special characters (like for example quotes) in C and Objective-C is a superset of C.
If you are looking for a handy tool for working with regular expressions I can recommend Patterns. Its very cheap and can export straight to NSRegularExpressions.
Debuggex currently supports only raw regexes. This means that if you are using the regex in a string, you need to escape your backslashes, like this:
([0-9]+)\\(([0-9]+),([0-9]+)\\)
Also note that Debuggex does not (yet) support Objective C. This probably won't matter for simple regexes, but for more complicated ones, different engines do different things. This can result in some unexpected behavior.
I have a long string. I'd like to take this long string, search for any occurrences of words that appear between quotes (i.e., "string"), and insert a string before the word (i.e., "x"), and a string after the word (i.e., "y").
Any solutions would be most appreciated! Thanks!
I see that I could use the following to grab the text between the quotes:
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"([\"])
(?:\\\\\\1|.)*?\\1" options:0 error:&error];
NSRange range = [regex rangeOfFirstMatchInString:myString options:0 range:NSRangeMake(0,
[myString length]];
However, now I need to replace the text that is inside the quotations, inserting the html tags "bold" before and "/bold" after. Is there anyway for me to do this? Also, if there are multiple occurrences of quoted text in a given string, how would I use the above code to cycle through the string to get modify each piece of quoted text one-by-one?
I came across this post ([click here]]1 but I'm not quite sure how to modify the sample code to achieve the result I want. Any help would be great!
I will refer you to this page: Shortcuts in Objective-C to concatenate NSStrings
The page talks about adding strings together by using two NSMutable strings and adding one to both, which seems to be the preferable of the two it gives. Unfortunately, there is no operation to add two or more strings together (which really sucks).
Try this:
NSString *original=#"The quick 'brown fox' The quick 'brown fox' ";
NSString *target=[original stringByReplacingOccurrencesOfString:#"'brown fox'" withString:#"<b>brown fox</b>"];