So, I have the following loop, and it's a bit of a bottle neck - is there any way I can speed this up?
NSArray *array = [an array of NSDictionaries];
NSArray *otherArray = [an array of NSStrings];
NSMutableArray *newArray = [NSMutableArray new] autorelease];
for (NSDictionary *dict in array)
{
NSString *name = [[NSString alloc] initWithString:[dict objectForKey#"name"]];
for (NSString *n in otherArray)
{
if ([name hasPrefix:n])
[newArray addObject:dict];
}
[name release];
}
You can define a NSPredicate and use
- (NSArray *)filteredArrayUsingPredicate:(NSPredicate *)predicate
on your array and don't loop on your own. You need to profile if it is actually faster.
About all I can see for substantial gains is that you could make up a boolean C-style array indexed by the first letter of your prefix and pre-load it with YES/NO based on whether that character is a "hit". (Probably you'd want a 256-element array indexed by the low byte of the 2-byte character.) Inside the outer loop take the first character of name, index this array, and if it's NO then skip the rest of the outer loop body. Only works if the prefix array is fairly small, though (so less than about half of the boolean array elements are YES).
You can probably make a small improvement by using a C-style array rather than the prefix NSArray, but at the expense of creating that C-style array up front.
There are other techniques that would involve hashing, but the setup expense and complexity is probably not worth it.
You can use a predicate to filter the array & remove the loop entirely.
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(%#, $str, name BEGINSWITH[cd] $str).#count != 0", otherArray];
NSArray * newArray = [array filteredArrayUsingPredicate:predicate];
Assuming name is the key.
Related
I have a fun challenging problem. So I have a mutable array that contains all of my items. I have a textfield that **might have one or two of these items if the person types them in. **
items= [[NSArray alloc]initWithObjects:#"apple", #"orange", #"pear", nil];
items2= [[NSArray alloc]initWithObjects:#"cheese", #"milk", #"eggs", nil];
Allitems= [NSMutableArray array];
[Allitems addObjectsFromArray:items];
[Allitems addObjectsFromArray:items2];
NSArray*WORDS =[Textfield componentsSeparatedByString:#" "];
I am trying to detect what specific words from **Allitems are in the textfield. (If the textfield contains any string from ALLitems, how can I find what specific string?**
for (int i = 0; i < [Allitems count]; i++)
{
NSString *grabstring;
grabstring=[Allitems objectAtIndex:i];
if (textfield isEqualto:grabstring){
?????
pull that specific string from allitems.
}
}
You want the intersection of two sets:
NSMutableSet* intersectionSet = [NSMutableSet setWithArray:Allitems];
[intersectionSet intersectSet:[NSSet setWithArray:WORDS]];
NSArray* intersectionArray = [intersectionSet allObjects];
After this intersectionArray contains the items that are present in both Allitems and WORDS.
BTW, why do you capitalise variable names in a non-standard and inconsistent manner? Why not just allItems and words?
As #Arkku suggests: It's better to switch the arrays. In your example it does not matter much, but in case Allitems were (very) big, you can save (a lot of) memory and CPU usage:
NSMutableSet* intersectionSet = [NSMutableSet setWithArray:WORDS];
[intersectionSet intersectSet:[NSSet setWithArray:Allitems]];
NSArray* intersectionArray = [intersectionSet allObjects];
There are a various ways of doing it, each with different pros and cons. Let's have the following (consistently capitalized) variables in common for each case:
NSArray *allItems = #[ #"apple", #"orange", #"pear", #"cheese", #"milk", #"egg" ];
NSString *textFieldText = #"CHEESE ham pear";
NSArray *words = [textFieldText.lowercaseString componentsSeparatedByString:#" "];
NSPredicate
NSArray *matchingItems = [allItems filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"SELF IN %#", words]];
This is perhaps the shortest (in lines of code) way, but not the most performant if allItems can be very long as it requires traversing all of it.
Iteration
Of course you could also simply iterate over the collection and do the matching manually:
NSMutableArray *matchingItems = [NSMutableArray array];
for (NSString *item in allItems) {
if ([words containsObject:item]) {
[matchingItems addObject:item];
}
}
Again requires traversing all of allItems (although you could break the iteration if all words are matched).
In addition to the for loop there are of course many other ways for iteration, e.g., enumerateObjectsUsingBlock:, but they are unlikely to have any advantage here.
NSSet
NSSet is often a good option for this kind of matching since testing set membership is faster than with NSArray. However, if using the most straightforward method intersetSet: (in NSMutableSet) care must be taken to not inadvertently create a large mutable set only to discard most of its items.
If the order of allItems does not matter, the best way would be to change it from an array into a set and always keep that set around, i.e., instead of creating the array allItems, you would create an NSSet:
NSSet *setOfAllItems = [NSSet setWithArray:allItems];
Or if it needs to be mutable:
NSMutableSet *setOfAllItems = [NSMutableSet set];
[setOfAllItems addObjectsFromArray:items1];
[setOfAllItems addObjectsFromArray:items2];
Then, when you have that set, you create a temporary mutable set out of words (which is presumably always the smaller set):
NSMutableSet *setOfMatches = [NSMutableSet setWithArray:words];
[setOfMatches intersectSet:setOfAllItems];
NSArray *matchingItems = setOfMatches.allObjects;
This would be likely be the most performant solution if setOfAllItems is large, but note that the matches will then need to be exact. The other methods are more easily adapted to things like matching the strings in words against fields of objects or keys in a dictionary (and returning the matched objects rather than the strings). In such a case one possibility to consider would be an NSDictionary mapping the words to match to the objects to return (also fast to then iterate over words and test for membership in the dictionary).
Conversion to string
And, since the question included conversion of matches to a string:
[matchingItems componentsJoinedByString:#", "]
In the example case this would result in the string "pear, cheese" (or possibly "cheese, pear" if using sets).
Scenario = I need to loop through an array and find how many "unread" there are and count how many to display to the user.
What I'm Looking For = something like this (this is not my real code)
for (NSDictionary *dic in self.objects) {
[unreadCountArray addObject:dic[#"wasRead"]];
for (YES in unreadCountArray) {
//statements
}
}
Question = Does anyone know how to loop through and find all of the YES booleans?
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"wasRead = YES"];
NSArray *arr = [array filteredArrayUsingPredicate:predicate];
Can sort a thousand objects in 0.0004 seconds.
Then just do:
for (NSDictionary *object in arr) {
//Statements
}
Edit: actually after further experimentation, using fast-enumeration is about four times faster, about 0.0001, which if scaled to 100000 objects can be much, much faster.
NSMutableArray *test = [NSMutableArray array];
for (NSDictionary *dict in array)
if ([dict[#"theKey"] boolValue])
[test addObject:dict];
So for sorting, fast-enumeration is actually faster but for just a couple hundred objects, the performance increase is negligible.
And please before asking questions like this and getting downvotes, those could have been completely avoided by checking the documentation. Like this article and this article.
If you have an array of dictionaries, and you want to filter them, then filteredArrayUsingPredicate: is the method to use.
You can create a predicate using the key from your dictionary (predicateWithFormat:).
This will then give you an array of dictionaries that match the conditions in your predicate.
No sample code, I'm answering this on a phone.
I've got the following NSArray :
NSArray myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
I want the list of value of my dictionaries :
#[#"5 min",#"15 min",#"30 min",#"1 hour"]
And the list of key of my dictionaries :
#[#300, #900, #1800, #3600]
What is the best way to do that ? I was thinking about predicate, but I don't know how to use it ?
Without some code to show how you'd want to go about this it is difficult to be sure exactly what you are after, and there is a bit of confusion in the question.
First, a predicate is exactly that - a statement that can be proven true or false. Predicates are hence used in logic expressions, including those employed implicitly in database queries - such as Core Data.
That is not what you want, if I read your question correctly. What you want is to reduce the complexity of your data model, removing some excess (one would hope) information in the process. A sort of flattening of an array of dictionaries.
Fair enough.
I can also see how the confusion with predicates came about - they are most often constructed using Key-Value Coding. KVC, as it is also known, is a very powerful technique that can accomplish what you are after. It just does not have much to do with a logic statement.
Having cleared that up, with KVC you can do what you want, and with minimal fuss. It goes like this:
NSArray *values = [myArray valueForKeyPath: #"#unionOfArrays.#allValues"];
NSArray *keys = [myArray valueForKeyPath: #"#unionOfArrays.#allKeys"];
A brief explanation might be in order:
The results that we want are
All the values (or keys) of each dictionary, obtaining an array of arrays of values (or keys)
Then we want to flatten these arrays into a single array.
To obtain all values (or keys) from a dictionary using KVC, the special key is #allValues or #allKeys, respectively.
The #unionOfArrays operator makes a union of the arrays obtained from the expression that follows it, i.e., flattens it into the array you wanted.
The price you pay for this coding simplicity is that you have to use KVC key paths with collection operators, which are just strings in your code. You therefore lose any help from the compiler with syntax and it doesn't check that the keys you enter exist in the objects. Similarly, the debugger and error messages are unhelpful if you mistype or use the wrong operator, for instance.
You can use dictionary property allValues to get all values of dictionary.
Try this code in your case
NSArray *myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
NSMutableArray *arr = [NSMutableArray array];
for (NSDictionary *dict in myArray) {
[arr addObject:[[dict allValues] objectAtIndex:0]];
}
NSLog(#"%#",arr);
Note : Make sure you have only one value in each dictionary.
it will return
[
5 min,
15 min,
30 min,
1 hour
]
#johnyu's answers is technically correct, but I don't see any reason to include the secondary loop, especially if the data structure will remain the same.
NSArray *myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
NSMutableArray *arrayOfValues = [NSMutableArray new];
NSMutableArray *arrayOfKeys = [NSMutableArray new];
for (NSDictionary *dictionary in myArray) {
[arrayOfValues addObject:dictionary.allValues[0]];
[arrayOfKeys addObject:dictionary.allKeys[0]];
}
NSLog(#"%#",arrayOfKeys);
NSLog(#"%#",arrayOfValues);
Try this:
NSArray *myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
NSMutableArray *keyArray = [[NSMutableArray alloc] init];
NSMutableArray *valueArray = [[NSMutableArray alloc] init];
for (NSDictionary *dictionary in myArray) {
for (NSString *key in dictionary) {
[keyArray addObject:key];
[valueArray addObject:[dictionary objectForKey:key]];
}
}
I am trying to sort an iOS UITableView object. I am currently using the following code:
// Sort terms alphabetically, ignoring case
[self.termsList sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
This sorts my list, whist ignoring case. However, it would be nice to ignore punctuation as well. For example:
c.a.t.
car
cat
should be sorted as follows:
car
c.a.t.
cat
(It doesn't actually matter which of the two cats (cat or c.a.t.) comes first, so long as they're sorted next to one another).
Is there a simple method to get around this? I presume the solution would involve extracting JUST the alphanumeric characters from the strings, then comparing those, then returning them back to their former states with the non-alphanumeric characters included again.
In point of fact, the only characters I truly care about are periods (.) but if there is a solution that covers all punctuation easily then it'd be useful to know.
Note: I asked this exact same question of Java a month ago. Now, I am creating the same solution in Objective-C. I wonder if there are any tricks available for the iOS API that make this easy...
Edit: I have tried using the following code to strip punctuation and populate another array which I sort (suggested by #tiguero). However, I don't know how to do the last step: to actually sort the first array according to the order of the second. Here is my code:
NSMutableArray *arrayWithoutPunctuation = [[NSMutableArray alloc] init];
for (NSString *item in arrayWithPunctuation)
{
// Replace hyphens/periods with spaces
item = [item stringByReplacingOccurrencesOfString:#"-" withString:#" "]; // ...hyphens
item = [item stringByReplacingOccurrencesOfString:#"." withString:#" "]; // ...periods
[arrayWithoutPunctuation addObject:item];
}
[arrayWithoutPunctuation sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
This provides 'arrayWithoutPunctuation' which is sorted, but of course doesn't contain the punctuation. This is no good, since, although it is now sorted nicely, it no longer contains punctuation which is crucial to the array in the first place. What I need to do is sort 'arrayWithPunctuation' according to the order of 'arrayWithoutPunctuation'... Any help appreciated.
You can use a comparison block on an NSArray and your code will look like the following:
NSArray* yourStringList = [NSArray arrayWithObjects:#"c.a.t.", #"car", #"cat", nil];
NSArray* yourStringSorted = [yourStringList sortedArrayUsingComparator:^(id a, id b){
NSString* as = (NSString*)a;
NSString* bs = (NSString*)b;
NSCharacterSet *unwantedChars = [NSCharacterSet characterSetWithCharactersInString:#"\\.:',"];
//Remove unwanted chars
as = [[as componentsSeparatedByCharactersInSet: unwantedChars] componentsJoinedByString: #""];
bs = [[as componentsSeparatedByCharactersInSet: unwantedChars] componentsJoinedByString: #""];
// make the case insensitive comparison btw your two strings
return [as caseInsensitiveCompare: bs];
}];
This might not be the most efficient code actually one other option would be to iterate on your array first and remove all unwanted chars and use a selector with the caseInsensitiveCompare method:
NSString* yourStringSorted = [yourStringList sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
This is a bit cleaner, and a bit more efficient:
NSArray* strings = #[#".....c",#"a.",#"a",#"b",#"b...",#"a..,"];
NSArray* sorted_strings = [strings sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString* a = [obj1 stringByTrimmingCharactersInSet:[NSCharacterSet punctuationCharacterSet]];
NSString* b = [obj2 stringByTrimmingCharactersInSet:[NSCharacterSet punctuationCharacterSet]];
return [a caseInsensitiveCompare:b];
}];
For real efficiency, I'd write a compare method that ignores punctuation, so that no memory allocations would be needed just to compare.
My solution would be to group each string into a custom object with two properties
the original string
the string without punctuation
...and then sort the objects based on the string without punctuation.
Objective C has some handy ways to do that.
So let's say we have two strings in this object:
NSString *myString;
NSString *modified;
First, add your custom objects to an array
NSMutableArray *myStrings = [[NSMutableArray alloc] init];
[myStrings addObject: ...];
Then, sort the array by the modified variable using the handy NSSortDescriptor.
//You can specify the variable name to sort by
//Sorting is done according to the locale using localizedStandardCompare
NSSortDescriptor *mySortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"modified" ascending:YES selector:#selector(localizedStandardCompare:)];
[myStrings sortedArrayUsingDescriptors:#[ mySortDescriptor ]];
Voila! Your objects (and strings) are sorted. For more info on NSSortDescriptor...
I am using this code in a loop to populate an NSMutable Array of NSMutableSets (of NSString objects). The index of the NSSet is based on the length of the word.
// if set of this length not initialized yet, initialize set.
wordIndex = [NSString stringWithFormat:#"%d", currentWordLength];
if ([myWordArray objectForKey:wordIndex] == nil)
[myWordArray setObject:[[NSMutableSet alloc] initWithObjects:currentWord, nil] forKey:wordIndex];
else
[[myWordArray objectForKey:wordIndex] addObject:currentWord];
The final intention is to split up an array of words into an array of sets of words grouped by their lengths.
However, I see that [myWordArray count] is 0 after this. Why?
You are confusing the methods of NSMutableDictionary and NSMutableArray: In Objective-C arrays do not have keys but have indexes. If you change the class for myWordArray to NSMutableDicitionary it should work.
Try this, it looks very much like your logic, but (1) it uses NSNumbers as keys, which makes a little more sense, (2) handles the missing set condition more simply, but just adding the set, and (3) breaks up the source lines somewhat for easier debugging...
NSArray *inputStrings = // however these are initialized goes here
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSString *currentString in inputStrings) {
NSInteger currentWordLength = currentString.length;
wordIndex = [NSNumber numberWithInt:currentWordLength];
NSMutableSet *wordSet = [result objectForKey:wordIndex];
if (!wordSet) {
wordSet = [NSMutableSet set];
[result setObject:wordSet forKey:wordIndex];
}
[wordSet addObject:currentWord];
}
If you still have an empty dictionary after running this, it might be simpler to watch what's happening by stepping through it.