Iterating through two arrays - ios

I have an iOS app that I load data from a server into an NSArray and then into UITableView.
This works fine. However, when the user pulls down to refresh the data, I make a new call to my server and grab data again. This new data contains objects that my local data already has and any new objects.
So localArray when populated for the first time will have objects, [A,B,C,D]. I then add a new object server side and refresh the data on the iOS app.
serverData will now have objects [A,B,C,D,E,F] - I need to add, E,F to localArray.
I thought a nested for loop would be the answer, something like this:
NSMutableArray *newItems = [NSMutableArray array];
for (BBItem *itemA in serverDataArray){
for (BBItem *itemB in localArray){
if (![itemA.name isEqualToString:itemb.named]){
//add to a newItems array
}
}
}
However I end up with newItems array containing a lot of duplicates of the same item. What is going on here?

Your algorithm will add an item to newItems if it's not equal to one item from localArray. Therefore, you would end up with lots of identical items most of the time.
You cannot add inside the nested loop. You need to go through all localArray, see that the item is not there, and only then add it. Here is how to fix your code:
for (BBItem *itemA in serverDataArray){
BOOL there = NO;
for (BBItem *itemB in localArray){
if ([itemA.name isEqualToString:itemb.named]){
there = YES;
break;
}
}
if (!there) {
//add to a newItems array
}
}
This is inefficient, because the inner loop goes through all items that you currently have. As the number of local items grows, this loop will get slower and slower. You would be better off maintaining an NSMutableSet of names of local items, because you can check it in constant, rather than in linear, time:
NSMutableSet *localNames = [NSMutableSet set];
for (BBItem *itemB in localArray) {
[localNames addObject:itemB.name];
}
for (BBItem *itemA in serverDataArray){
if (![localNames containsObject:itemA.name]) {
//add to a newItems array
}
}

Related

Fast enumeration with RLMResults on large datasets

I'm running into an issue where I need to enumerate an RLMResults collection on a relatively large data set (>7,000 items). I get that realm lazily loads its objects as they are accessed, but the issue I run into is that I need to access each of these items in the collection which causes each of the 7,000+ items to be loaded into memory thereby causing an out of memory error. According to the realm documentation they don't support limiting the results of a query.
An example of something I might need to do is to go through and delete files from the file system, and yes I could query using a predicate and only ask for items that are cached, but in a worst case scenario that query could return all items in the library.
RLMResults<DLLibrary *> *allItems = [DLLibrary allObjects];
for( DLLibrary *item in allItems ) {
// My understanding is that once the realm object is ready, it will be
// lazily loaded into memory. If I have many 1,000's of items in my data
// store this quickly becomes a problem memory wise.
if( item.isCached ) {
[[DLCacheService instance] deleteCachedFileWithDocumentId:item.id];
}
}
The easiest way to mitigate this would be the use of an #autoreleasepool brace to explicitly guarantee the object you lazily-loaded is promptly released once you've finished checking its contents. :)
RLMResults<DLLibrary *> *allItems = [DLLibrary allObjects];
for (NSInteger i = 0; i < allItems.count; i++) {
#autoreleasepool {
DLLibrary *item = allItems[i];
if (item.isCached) {
[[DLCacheService instance] deleteCachedFileWithDocumentId:item.id];
}
}
}

Best way to loop through an array as fast as possible?

Right now, my app retrieves around 1000 CKRecords which gets put into an array. I need to loop through this array later on. When I just had about 100 records to loop through my app worked smoothly and fast, but now it gets slower and slower as the number of records grow. I plan to have around 3000 CKRecords to retrieve so this could be a serious problem for me.
This is what I have come up with for now:
//movies == The NSMutableArray
if let recordMovies = movies.objectEnumerator().allObjects as? [CKRecord] {
for movie in recordMovies {
//Looping
print(movie.objectForKey("Title") as? String)
{
}
But this doesn´t help a lot, seeing as I have to turn the NSMutableArray into an Array of CKRecords. I do this because I have to access the CKRecords attributes when looping, such as movieTitle. What measures can I take to speed up the looping process
Iterating through 1000 (or 5000) elements of an array should take a tiny fraction of a second.
Printing to the debug console is very slow however. Get rid if your print statement.
If movies is an array of CKRecord objects, why can't you just cast it to the desired array type?
let start = NSDate.timeIntervalSinceReferenceDate()
if let recordMovies = movies as? [CKRecord] {
for movie in recordMovies {
//Do whatever work you need to do on each object, but DON'T use print
{
}
let elapsed = NSDate.timeIntervalSinceReferenceDate() - start
print( "iterating through the loop took \(elapsed) seconds")
You can save a lot of time by making "movies" not just an NSMutableArray, but an NSMutableArray of CKRecords, which makes it unnecessary to convert the array from NSMutableArray to a Swift array of CKRecord.
In Objective-C: Instead of
NSMutableArray* movies;
you use
NSMutableArray <CKRecord*> *movies;
As a result, movies [1] for example will have type CKRecord*, not id. An assignment movies [1] = something; requires that "something" is of type CKRecord* or id, nothing else.

NSMutable dictionary not working properly

I am developing an iPad application and for this application I have one function as below :-
-(void)testcurrentest:(NSMutableDictionary *)keydictionary{
NSArray *allKeys = [keydictionary allKeys];
if ([allKeys count] > 0) {
for(int i = 0;i< allKeys.count;i++){
[_currenies removeAllObjects];
NSString *product = [NSString stringWithFormat:#"%#", [keydictionary objectForKey:allKeys[i]]];
int kl = [productPriceSeasonCode intValue];
for(int i =0;i<kl;i++){
[_currenies addObject:#"0"];
}
NSLog(#"................%#",_currenies);
[_currencydictionary1 setObject:_currenies forKey:allKeys[i]];
NSLog(#"full dictionary...%#",_currencydictionary1);
}
}
}
Here, NSLog print the currencies array based on the kl integer values but when I'm trying to set the NSMutableDictionary the currencies but mutable array always show the latest array values.
You are using the same array for all values, they should be unique objects if you don't want change of one value to affect the other values. Initialise _currenies on every loop step or use its deep copy when preparing a new object.
A bit of code:
[_currenies removeAllObjects]; // < The same array you've added to dict on previous loop steps
Creating a new array at each loop step would create a unique object for all key-value pair:
_currenies = [NSMutableArray array]; // < Note it is not retained, apply memory management depending on your project configuration
Your code is a garbled mess. As others have pointed out, you are using the same loop index, i, in 2 nested loops, making it very hard to tell your intent. Don't do that, ever. It's horrible programming style.
You are also creating a string "product" that you never use, and fetching the same integer value of productPriceSeasonCode on every pass through the outer loop. I suspect you meant to fetch a value that varies with each entry in your keydictionary.
Then, you have an array, _currenies, which you empty on each pass through your outer loop. You then add a number of "0" strings to it, set a key/value pair in your _currencydictionary1 dictionary to the contents of that array, and then repeat. Since you re-use your _currenies array each time, every key/value pair you create in your _currencydictionary1 dictionary points to the exact same array, which you keep changing. At the last iteration of your outer loop, all the entries in your _currencydictionary1 will point to your _currenies array, which will contain the last set of contents you put there.
Create a new array for each pass through your outer array, and add that newly created array to your _currencydictionary1. You want a unique array in each key/value pair of your _currencydictionary1.
In short, NSMutableDictionary is working just fine. It's your code that isn't working properly.
Not an answer but comments don't have formatting.
The question should provide more information on the input and desired output.
First simplify your code and it should be easier to find the error:
-(void)testcurrentest:(NSMutableDictionary *)keydictionary{
NSArray *allKeys = [keydictionary allKeys];
for(NSString *key in allKeys) {
[_currenies removeAllObjects];
int kl = [productPriceSeasonCode intValue];
for(int i =0; i<kl; i++){
[_currenies addObject:#"0"];
}
NSLog(#"................%#",_currenies);
_currencydictionary1[key] = _currenies;
NSLog(#"full dictionary...%#",_currencydictionary1);
}
}
Note: product was never used.

Objective-C how to pull several random items out of NSArray without getting duplicates? [duplicate]

This question already has answers here:
Getting a random object from NSArray without duplication
(3 answers)
Closed 7 years ago.
I have an array of random properties I would like to assign to equipment within the game I'm developing.
The code that I use below is returning an NSArray. I'm interested if there's way to get item indices from that array without getting duplicate values. The obvious solution is to create a mutable array with the returned array, do random, remove item that was returned and loop until the number of items is received.
But is there a different way of getting X random items from NSArray without getting duplicates?
//get possible enchantments
NSPredicate *p = [NSPredicate predicateWithFormat:#"type = %i AND grade >= %i", kEnchantmentArmor,armor.grade];
NSArray* possibleEnchantments = [[EquipmentGenerator allEnchantmentDictionary] objectForKey:#"enchantments"];
//get only applicable enchantments
NSArray *validEnchantments = [possibleEnchantments filteredArrayUsingPredicate:p];
NSMutableArray* mutableArray = [NSMutableArray arrayWithArray:validEnchantments];
NSDictionary* enchantment = nil;
if(mutableArray.count>0)
{
//got enchantments, assign number and intensity based on grade
for (int i = 0; i<3;i++)
{
enchantment = mutableArray[arc4random()%mutableArray.count];
[mutableArray removeObject:enchantment];
//create enchantment from dictionary and assign to item.
}
}
You can shuffle the array using one of the following techniques:
What's the Best Way to Shuffle an NSMutableArray?
Non repeating random numbers
Then, take the first X elements from the array.
Many years ago, I was working on card game and I realized that shuffling the deck was an inefficient way to get random cards. What I would do in your shoes is pick a random element, and then replace it with the element at the end of the array, like so:
#interface NSMutableArray (pickAndShrink)
- (id) pullElementFromIndex:(int) index // pass in your random value here
{
id pickedItem = [self elementAtIndex:index];
[self replaceObjectAtIndex:index withObject:[self lastObject]];
[self removeLastObject];
return pickedItem;
}
#end
The array will shrink by one every time you pull an element this way.
You could use a random number generator to pick a starting index, and then pick the subsequent indices based on some kind of math function. You would still need to loop depending on how many properties you want.
Eg:
-(NSMutableArray*)getRandomPropertiesFromArray:(NSArray*)myArray
{
int lengthOfMyArray = myArray.count;
int startingIndex = arc4random()%lengthOfMyArray;
NSMutableArray *finalArray = [[NSMutableArray alloc]init]autorelease];
for(int i=0; i<numberOfPropertiesRequired; i++)
{
int index = [self computeIndex:i usingStartingIndex:startingIndex origninalArray:myArray];
[finalArray addObject:[myArray objectAtIndex:index]];
}
return finalArray;
}
-(int)computeIndex:(int)index usingStartingIndex:(int)startingIndex
{
//You write your custom function here. This is just an example.
//You will have to write some code to make use you don't pick an Index greater than the length of your array.
int computedIndex = startingIndex + index*2;
return startingIndex;
}
EDIT: Even your computeIndex function could use randomness in picking the subsequent indices. Since you have a startingIndex, and another index, you could use that to offset your function so that you never pick a duplicate.
EDIT: If your array is very large, and the subset you need to pick is small, then rather than shuffle the entire array (maybe more expensive), you could use this method to pick the number of items you need. But if your array is small, or if the number of items you need to pick are almost the size of the array, then the godel9's solution is better.
You can use a mutable array and then remove them as the are selected, use something like random()%array.count to get a random index. If you don't want to modify the array then copy it with [array mutableCopy].

Edited UITableViewCell is being stored as a new cell

I have a table view that has a data source in sync with Core Data. However, I'm having a problem. Whenever I edit or delete a tableview cell, and I reload the view, I see a copy of the tableview cell that was there before it was edited. Here's some code to make it clearer.
When the view first loads, it tries to get all the "SOCommands" from "SOModule" which has a one-to-many relationship. Then, it converts it into "SOCommandTemp", so that I can work with them without altering the database.
_serverModuleCommands = [[NSMutableArray alloc]initWithArray:[self.serverModule.socommand allObjects]];
for(int i=0;i<[_serverModuleCommands count];i++)
{
SOCommandTemp* newTemp = [[SOCommandTemp alloc]init];
newTemp.commandName = ((SOCommand*)[_serverModuleCommands objectAtIndex:i]).commandName;
newTemp.sshCommand = ((SOCommand*)[_serverModuleCommands objectAtIndex:i]).sshCommand;
[_serverModuleCommands replaceObjectAtIndex:i withObject:newTemp];
}
Then, when I'm editing cells, I call methods such as these:
[_serverModuleCommands addObject:commandValues]; //commandValues is in the form of SOCommandTemp
[_serverModuleCommands replaceObjectAtIndex:_selectedCommandCell.row withObject:commandValues]; //_selectedCommandCell is an ivar that is cleared immediately after use
Then, when saving, I convert the array into SOCommand by doing this:
for(int j=0; j<[_serverModuleCommands count]; j++){
SOCommand* newCommand = [NSEntityDescription insertNewObjectForEntityForName:#"SOCommand" inManagedObjectContext:self.managedObjectContext];
newCommand.commandName = ((SOCommandTemp*)[_serverModuleCommands objectAtIndex:j]).commandName;
newCommand.sshCommand = ((SOCommandTemp*)[_serverModuleCommands objectAtIndex:j]).sshCommand;
newCommand.somodule = newModule;
}
However, before this is called, I want to make sure that I'm saving only one array item, since I added and editing one cell, so I do this:
NSLog(#"Going to Save: %#",[_serverModuleCommands description]);
And sure enough, I get only 1 array item. Then, I do save it, and exit the view controller. But when the first line:
_serverModuleCommands = [[NSMutableArray alloc]initWithArray:[self.serverModule.socommand allObjects]];
is called again, I'm getting two values in its description, one for the original and one for the edited.
Any help would be great!
~Carpetfizz
In your saving segment, you create a new SOCommand object no matter if it already exist.
Why not just use the actual objects (SOCommand) and edit them, this will not alter your DB information until you save the context.
It will save you some grieve swapping back and forth between your objects.
If you cannot edit in context, you should pass the existing item objectID to your "temp" objects and if it exist, fetch this object from DB and make the update to the existing item:
NSManagedObjectID* oID = ((SOCommandTemp*)[_serverModuleCommands objectAtIndex:j]).objectID;
if(oID) {
SOCommand* cmd = (SOCommand*)[context existingObjectWithID:oID error:nil];
if (cmd) { //no error fetching the object
//update `cmd` with your new values
}
}

Resources