This question already has answers here:
Comparing strings which are not exactly same
(3 answers)
Closed 9 years ago.
answered thanks to all that helped. I'm new to stack overflow as far as posting so if my edit is messy i apologize. the code below is what i ended up coming up with and think it may help others though i believe the person who answered last may also produce similar results.
I have been searching for a while now and I am sure I could break this down and get it done with a couple of sloppy if statements but I am looking for a common, efficient way.
I have a search with typical string comparison. For each string in an array using NSRange, but the results I get from that can be picky.
If I have a string for example Bat Man I could find it with searching Bat or Man but not once I add an m onto bat for Batm.
I guess what im looking for, is if the characters exist within the string and not exactly that string.
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
[roomsList removeAllObjects];
stringScore = 0;
// clear
if([searchText length] == 0){
searchingRooms = NO;
[roomsList addObjectsFromArray:[newDataManager shared].rooms];
[self.gsgSearchBar resignFirstResponder];
// search users
}else{
searchingRooms = YES;
for(QBChatRoom *room in [newDataManager shared].rooms){
stringScore = 0;
NSString* newRoomNameText = [room.name stringByReplacingOccurrencesOfString:#"_" withString:#""];
NSString* newSearchText = [searchText stringByReplacingOccurrencesOfString:#" " withString:#""];
for(int i = 0 ;i<[newSearchText length]; i++) {
char mySearchChar = [newSearchText characterAtIndex:i];
lastSearchString = [NSString stringWithFormat:#"%c",mySearchChar];
NSRange searchRange = [newRoomNameText rangeOfString:lastSearchString options:NSCaseInsensitiveSearch];
if (searchRange.location != NSNotFound){
stringScore = stringScore + 1;
if (stringScore == newSearchText.length) {
[roomsList addObject:room];
}
}}}}
[gsgTableView reloadData];
}
You can use predicates to solve this problem. They come in very handy while filtering arrays.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"Self contains[c] %#",self.searchString.text];
NSArray *filteredArray = [self.superHeroesArray filteredArrayUsingPredicate:predicate];
Adding [c] after contains makes sure that the search is case insensitive.
Related
i have implemented a search bar that searching trough an array of countries(presented in a picker view), the problem is that the user need to type the full country name that it will find it and i want him to be able to type even one letter and it will show the first country that starts with that letter and if types another than it sorts even further etc etc.
Anyone have any ideas??
for(int x = 0; x < countryTable.count; x++){
NSString *countryName = [[countryTable objectAtIndex:x]objectForKey:#"name"];
if([searchedStr isEqualToString:countryName.lowercaseString]){
[self.picker selectRow:i inComponent:0 animated:YES];
flag.image = [UIImage imageNamed:[[countryTable objectAtIndex:i]objectForKey:#"flag"]];
}
}
There's a method on NSArray called filteredArrayUsingPredicate: and a method on NSString called hasPrefix:. Together they do what you need...
NSString *userInput = //... user input as lowercase string. don't call this countryName, its confusing
NSPredicate *p = [NSPredicate predicateWithBlock:^BOOL(id element, NSDictionary *bind) {
NSString countryName = [[element objectForKey:#"name"] lowercaseString];
return [countryName hasPrefix:userInput];
}];
NSArray *filteredCountries = [countryTable filteredArrayUsingPredicate:p];
If you're on iOS 8 or OS X Yosemite, you can do:
NSString *country = countryName.lowercaseString; //"england"
NSString *needle = #"engl";
if (![country containsString:needle]) {
NSLog(#"Country string does not contain part (or whole) of searched country");
} else {
NSLog(#"Found the country!");
}
Else, if on versions below iOS 8:
NSString *country = countryName.lowercaseString; //"england"
NSString *needle = #"engl";
if ([country rangeOfString:needle].location == NSNotFound) {
NSLog(#"Country string does not contain part (or whole) of searched country");
} else {
NSLog(#"Found the country!");
}
Lastly, just iterate through all possible countries and apply this to them all. There might exist more robust solutions out there (like danh's solution with some smaller modifications), but this is by far the easiest to start with.
This question already has answers here:
Check that a input to UITextField is numeric only
(22 answers)
Closed 8 years ago.
I have a TextField and wanted to know if the user just pressed numbers
eg::
_tfNumber.text only has numbers?
is there any function on NSString for this?
This will let you know if all of the characters are numbers:
NSString *originalString = #"1234";
NSCharacterSet *numberSet = [NSCharacterSet decimalDigitCharacterSet];
NSString * trimmedString = [originalString stringByTrimmingCharactersInSet:numberSet];
if ((trimmedString.length == 0) && (originalString.length > 0)) {
NSLog(#"Original string was all numbers.");
}
Note that this ensures it won't give a false positive for the empty string, which technically also doesn't contain any non-numbers.
try this:
NSCharacterSet *_NumericOnly = [NSCharacterSet decimalDigitCharacterSet];
NSCharacterSet *myStringSet = [NSCharacterSet characterSetWithCharactersInString:mystring];
if ([_NumericOnly isSupersetOfSet: myStringSet]) {
NSLog(#"String has only numbers");
}
I got it from: http://i-software-developers.com/2013/07/01/check-if-nsstring-contains-only-numbers/
You can use this method in your UITextField's delegate method textField:shouldChangeCharactersInRange:replacementString: and do the verification while the user is typing.
No, but it should be easy to write:
- (BOOL)justContainsNumbers:(NSString *)str {
if ([str length] == 0)
return NO;
for (NSUInteger i = 0; i < [str length]; i++)
if (!isdigit([str characterAtIndex:i]))
return NO;
return YES;
}
Let's try regular Expression,
NSString * numberReg = #"[0-9]";
NSPredicate * numberCheck = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", numberReg];
if ([numberCheck evaluateWithObject:textField.text])
NSLog (#"Number");
No. NSString is not an NSNumber and any values you get from a UITextField will be an NSString. See THIS SO answer for converting that entered NSString value into an NSNumber.
This question already has answers here:
Split an NSString to access one particular piece
(7 answers)
Closed 9 years ago.
I want to filter string after character '='. For eg if 8+9=17 My output should be 17. I can filter character before '=' using NSScanner, how to do its reverse??? I need a efficient way to do this without using componentsSeparatedByString or creating an array
Everyone seems to like to use componentsSeparatedByString but it is quite inefficient when you just want one part of a string.
Try this:
NSString *str = #"8+9=17";
NSRange equalRange = [str rangeOfString:#"=" options:NSBackwardsSearch];
if (equalRange.location != NSNotFound) {
NSString *result = [str substringFromIndex:equalRange.location + equalRange.length];
NSLog(#"The result = %#", result);
} else {
NSLog(#"There is no = in the string");
}
Update:
Note - for this specific example, the difference in efficiencies is negligible if it is only being done once.
But in general, using componentsSeparatedByString: is going to scan the entire string looking for every occurrence of the delimiter. It then creates an array with all of the substrings. This is great when you need most of those substrings.
When you only need one part of a larger string, this is very wasteful. There is no need to scan the entire string. There is no need to create an array. There is no need to get all of the other substrings.
NSArray * array = [string componentsSeparatedByString:#"="];
if (array)
{
NSString * desiredString = (NSString *)[array lastObject]; //or whichever the index
}
else
{
NSLog(#""); //report error - = not found. Of array could somehow be not created.
}
NOTE:
Though this is very popular splitting solution, it is only worth trying whenever every substring separated by separator string is required. rmaddy's answer suggest better mechanism whenever the need is only to get small part of the string. Use that instead of this approach whenever only small part of the string is required.
Try to use this one
NSArray *arr = [string componentsSeparatedByString:#"="];
if (arr.count > 0)
{
NSString * firstString = [arr objectAtIndex:0];
NSString * secondString = [arr objectAtIndex:1];
NSLog(#"First String %#",firstString);
NSLog(#"Second String %#",secondString);
}
Output
First String 8+9
Second String 17
Use this:
NSString *a =#"4+6=10";
NSLog(#"%#",[a componentsSeparatedByString:#"="])
;
Log: Practice[7582:11303] (
"4+6",
10
)
Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 10 years ago.
Improve this question
I'm building a scrabble game, and having some problem with the word dictionary. It contains ~700,000 words, and about 18 MB big.
Right now, I'm loading the whole dict into an array, which takes 12 seconds on an iPhone 4.
wordList = [NSMutableArray arrayWithContentsOfFile: [[self applicationDocumentsDirectory] stringByAppendingString:#"/wordlist.plist"]];
I have two questions:
Is there a better way to load the wordlist faster and/or reduce memory?
It takes about 12 seconds to get all possible words from a set of letters. Is it possible to make it quicker? Here's the code:
-(NSMutableArray *)getValidWords:(NSString *)letters{
NSMutableArray *list = [[NSMutableArray alloc] init];
for (int i = 0, c = [wordList count]; i < c; i++){
if ([self isWordValid: [wordList objectAtIndex: i] forLetters:letters]){
[list addObject:[wordList objectAtIndex: i]];
}
}
return list;
}
- (BOOL)isWordValid:(NSString *)word forLetters:(NSString *)ltrs{
int i, z;
NSRange range;
BOOL found;
static NSMutableString *letters = nil;
if ([word length] < 2) return NO;
if(letters == nil) {
letters = [[NSMutableString alloc] initWithString:ltrs];
}
else {
[letters setString: ltrs];
}
found = NO;
range.length = 1;
for(i = 0; i < [word length]; i++){
for(z = 0; z < [letters length]; z++){
if([word characterAtIndex:i] == [letters characterAtIndex:z]){
range.location = z;
[letters deleteCharactersInRange: range];
found = YES;
break;
}
}
if (found == NO){
return NO;
}
found = NO;
}
return YES;
}
You need to change few things to speed up.
Use fast enumeration in place of old C-style loop.
Avoid a lot of method calls.
Use NSPredicate and/or Regex if possible.
As whenever you write [letters length] a method is called, instead of finding it millions of time (this is inside 3rd level of nested loop), store it in a variable and use it.
Fast enumeration : Instead of for(int i=0; i<[someArrays count];i++) use for(id object in someArrays).
Use this
[NSThread detachNewThreadSelector:#selector(fetchWords:) toTarget:self withObject:data];
Do not do it in main thread
use this code modify it if u need to search words
NSMutableArray *subpredicates = [NSMutableArray array];
for(NSString *term in arryOfWordsToBeSearched) {
NSPredicate *p = [NSPredicate predicateWithFormat:#"self contains[cd] %#",term];
[subpredicates addObject:p];
}
NSPredicate *filter = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
result = (NSMutableArray*)[arryOfDummyData filteredArrayUsingPredicate: filter];
//result is a array
Just for starters, create a NSCharacterSet from your letters and call this function before calling the lengthy function. This is a faster check to reduce the possibilities and it should improve your computation time.
NSCharacterSet* lettersSet = [NSCharacterSet characterSetWithCharactersInString:letters];
- (BOOL)isWordValid:(NSString*)word forLettersSet:(NSCharacterSet*)lettersSet {
if ([word length] < 2) return NO;
NSCharacterSet* wordLetters = [NSCharacterSet characterSetWithCharactersInString:word];
return [lettersSet isSupersetOfSet:wordLetters];
}
Ideally, your word database should have precomputed the letter count for each word (e.g. every = {e=2, r=1, v=1, y=1} and your should work only with these structures. Note that the order of letters is not significant - using this fact can greatly improve the performance of your algorithm.
You can also try to create a Core Data database - every word will be one record with a number field for every letter. Then you can create a request which will return you the available words VERY fast.
(Of course, the database will probably take bigger amount of space).
EDIT: Now I have found out NSCountedSet class, so let's try to use it:
-(NSCountedSet*)lettersSetFromString:(NSString*)string {
NSCountedSet* letters = [NSCountedSet set];
[string enumerateSubstringsInRange:NSMakeRange(0, self.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[letters addObject:substring];
}];
}
-(NSMutableArray *)validWordsForLetters:(NSString*)letters {
NSCountedSet* lettersSet = [self lettersSetFromString:letters];
NSMutableArray* results = [NSMutableArray array];
for (NSString* word in wordList) {
if ([word length] < 2) {
continue;
}
NSCountedSet* wordLettersSet = [self lettersSetFromString:word];
if ([wordLettersSet isSubsetOfSet:lettersSet]) {
[results addObject:word];
}
}
return results;
}
Generating the counted sets for every word beforehand will help the perfomance a lot. Using a Core Data database will still be faster and better for the OS memory.
I am searching my products like the below. It works fine however when searching between 2 words the results disappear then reappear once the user has entered the first character of the next word. i.e if I search "td pro" or "pro td" the results are there. If I search like this td(i have a result) td(space) I have NO result td p(I have a result) just to add I need to separate the words by space in componentsSeperatedByString because the user may not search for a product in any particular order. Unless there is a better method?
Here is my code:
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
[self.filteredListContent removeAllObjects]; // First clear the filtered array.
for (XMLStringFile *new in rssOutputData_MutableArray)
{
//Product scope
if ([scope isEqualToString:#"Product"])
{
// Split into search text into separate "words"
NSArray * searchComponents = [searchText componentsSeparatedByString: #" "];
;
BOOL foundSearchText = YES;
// Check each word to see if it was found
for (NSString * searchComponent in searchComponents) {
NSRange result = [new.xmlItem rangeOfString:searchComponent options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)];
NSRange descriptionRange = [new.xmlDescription rangeOfString:searchComponent options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)];
foundSearchText &= (result.location != NSNotFound|| descriptionRange.location != NSNotFound);
}
// If all search words found, add to the array
if (foundSearchText)
{
[self.filteredListContent addObject: new];
}
}
}
}
Many thanks
I fixed it with the help of another stack question that someone helped me with.
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
[self.filteredListContent removeAllObjects]; // First clear the filtered array.
for (XMLStringFile *new in rssOutputData_MutableArray)
{
//Product scope
if ([scope isEqualToString:#"Product"])
{
// Split into search text into separate "words"
NSArray * searchComponents = [searchText componentsSeparatedByString: #" "];
//Before searching trim off the whitespace
searchText = [searchText stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
BOOL foundSearchText = YES;
// Check each word to see if it was found
for (NSString * searchComponent in searchComponents) {
NSRange result = [new.xmlItem rangeOfString:searchComponent options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)];
NSRange descriptionRange = [new.xmlDescription rangeOfString:searchComponent options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)];
foundSearchText &= (result.location != NSNotFound|| descriptionRange.location != NSNotFound);
}
// If all search words found, add to the array
if (foundSearchText)
{
[self.filteredListContent addObject: new];
}
}
}
}
The winning line is:
//Before searching trim off the whitespace
searchText = [searchText stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];