Count the amount of times '$' shows up in a string (Objective C) - objective-c

I was wondering if there was an easy method to find the amount of times a character such as '$' shows up in a string in the language objective-c.
The real world example I am using is a string that would look like:
542$764$231$DataEntry
What I need to do is first:
1) count the amount of times the '$' shows up to know what tier the DataEntry is in my database (my database structure is one I made up)
2) then I need to get all of the numbers, as they are index numbers. The numbers need to be stored in a NSArray. And I will loop through them all getting the different indexes. I'm not going to explain how my database structure works as that isn't relevant.
Basically from that NSString, I need, the amount of times '$' shows up. And all of the numbers in between the dollar signs. This would be a breeze to do in PHP, but I was curious to see how I could go about this in Objective-C.
Thanks,
Michael

[[#"542$764$231$DataEntry" componentsSeparatedByString:#"$"] count]-1

The componentsSeparatedByString suggested by #Parag Bafna and #J Shapiro or NSRegularExpression e.g.:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
#autoreleasepool {
NSError *error = NULL;
NSString *searchText = #"542$764$231$DataEntry";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"(\\d{3})\\$" options:NSRegularExpressionCaseInsensitive error:&error];
NSUInteger numberOfMatches = [regex numberOfMatchesInString:searchText options:0 range:NSMakeRange(0, [searchText length]) ];
printf("match count = %ld\n",numberOfMatches);
[regex enumerateMatchesInString:searchText
options:0
range:NSMakeRange(0,[searchText length])
usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
NSRange range = [match rangeAtIndex:1];
printf("match = %s\n",[[searchText substringWithRange:range] UTF8String]);
}];
}
}
The componentsSeparatedByString is probably the preferred approach and much more performant where the pattern has simple repeating delimiters; but I included this approach for completeness sake.

Try this code:
NSMutableArray* substrings=[NSMutableArray new];
// This will contain all the substrings
NSMutableArray* numbers=[NSMutableArray new];
// This will contain all the numbers
NSNumberFormatter* formatter=[NSNumberFormatter new];
// The formatter will scan all the strings and estabilish if they're
// valid numbers, if so it will produce a NSNumber object
[formatter setNumberStyle: NSNumberFormatterDecimalStyle];
NSString* entry= #"542$764$231$DataEntry";
NSUInteger count=0,last=0;
// count will contain the number of '$' characters found
NSRange range=NSMakeRange(0, entry.length);
// range is the range where to check
do
{
range= [entry rangeOfString: #"$" options: NSLiteralSearch range: range];
// Check for a substring
if(range.location!=NSNotFound)
{
// If there's not a further substring range.location will be NSNotFound
NSRange substringRange= NSMakeRange(last, range.location-last);
// Get the range of the substring
NSString* substring=[entry substringWithRange: substringRange];
[substrings addObject: substring];
// Get the substring and add it to the substrings mutable array
last=range.location+1;
range= NSMakeRange(range.location+range.length, entry.length-range.length-range.location);
// Calculate the new range where to check for the next substring
count++;
// Increase the count
}
}while( range.location!=NSNotFound);
// Now count contains the number of '$' characters found, and substrings
// contains all the substrings separated by '$'
for(NSString* substring in substrings)
{
// Check all the substrings found
NSNumber* number;
if([formatter getObjectValue: &number forString: substring range: nil error: nil])
{
// If the substring is a valid number, the method returns YES and we go
// inside this scope, so we can add the number to the numbers array
[numbers addObject: number];
}
}
// Now numbers contains all the numbers found

Related

How to split NSString where two or more whitespace characters are found?

given string input:
#"bonus pay savings 2.69 F";
#"brick and mortar 0.15-B";
desired output string:
[#"bonus pay savings", #"2.69 F"];
[#"brick and mortar", #"0.15-B"];
I tried this approach:
NSString * str = #"bonus pay savings 2.69 F";
NSArray * arr = [str componentsSeparatedByString:#" "];
NSLog(#"Array values are : %#",arr);
But the drawback of my approach is I'm using 3 spaces as a delimiter whereas the number of spaces can vary. How can this be accomplished? Thank you.
You can use NSRegularExpression to split your string. Let's make a category on NSString:
NSString+asdiu.h
#interface NSString (asdiu)
- (NSArray<NSString *> *)componentsSeparatedByRegularExpressionPattern:(NSString *)pattern error:(NSError **)errorOut;
#end
NSString+asdiu.m
#implementation NSString (asdiu)
- (NSArray<NSString *> *)componentsSeparatedByRegularExpressionPattern:(NSString *)pattern error:(NSError **)errorOut {
NSRegularExpression *rex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:errorOut];
if (rex == nil) { return nil; }
NSMutableArray<NSString *> *components = [NSMutableArray new];
__block NSUInteger start = 0;
[rex enumerateMatchesInString:self options:0 range:NSMakeRange(0, self.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
NSRange separatorRange = result.range;
NSRange componentRange = NSMakeRange(start, separatorRange.location - start);
[components addObject:[self substringWithRange:componentRange]];
start = NSMaxRange(separatorRange);
}];
[components addObject:[self substringFromIndex:start]];
return components;
}
#end
You can use it like this:
NSArray<NSString *> *inputs = #[#"bonus pay savings 2.69 F", #"brick and mortar 0.15-B"];
for (NSString *input in inputs) {
NSArray<NSString *> *fields = [input componentsSeparatedByRegularExpressionPattern:#"\\s\\s+" error:nil];
NSLog(#"fields: %#", fields);
}
Output:
2018-06-15 13:38:13.152725-0500 test[23423:1386429] fields: (
"bonus pay savings",
"2.69 F"
)
2018-06-15 13:38:13.153140-0500 test[23423:1386429] fields: (
"brick and mortar",
"0.15-B"
)
A simple solution with Regular Expression.
It replaces all occurrences of 2 or more ({2,}) whitespace characters (\\s) with a random UUID string. Then it splits the string by that UUID string.
NSString *separator = [NSUUID UUID].UUIDString;
NSString *string = #"bonus pay savings 2.69 F";
NSString *collapsedString = [string stringByReplacingOccurrencesOfString:#"\\s{2,}"
withString:separator
options:NSRegularExpressionSearch
range:NSMakeRange(0, [string length])];
NSArray *output = [collapsedString componentsSeparatedByString:separator];
NSLog(#"%#", output);
If you can assume that you only have 2 fields in the input string, I would use a limited split method like this one that always returns an array of 2 items, and then "trim" spaces off the second item using stringByTrimmingCharactersInSet.
#vadian and #robmayoff have both provided good solutions based on regular expressions (REs), in both cases the REs are used to match the gaps to find where to break your string. For comparison approaching the problem the other way by using a RE to match the parts you are interested in is also possible. The RE:
\S+(\h\S+)*
will match the text you are interested in, made up as as follows:
\S - match any non-space character, \S excludes both horizontal
(e.g. spaces, tabs) and vertical space (e.g. newlines)
\S+ - one or more non-space characters, i.e. a "word" of sorts
\h - a single horizontal space character (if you wish matches to
span lines use \s - any horizontal *or* vertical space)
\h\S+ - a space followed by a word
(\h\S+)* - zero or more space separated words
\S+(\h\S+)* - a word follow by zero or more words
With this simple regular expression you can use matchesInString:options:range: to obtain an array of NSTextCheckingResult objects, one for each match in your input; or you can use enumerateMatchesInString:options:range:usingBlock: to have a block called with each match.
As an example here is a solution following #robmayoff's approach:
#interface NSString (componentsMatchingRegularExpression)
- (NSArray<NSString *>*) componentsMatchingRegularExpression:(NSString *)pattern;
#end
#implementation NSString (componentsMatchingRegularExpression)
- (NSArray<NSString *>*) componentsMatchingRegularExpression:(NSString *)pattern
{
NSError *errorReturn;
NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&errorReturn];
if (!regularExpression)
return nil;
NSMutableArray *matches = NSMutableArray.new;
[regularExpression enumerateMatchesInString:self
options:0
range:NSMakeRange(0, self.length)
usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop)
{
[matches addObject:[self substringWithRange:result.range]];
}
];
return matches.copy; // non-mutable copy
}
#end
Whether matching what you wish to keep or remove is better is subjective, take your pick.
Regular Expressions are fine for this, and the solutions given using them are perfectly fine, but just for completion you can also do this using NSScanner, which will almost always have better performance than regexes, and is pretty handy to get used to using if you need to do more complicated text parsing.
NSString *str = #"bonus pay savings 2.69 F";
NSScanner *scanner = [NSScanner scannerWithString:str];
scanner.charactersToBeSkipped = nil; // default is to ignore whitespace
while (!scanner.isAtEnd) {
NSString *name;
NSString *value;
// scan up to two spaces, this would be the name
[scanner scanUpToString:#" " intoString:&name];
// scan the two spaces and any extra whitespace
[scanner scanCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:nil];
// scan to the end of the line, this is the value
[scanner scanUpToString:#"\n" intoString:&value];
}

Replace specific words in NSString

what is the best way to get and replace specific words in string ?
for example I have
NSString * currentString = #"one {two}, thing {thing} good";
now I need find each {currentWord}
and apply function for it
[self replaceWord:currentWord]
then replace currentWord with result from function
-(NSString*)replaceWord:(NSString*)currentWord;
The following example shows how you can use NSRegularExpression and enumerateMatchesInString to accomplish the task. I have just used uppercaseString as function that replaces a word, but you can use your replaceWord method as well:
EDIT: The first version of my answer did not work correctly if the replaced words are
shorter or longer as the original words (thanks to Fabian Kreiser for noting that!) .
Now it should work correctly in all cases.
NSString *currentString = #"one {two}, thing {thing} good";
// Regular expression to find "word characters" enclosed by {...}:
NSRegularExpression *regex;
regex = [NSRegularExpression regularExpressionWithPattern:#"\\{(\\w+)\\}"
options:0
error:NULL];
NSMutableString *modifiedString = [currentString mutableCopy];
__block int offset = 0;
[regex enumerateMatchesInString:currentString
options:0
range:NSMakeRange(0, [currentString length])
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
// range = location of the regex capture group "(\\w+)" in currentString:
NSRange range = [result rangeAtIndex:1];
// Adjust location for modifiedString:
range.location += offset;
// Get old word:
NSString *oldWord = [modifiedString substringWithRange:range];
// Compute new word:
// In your case, that would be
// NSString *newWord = [self replaceWord:oldWord];
NSString *newWord = [NSString stringWithFormat:#"--- %# ---", [oldWord uppercaseString] ];
// Replace new word in modifiedString:
[modifiedString replaceCharactersInRange:range withString:newWord];
// Update offset:
offset += [newWord length] - [oldWord length];
}
];
NSLog(#"%#", modifiedString);
Output:
one {--- TWO ---}, thing {--- THING ---} good

Counting occurrences of capital letters and numbers in an NSString

In PHP I am using the following code...
$passwordCapitalLettersLength = strlen(preg_replace("![^A-Z]+!", "", $password));
$passwordNumbersLength = strlen(preg_replace("/[0-9]/", "", $password));
...to count how many times capital letters and numbers appear in the password.
What is the equivalent of this in Objective C?
You can use the NSCharacterSet:
NSString *password = #"aas2dASDasd1asdASDasdas32D";
int occurrenceCapital = 0;
int occurenceNumbers = 0;
for (int i = 0; i < [password length]; i++) {
if([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:[password characterAtIndex:i]])
occurenceCapital++;
if([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[password characterAtIndex:i]])
occurenceNumbers++;
}
This can be done fairly concisely using the abilities of NSString and NSCharacterSet, instead of the need to iterate manually.
A decrement of 1 is required as componentsSeparatedByCharactersInSet: will always return at least one element, and that one element won't count your separations.
NSString* password = #"dhdjGHSJD7d56dhHDHa7d5bw3/%£hDJ7hdjs464525";
NSArray* capitalArr = [password componentsSeparatedByCharactersInSet:[NSCharacterSet uppercaseLetterCharacterSet]];
NSLog(#"Number of capital letters: %ld", (unsigned long) capitalArr.count - 1);
NSArray* numericArr = [password componentsSeparatedByCharactersInSet:[NSCharacterSet decimalDigitCharacterSet]];
NSLog(#"Number of numeric digits: %ld", (unsigned long) numericArr.count - 1);
Original answer: Whilst the code you've provided won't cover all bases, if you need to keep using those regular expressions for safety/risk reasons, you can do so below.
You can use RegEx in Objective-C. Saves manually iterating through the String, and keeps the code concise. It also means because you aren't iterating manually, you could potentially get a performance boost, as you can let the compiler/framework writer optimize it.
// Testing string
NSString* password = #"dhdjGHSJD7d56dhHDHa7d5bw3/%£hDJ7hdjs464525";
NSRegularExpression* capitalRegex = [NSRegularExpression regularExpressionWithPattern:#"[A-Z]"
options:0
error:nil];
NSRegularExpression* numbersRegex = [NSRegularExpression regularExpressionWithPattern:#"[0-9]"
options:0
error:nil];
NSLog(#"Number of capital letters: %ld", (unsigned long)[capitalRegex matchesInString:password
options:0
range:NSMakeRange(0, password.length)].count);
NSLog(#"Number of numeric digits: %ld", (unsigned long)[numbersRegex matchesInString:password
options:0
range:NSMakeRange(0, password.length)].count);

Objective-C NSString character substitution

I have a NSString category I am working on to perform character substitution similar to PHP's strtr. This method takes a string and replaces every occurrence of each character in fromString and replaces it with the character in toString with the same index. I have a working method but it is not very performant and would like to make it quicker and able to handle megabytes of data.
Edit (for clarity):
stringByReplacingOccurrencesOfString:withString:options:range: will not work. I have to take a string like "ABC" and after replacing "A" with "B" and "B" with "A" end up with "BAC". Successive invocations of stringByReplacingOccurrencesOfString:withString:options:range: would make a string like "AAC" which would be incorrect.
Suggestions would be great, sample code would be even better!
Code:
- (NSString *)stringBySubstitutingCharactersFromString:(NSString *)fromString
toString:(NSString *)toString;
{
NSMutableString *substitutedString = [self mutableCopy];
NSString *aCharacterString;
NSUInteger characterIndex
, stringLength = substitutedString.length;
for (NSUInteger i = 0; i < stringLength; ++i) {
aCharacterString = [NSString stringWithFormat: #"%C", [substitutedString characterAtIndex:i]];
characterIndex = [fromString rangeOfString:aCharacterString].location;
if (characterIndex == NSNotFound) continue;
[substitutedString replaceCharactersInRange:NSMakeRange(i, 1)
withString:[NSString stringWithFormat:#"%C", [toString characterAtIndex:characterIndex]]];
}
return substitutedString;
}
Also this code is executed after every change to text in a text view. It is passed the entire string every time. I know that there is a better way to do it, but I do not know how. Any suggestions for this would be most certainly appreciated!
You can make that kind of string substitution with NSRegularExpression either modifying an mutable string or creating a new immutable string. It will work with any two strings to substitute (even if they are more than one symbol) but you will need to escape any character that means something in a regular expression (like \ [ ( . * ? + etc).
The pattern finds either of the two substrings with the optional "anything" between and than replaces them with the two substrings with each other preserving the optional string between them.
// These string can be of any length
NSString *firstString = #"Axa";
NSString *secondString = #"By";
// Escaping of characters used in regular expressions has NOT been done here
NSString *pattern = [NSString stringWithFormat:#"(%#|%#)(.*?)(%#|%#)", firstString, secondString, firstString, secondString];
NSString *string = #"AxaByCAxaCBy";
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
options:NSRegularExpressionCaseInsensitive
error:&error];
if (error) {
// Insert error handling here...
}
NSString *modifiedString = [regex stringByReplacingMatchesInString:string
options:0
range:NSMakeRange(0, [string length])
withTemplate:#"$3$2$1"];
NSLog(#"Before:\t%#", string); // AxaByCAxaCBy
NSLog(#"After: \t%#", modifiedString); // ByAxaCByCAxa

Is there an Objective-c regex replace with callback/C# MatchEvaluator equivalent?

I have a C# project I'm intending to port to Objective-C. From what I understand about Obj-C, it looks like there's a confusing variety of Regex options but I can't see anything about a way of doing a replace with callback.
I'm looking for something that is the equivalent of the C# MatchEvaluator delegate or PHP's preg_replace_callback. An example of what I want to do in C# is -
// change input so each word is followed a number showing how many letters it has
string inputString = "Hello, how are you today ?";
Regex theRegex = new Regex(#"\w+");
string outputString = theRegex.Replace(inputString, delegate (Match thisMatch){
return thisMatch.Value + thisMatch.Value.Length;
});
// outputString is now 'Hello5, how3 are3 you3 today5 ?'
How could I do this in Objective-C ? In my actual situation the Regex has both lookahead and lookbehind assertions in it though, so any alternative involving finding the strings in advance and then doing a series of straight string replaces won't work unfortunately.
Foundation has a NSRegularExpression class (iOS4 and later), which may be useful to you. From the docs:
The fundamental matching method for
NSRegularExpression is a Block
iterator method that allows clients to
supply a Block object which will be
invoked each time the regular
expression matches a portion of the
target string. There are additional
convenience methods for returning all
the matches as an array, the total
number of matches, the first match,
and the range of the first match.
For example:
NSString *input = #"Hello, how are you today?";
// make a copy of the input string. we are going to edit this one as we iterate
NSMutableString *output = [NSMutableString stringWithString:input];
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:#"\\w+"
options:NSRegularExpressionCaseInsensitive
error:&error];
// keep track of how many additional characters we've added (1 per iteration)
__block NSUInteger count = 0;
[regex enumerateMatchesInString:input
options:0
range:NSMakeRange(0, [input length])
usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
// Note that Blocks in Objective C are basically closures
// so they will keep a constant copy of variables that were in scope
// when the block was declared
// unless you prefix the variable with the __block qualifier
// match.range is a C struct
// match.range.location is the character offset of the match
// match.range.length is the length of the match
NSString *matchedword = [input substringWithRange:match.range];
// the matched word with the length appended
NSString *new = [matchedword stringByAppendingFormat:#"%d", [matchedword length]];
// every iteration, the output string is getting longer
// so we need to adjust the range that we are editing
NSRange newrange = NSMakeRange(match.range.location+count, match.range.length);
[output replaceCharactersInRange:newrange withString:new];
count++;
}];
NSLog(#"%#", output); //output: Hello5, how3 are3 you3 today5?
I modified atshum's code to make it a bit more flexible:
__block int prevEndPosition = 0;
[regex enumerateMatchesInString:text
options:0
range:NSMakeRange(0, [text length])
usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop)
{
NSRange r = {.location = prevEndPosition, .length = match.range.location - prevEndPosition};
// Copy everything without modification between previous replacement and new one
[output appendString:[text substringWithRange:r]];
// Append string to be replaced
[output appendString:#"REPLACED"];
prevEndPosition = match.range.location + match.range.length;
}];
// Finalize string end
NSRange r = {.location = prevEndPosition, .length = [text length] - prevEndPosition};
[output appendString:[text substringWithRange:r]];
Seems to work for now (probably needs a bit more testing)