Create NSMutableArray with NSDictionary elements slow - ios

This chunk of code is a method that creates an array for use by multiple other classes. Input is an array from a CoreData fetch, of type NSDictionaryResultType.
3 of the fields are strings that I need to break into arrays, thus the componentsSeparatedByString.
The resulting array, _dataProductionArray, works great --- BUT --- this chunk of code takes a FULL 5 SECONDS to process for about 32,000 records.
Any help pointing out glaring mistakes that are causing this slow performance would be greatly appreciated!!
NSMutableArray *dataArray = [NSMutableArray array];
int j = 0;
int maxNumMonths = 0;
for (id obj in _dictionaries) {
if ([_dictionaries[j] [#"month"] length] >0 ) {
// get production values
NSArray *aItems = [_dictionaries[j] [#"prodA"] componentsSeparatedByString:#","];
NSArray *bItems = [_dictionaries[j] [#"prodB"] componentsSeparatedByString:#","];
NSArray *monthItems = [_dictionaries[j] [#"month"] componentsSeparatedByString:#","];
NSMutableArray *productionAArray = [NSMutableArray array];
NSMutableArray *productionBArray = [NSMutableArray array];
int monthLoop = 1;
for (NSNumber *month in monthItems) {
if (monthLoop <= MONTHS_OF_PRODUCTION) {
if ([month intValue] == monthLoop) {
[productionAArray addObject:[aItems objectAtIndex:monthLoop-1]];
[productionBArray addObject:[bItems objectAtIndex:monthLoop-1]];
productionCount ++;
if (monthLoop > maxNumMonths)
maxNumMonths = monthLoop;
}
}
monthLoop++;
}
NSDictionary *arrayItem = #{#"name":_dictionaries[j] [#"name"],
#"type":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"type"]],
#"height":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"height"]],
#"aArray":productionAArray,
#"bArray":productionBArray,
};
[dataArray addObject:arrayItem];
}
j++;
}
_dataProductionArray = [NSArray arrayWithArray:dataArray];

I can see a few optimizations you could do in the loop, but I'm not sure how much these would help (especially if the compiler is doing them anyway). The root problem is that 32k is a lot of iterations.
Do you need all 32k results at once? You could get a dramatic improvement in user experience by doing this work lazily, as the UI demands the transformed record.
This approach would be to make dataProductionArray a mutable dictionary, indexed by an NSNumber index. Then, instead of ...
// replace this
self.dataProductionArray[128];
// with this
[self dataProductionAtIndex:#128];
That new getter method calls the code you wrote lazily, like this ...
- (id)dataProductionAtIndex:(NSNumber *)index {
// replace dataProductionArray with dataProductionDictionary
id result = self.dataProductionDictionary[index];
if (!result) {
result = [self getDataAt:index];
self.dataProductionDictionary[index] = result;
}
return result;
}
Then getDataAt: is a simple refactor of the code you posted, except instead of looping 32k elements, it does the work for just one index that gets passed in....
- (id)getDataAt:(NSNumber *)index {
int j = [index intValue];
// no loop, just skip to iteration j
NSArray *aItems = [_dictionaries[j] [#"prodA"] componentsSeparatedByString:#","];
NSArray *bItems = [_dictionaries[j] [#"prodB"] componentsSeparatedByString:#","];
// and so on, then at the end, don't save arrayItem, just return it
NSDictionary *arrayItem = #{#"name":_dictionaries[j] [#"name"],
#"type":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"type"]],
#"height":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"height"]],
#"aArray":productionAArray,
#"bArray":productionBArray,
};
return arrayItem;
}
PS - A mutable dictionary is a good data structure for lazy evaluation. The next level of sophistication is NSCache, which acts like a mutable dictionary and also manages memory (class ref here).

Your for loop is daft. Just write
for (NSDictionary* dict in _dictionaries)...
and use dict instead of _dictionaries [j]. One method call saved each time.
stringWithFormat: creates a new string each time. Can't you just add the item itself instead of turning it into a string?
Instead of extracting all the items into productionAArray and productionBArray, create an NSIndexSet, fill it in the loop -- or better yet using a block -- and create the arrays in one go.

Related

Strange dictionary sort ios objective c

I want to do kind of a weird dictionary sort. I have non-unique values and keys and get something like this
NSArray *counts = [#"1",#"2",#"2",#"3",#"6",#"10"];
NSArray *names =[#"Jerry",#"Marge",#"Jerry",#"Marge",#"Jen",#"Mark"];
The output that I want is an descending ordered list by counts with unique names. I don't want lower values of the same person in my outputted arrays. The output should be.
sortedNames=[#"Mark",#"Jen",#"Marge",#"Jerry"]
sortedCounts=[#"10",#"6",#"3",#"2"];
I would really appreciate some help on this.
NSMutableArray *userNameArray = [[NSMutableArray alloc] init];
NSMutableArray *countArray = [[NSMutableArray alloc] init];
for (NSDictionary *dict in bigDick) {
NSString *nameString =[dict objectForKey:#"Name"];
NSString *countString =[dict objectForKey:#"Count"];
NSInteger countInt = [countString integerValue];
NSNumber *countNumber =[NSNumber numberWithInt:countInt];
[userNameArray addObject:nameString];
[countArray addObject:countNumber];
}
NSArray *namesAscending =[[userNameArray reverseObjectEnumerator]allObjects];
NSArray *countsAscending=[[countArray reverseObjectEnumerator]allObjects];
// Put the two arrays into a dictionary as keys and values
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:countsAscending forKeys:namesAscending];
// Sort the first array
NSArray *sortedCountArray = [[dictionary allValues] sortedArrayUsingSelector:#selector(compare:)];
// Sort the second array based on the sorted first array
// NSArray *sortedNameArray= [dictionary objectsForKeys:sortedCountArray notFoundMarker:[NSNull null]];
NSMutableArray *nameArray =[[NSMutableArray alloc] init];
for (int i=1; i<sortedCountArray.count; i++) {
NSString *name = [dictionary allKeysForObject:sortedCountArray[i]];
if (sortedCountArray[i]!=sortedCountArray[i-1]) {
[nameArray addObject:name];
}
}
an old method is to manual sort the array with numbers, by searching on every iteraton for the biggest value, and when you find the max value take the name from the other vector at index of the max number and move it in new vector...
max = counts[0];
counter = 0;
for (int i=0;i<counts.count;i++)
{
temp = counts[i];
if (max<temp)
max = temp;
counter = i;
}
[new_names addObject: [names objectAtIndex:counter]];
[new_numbers addObject: max];
[numbers removeObjectAtIndex: counter];
[names removeObjectAtIndex:counter];
Try something like this. It should work if you do it this way.
Important! do not remove elements in for from array that you count for the for length.
Your problem is in your algorithm design, if you step through it a line at a time in the debugger you should see what it does and where it goes wrong.
We're not here to write you code, but let's see if we can go through one step of an algorithm to help you one your way:
Useful fact: If you lookup a key in a dictionary and that key does not exist the return value will be nil.
From this: you can use a dictionary to keep track of the names you have seen paired with the highest score so far. You obtain a name,score pair, lookup the name in the dictionary - if you get nil its a new name with a new high score. If it's not nil its the currently known high score, so you can compare and update.
That's a rough algorithm, let's try it. Before we start rather than using literal strings for keys everywhere let's define some constants. This has the advantage that we won't mistype the strings, the compiler will spot if we mistype the constant names. These can be defined at the file level or within a method:
const NSString *kName = #"Name";
const NSString *kCount = #"Count";
Now to the code, in a method somewhere, we'll need a dictionary:
NSMutableDictionary *highScores = [NSMutableDictionary new]; // a single dictionary rather than your two arrays
Now start your loop as before:
for (NSDictionary *dict in bigDict) // same loop as your code
{
and extract the two values as before:
NSString *nameString = dict[kName]; // same as your code, but using modern syntax
NSInteger countInt = [dict[kCount] integerValue]; // condense two lines of your code into one
Now we can lookup the name in our dictionary:
NSNumber *currentScore = highScores[nameString]; // get current high score for user, if any
If the name exists as a key this will return the current associated value - the score in this case, if there is no matching key this will return nil. We can test for this in a single if:
if (currentScore == nil // not seen user before, no high score
|| currentScore.integerValue < countInt) // seen user, countInt is greater
{
The above condition will evaluate to true if we either need to add the name or update its score. Adding & updating a key/value pair is the same operation, so we just need the line:
highScores[nameString] = #(countInt); // add or update score for user
and a couple of braces to terminate the if and for:
}
}
Let's see what we have:
NSLog(#"Output: %#", highScores);
This outputs:
Output: {
Jen = 6;
Jerry = 2;
Marge = 3;
Mark = 10;
}
Which is a step in the right direction. (Note: the dictionary is not sorted, NSLog just displays the keys in sorted order.)
Make sure you understand why that works, copy the code and test it. Then try to design the next phase of the algorithm.
If you get stuck you can ask a new question showing the algorithm and code you've developed and someone will probably help. If you do this you should include a link to this question so people can see the history (and know you're not trying to get an app written for you through multiple questions!)
HTH
Try this.
sortedArray = [yourArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
After sort your array then remove duplicates using following.
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray: sortedArray];
NSArray *arrayWithoutDuplicates = [orderedSet array];

Looping thru NSArray of NSString logic

I need help with the following:
I have an NSArray with NSStrings, I want to loop thru these strings and find a matching string, when match is found the strings after this match will be extracted into an NSDictionary until a certain other match is hit.
Here is an example:
NSArray *array = #[#"Fruit",#"Apple",#"Vegtable",#"Tomato",#"Fruit",#"Banana",#"Vegtable",#"Cucumber"];
So I want to loop thru this array and split it in 2 arrays one for fruit and one for vegetable.
Anyone can help with the logic?
Thanks
This is probably the simplest way to solve the problem:
NSArray *array = #[#"Chair",#"Fruit",#"Apple",#"Orange",#"Vegetable",#"Tomato",#"Fruit",#"Banana",#"Vegetable",#"Cucumber"];
NSMutableArray *fruitArray = [NSMutableArray array];
NSMutableArray *vegetableArray = [NSMutableArray array];
NSMutableArray *currentTarget = nil;
for (NSString *item in array)
{
if ([item isEqualToString: #"Fruit"])
{
currentTarget = fruitArray;
}
else if ([item isEqualToString: #"Vegetable"])
{
currentTarget = vegetableArray;
}
else
{
[currentTarget addObject: item];
}
}
In one iteration over the array, you just keep adding items to a result array using a pointer to one of two result arrays according to the last occurrence of the #"Fruit" or #"Vegetable" string.
This algorithm ignores all items before the first occurrence of the #"Fruit" or #"Vegetable" string, because the currentTarget is initialized to nil, which ignores the addObject: messages. If you want different behaviour, just change the initialization.
You said you wanted the results in a NSDictionary, but didn't specify what should be the key. If you want one NSDictionary with two keys, Fruit and Vegetable, and values NSArrays containing the items, just use the arrays previously created:
NSDictionary *dict = #{ #"Fruit": fruitArray, #"Vegetable": vegetableArray };
PS: You have a typo in your example, Vegtable instead of Vegetable. I corrected it in my code, so keep it in mind.
If I completely understand you:
NSArray *array = #[#"Fruit",#"Apple",#"Vegtable",#"Tomato",#"Fruit",#"Banana",#"Vegtable",#"Cucumber"];
NSMutableArray *fruits = [NSMutableArray array];
NSMutableArray *vegtables = [NSMutableArray array];
for (NSInteger i = 0; i < array.count; ++i){
if ([array[i] isEqualToString:#"Fruit"]){
++i;
[fruits addObject:array[i]];
}
else if ([array[i] isEqualToString:#"Vegtable"]){
++i;
[vegtables addObject:array[i]];
}
}

Building combinations using recursion rather than using iteration

I am trying to customise the combination method found in the solution given here: Combinations of different NSArray objects so that it utilises recursion, instead of the solution given, which is using iteration. The reason why I am trying to customise this function is to increase the performance of this function as it starts slowing down significantly when the arrays start getting larger and there are many combinations to compute.
Any advice on how this could be done?
I think you'd have to combine iteration and recursion. If you want it to do a varying number of arrays then I came up with something like this...
First pass at an iterative method.
- (NSArray *)combineArrays:(NSArray *)arrays
{
// arrays is an array of arrays of elements that you want to combine.
NSMutableArray *combinations = [NSMutableArray array];
for(NSArray *array in arrays) {
NSMutableArray *temp = [NSMutableArray array];
for (id element in array) {
if (combinations.count == 0) {
// the first level of the array is just all the elements from the first array
[temp addObject:#[element]];
} else {
// copy each array in combinations for each new element and add new element to end
for (NSArray *array in combinations) {
[temp addObject:[array arrayByAddingObject:element];
}
}
}
// save the current combinations
combinations = temp;
}
return combinations;
}
I'm almost certain that the middle part can be recursed into a single method. Just working on that now.
- (NSArray *)combineArray:(NSArray *)combinations withArrays:(NSMutableArray *)arrays
{
// if there are no arrays to combine then you are at the end of recursion so return combinations.
if (!array || array.count == 0) {
return combinations;
}
// to start with you "combine" the first array with an empty array
if (!combinations) {
combinations = #[#[]];
}
NSMutableArray *newCombinations = [NSMutableArray array];
NSArray *array = [arrays firstObject];
for (id element in array) {
for (NSArray combination in combinations) {
[newCombinations addObject:[combination arrayByAddingObject:element];
}
}
[arrays removeObjectAtIndex:0];
return [self combineArray:newCombinations withArrays:arrays];
}
Something like this might work. It's no faster than iteration though.
Take a look at this answer JavaScript - Generating combinations from n arrays with m elements which does what you want but in JavaScript. You still have to touch each element of each array once though.

Multi dimensional array containing array of dictionaries

Trough my ios first app developpement i have to re-order an array containing dictionaires, parsed from a xml document, the purpose of re-ordering it is to send it to a function that build a collapsible, so it need a childCell index and a parentCell Index to print the strings of each child then pass to another parent. The problem is here : i'am able to fill my big array containing arrays of dictionaries, then i that array and do a loop to fill the childArray to contain multiple dictionaries, then i add this child array to my parent array, every thing seem to run but it gives me an empty array at the end. i put my code to show you how i tried to do this :
stories is the NSArray of dictionaries, childArray is the Array that should contain the dictionaries of stories, and parentArray is the Array that contains it all.
If someone who already did that can explain me were it goes wrong please it would be very much appreciated.
-(NSMutableArray *)orderChildsAndParents:(NSMutableArray *)fromArray
{
int varial = 0;
int catIndex = 0;
NSMutableArray *parentArray = [NSMutableArray array];
while(varial < [stories count])
{
NSString* cleanedString = [[[[stories objectAtIndex:varial] objectForKey:#"category"] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]
componentsJoinedByString:#""];
if ([cleanedString isEqualToString:[category objectAtIndex:catIndex] ])
{
if (!childArray || !childArray.count)
childArray = [NSMutableArray array];
[childArray addObject:[stories objectAtIndex:varial]];
varial++;
}
else{
[parentArray addObject:childArray];
[childArray removeAllObjects];
catIndex++;
}
}
NSLog(#"%#", parentArray);
return parentArray;
}
- (NSString *) labelForCellAtChildIndex:(NSInteger) childIndex withinParentCellIndex:(NSInteger) parentIndex {
NSMutableArray *orderedArray = [self orderChildsAndParents:stories];
NSLog(#"format string %#", [[[orderedArray objectAtIndex:parentIndex] objectAtIndex:childIndex] objectForKey:#"name"]); // empty :8
return [[[orderedArray objectAtIndex:parentIndex] objectAtIndex:childIndex] objectForKey:#"name"];
}

a faster way to mutate an Array of NSMutableDictionaries

based on the fact that you cannot edit mutable Collections while enumerating them, this is the best solution i could come up with to edit a Array of NSMutableDictionaries:
__block NSMutableDictionary *tempDict = [NSMutableDictionary dictionaryWithCapacity:1];
__block NSUInteger idx;
[_myArray enumerateObjectsUsingBlock:^(NSMutableDictionary* obj,
NSUInteger indx, BOOL *stop) {
if (// some condition is met) {
tempDict = [obj mutableCopy];
idx = indx;
}
}];
[tempDict setObject:[NSNumber numberWithInt:thisQueryResults] forKey:#"resultsNum"];
[_myArray replaceObjectAtIndex:idx withObject:rowSelected];
this seems way too complicated (even for a language like obj-c).. and since it's involving two data types (NSMutableArray and NSMutableDictionary), it doesn't seem like I can cleanly put them into a category.. advice?
update: one comment asked why do I create a mutablecopy (as opposed to just a copy.. since it's copying a mutable object)..
suppose I just used copy.. if i put a break on tempDict this is what I get:
// tempDict = [obj copy]
po tempDict
$0 = 0x0b28cc10 <__NSArrayI 0xb28cc10>(
1
)
// tempDict = [obj mutableCopy]
po tempDict
$0 = 0x0b28cc10 <__NSArrayM 0xb28cc10>( //notice the M in __NSArrayM as opposed to I above
1
)
in case of copy.. if I follow it with a line like this:
[tempDict setObject:[NSNumber numberWithInt:thisQueryResults] forKey:#"resultsNum"];
I get this error:
[__NSDictionaryI setObject:forKey:]: unrecognized selector sent to instance 0xb245100
I get the same above error with this code:
for (NSUInteger idx = 0; idx < [_myMutableArray count]; idx++) {
NSMutableDictionary* myMutableDict = _myMutableArray[idx];
[myMutableDict setObject:obj forKey:key];
}
update 2:
the origin of the problem was instantiating non mutable arrays and dictionaries.. I'm new to the whole new obj-c literals, so I didn't know that to create a NSMutableArray and NSDictionary, you gotta do this, respectively:
[#[..] mutableCopy]
[#{..} mutableCopy]
So in your case, I don't quite follow why you call tempDict = [obj mutableCopy]; when from the conditions you write the dictionary is already writable.
You can use several tricks. Like using
for (NSUInteger idx = 0; idx < _myArray.count: idx++_ {
NSMutableDictionary *obj = _myArray[idx];
// modify
}
For NSDictionaries you can get allKeys and iterate over that copy. This is a bit slower than using fast enumeration, but still faster than doing workarounds like boxing integers to replace later :)
In your case you are NOT modifying the array at all only the dictionaries within the array. There are no contstraits on how you modify the objects within the array. Here is a bit of equivalent code:
for (NSMutableDictionary *dict in _myArray) {
if (someCondition)
[dict setObject:[NSNumber numberWithInt:thisQueryResults] forKey:#"resultsNum"]
}
You would have a problem if you absolutely needed to replace the object in your array. In that case, if the array is not huge I would suggest the same as #Markus. Iterate over a copy and modify the original.
Maybe you can use KVC and do :
NSArray<NSMutableDictionary *> *result = [[_myArray filteredArrayUsingPredicate:[NSPredicate withFormat:#"{YOUR CONDITION}"]] valueForKey:#"mutableCopy"];

Resources