NSMutableArray of Objects - iphone

First off I am very new to Objective C and iPhone programming. Now that that is out of the way. I have read through most of the Apple documentation on this and some third party manuals.
I guess I just want to know if I'm going about this the correct way ...
- (NSMutableArray *)makeModel {
NSString *api = #"http://www.mycoolnewssite.com/api/v1";
NSArray *namesArray = [NSArray arrayWithObjects:#"News", #"Sports", #"Entertainment", #"Business", #"Features", nil];
NSArray *urlsArray = [NSArray arrayWithObjects:
[NSString stringWithFormat:#"%#/news/news/25/stories.json", api],
[NSString stringWithFormat:#"%#/news/sports/25/stories.json", api],
[NSString stringWithFormat:#"%#/news/entertainment/25/stories.json", api],
[NSString stringWithFormat:#"%#/news/business/25/stories.json", api],
[NSString stringWithFormat:#"%#/news/features/25/stories.json", api], nil];
NSMutableArray *result = [NSMutableArray array];
for (int i = 0; i < [namesArray count]; i++) {
NSMutableDictionary *objectDict = [NSMutableDictionary dictionary];
NSString *name = (NSString *)[namesArray objectAtIndex:i];
NSString *url = (NSString *)[urlsArray objectAtIndex:i];
[objectDict setObject:name forKey:#"NAME"];
[objectDict setObject:url forKey:#"URL"];
[objectDict setObject:#"NO" forKey:#"HASSTORIES"];
[result addObject:objectDict];
}
return result;
}
The output of the result is ...
(
{
HASSTORIES = NO;
NAME = News;
URL = "http://www.mycoolnewssite.com/api/v1/news/news/25/stories.json";
},
{
HASSTORIES = NO;
NAME = Sports;
URL = "http://www.mycoolnewssite.com/api/v1/news/sports/25/stories.json";
},
{
HASSTORIES = NO;
NAME = Entertainment;
URL = "http://www.mycoolnewssite.com/api/v1/news/entertainment/25/stories.json";
},
{
HASSTORIES = NO;
NAME = Business;
URL = "http://www.mycoolnewssite.com/api/v1/news/business/25/stories.json";
},
{
HASSTORIES = NO;
NAME = Features;
URL = "http://www.mycoolnewssite.com/api/v1/news/features/25/stories.json";
}
)
Any insight would be appreciated ;-)

It looks fine. There can be some minor improvements if you care.
1.
[NSString stringWithFormat:#"%#/news/news/25/stories.json", api]
can be replaced by
[api stringByAppendingString:#"/news/news/25/stories.json"]
if there's no chance the api appears in the middle or accepts other arguments.
2.
NSString *name = (NSString *)[namesArray objectAtIndex:i];
NSString *url = (NSString *)[urlsArray objectAtIndex:i];
The explicit cast is unnecessary. An id can be implicitly casted to and from other ObjC objects.
3.
You could use a convenient method -dictionaryWithObjectsAndKeys: to construct the dictionary in one-shot, so you don't need a temperary dictionary:
[result addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
name, #"NAME",
url, #"URL",
#"NO", #"HASSTORIES", nil]];
4. (optional)
This transform is not useful if the function is not a hot spot.
Since the arrays are only used locally, it's more efficient to use a C array.
static const int arraySize = 5;
NSString* namesCArray[] = {#"News", #"Sports", #"Entertainment", #"Business", #"Features"};
NSString* urlsCArray[arraySize];
urlsArray[0] = [api stringByAppendingString:#"/news/news/25/stories.json"];
...
for (int i = 0; i < arraySize; ++ i) {
...
NSString* name = namesCArray[i];
NSString* url = urlsCArray[i];
...
}
this removes the repeated -count and -objectAtIndex: calls which is very slow compared with direct element access.
5. (optional)
This transform is not useful if the array is short.
You could use fast-enumeration to loop over an ObjC container:
int i = 0;
for (NSString* name in namesArray) {
NSString* url = [urlsArray objectAtIndex:i];
...
++ i;
}
6.
Usually we use [NSNumber numberWithBool:NO] to represent a boxed true/false value, instead of a string #"NO". NSNumber is also used a lot whenever a primitive number (int, float, etc.) cannot be used (e.g. to be stored in an NSArray). I don't know if your API explicitly requires a string NO, so it may not unsuitable for you.

Related

How to load values from a plist?

I'm loading the data from a plist and am trying to find out how to access data from each item. In the following code I'd like to be able to extract the value of text and whether it's checked = 0 or checked = 1
I've tried this:
NSString *dataArray1 = [[dataArray objectAtIndex:1] objectAtIndex:2];
but was wondering if that is the best approach
thanks for any help.
// load data from a plist file inside our app bundle
NSString *path = [[NSBundle mainBundle] pathForResource:#"Providers" ofType:#"plist"];
dataArray = [NSMutableArray arrayWithContentsOfFile:path];
NSLog(#"data array from offers %#", dataArray);
Here's the output:
2013-01-21 15:13:34.599 data array from offers (
{
checked = 1;
text = Provider1;
},
{
checked = 1;
text = Provider2;
},
{
checked = 1;
text = Provider3;
},
{
checked = 1;
text = Provider4;
}
)
So I'd like to be able to find out what the checked value of each item. That Provider4 is set to 1 and Provider3 is 0 etc... then use that to pass in parameters to a string.
NSString *path = [[NSBundle mainBundle] pathForResource:#"Providers" ofType:#"plist"];
dataArray = [NSMutableArray arrayWithContentsOfFile:path];
for (NSDictionary *dictionary in dataArray)
{
NSString *text = [dictionary valueForKey:#"text"];
NSNumber *checked = [dictionary valueForKey:#"checked"];
NSLog(#"%# checked value is: %#", text, checked);
}
something like this, i guess:
for (id dict in dataArray)
int checked = [[(NSDictionary *)dict objectForKey:#"checked"] intValue];

Sort array with a count value

I have an array which contains some strings. For each character of a string an integer value is assigned. For example a=2,b=5,c=6 ,o=1,k=3 etc
The final value in the a string is the sum of the character's value. So that for an example string "BOOK" the string will be stored as "BOOK (7)". Similarly every string will have a final integer value. I would like to sort these array with these final integer values stored in the string which is present in each array index. The array contains more than 200,000 words. So the sorting process should be pretty fast. Is there any method for it?
A brutal quick example could be, if your strings structure is always the same, like "Book (7)" you can operate on the string by finding the number between the "()" and then you can use a dictionary to store temporally the objects:
NSMutableArray *arr=[NSMutableArray arrayWithObjects:#"Book (99)",#"Pencil (66)",#"Trash (04)", nil];
NSLog(#"%#",arr);
NSMutableDictionary *dict=[NSMutableDictionary dictionary];
//Find the numbers and store each element in the dictionary
for (int i =0;i<arr.count;i++) {
NSString *s=[arr objectAtIndex:i];
int start=[s rangeOfString:#"("].location;
NSString *sub1=[s substringFromIndex:start];
NSString *temp1=[sub1 stringByReplacingOccurrencesOfString:#"(" withString:#""];
NSString *newIndex=[temp1 stringByReplacingOccurrencesOfString:#")" withString:#""];
//NSLog(#"%d",[newIndex intValue]);
[dict setValue:s forKey:newIndex];
}
//Sorting the keys and create the new array
NSArray *sortedValues = [[dict allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSMutableArray *newArray=[[NSMutableArray alloc]init];
for(NSString *valor in sortedValues){
[newArray addObject:[dict valueForKey:valor]];
}
NSLog(#"%#",newArray);
This prints:
(
"Book (99)",
"Pencil (66)",
"Trash (04)"
)
(
"Trash (04)",
"Pencil (66)",
"Book (99)"
)
as i understand, you want to sort an array which contains string formated in the following
a=3
and you want to sort according to the number while ignoring the character.
in this case the following code will work with you
-(NSArray *)Sort:(NSArray*)myArray
{
return [myArray sortedArrayUsingComparator:(NSComparator)^(id obj1, id obj2)
{
NSString *first = [[obj1 componentsSeparatedByString:#"="] objectAtIndex:1];
NSString *second = [[obj2 componentsSeparatedByString:#"="] objectAtIndex:1];
return [first caseInsensitiveCompare:second];
}];
}
How to use it:
NSArray *arr= [[NSArray alloc] initWithObjects:#"a=3",#"b=1",#"c=4",#"f=2", nil];
NSArray *sorted = [self Sort:arr];
for (NSString* str in sorted)
{
NSLog(#"%#",str);
}
Output
b=1
f=2
a=3
c=4
Try this methods
+(NSString*)strTotalCount:(NSString*)str
{
NSInteger totalCount = 0;
// initial your character-count directory
NSDictionary* characterDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:2], [NSString stringWithUTF8String:"a"],
[NSNumber numberWithInt:5], [NSString stringWithUTF8String:"b"],
[NSNumber numberWithInt:6], [NSString stringWithUTF8String:"c"],
[NSNumber numberWithInt:1], [NSString stringWithUTF8String:"o"],
[NSNumber numberWithInt:3], [NSString stringWithUTF8String:"k"],
nil];
NSString* tempString = str;
for (NSInteger i =0; i<tempString.length; i++) {
NSString* character = [tempString substringWithRange:NSMakeRange(i, 1)];
character = [character lowercaseString];
NSNumber* count = [characterDictionary objectForKey:character];
totalCount += [count integerValue];
};
return [NSString stringWithFormat:#"%#(%d)",str,totalCount];
}
The test sentence:
NSLog(#"%#", [ViewController strTotalCount:#"BOOK"]);
will output " BOOK(10) "
You may change the ViewController to you own class name;
First - create a custom object to save your values. Don't put the value inside the string.
Sorting is not your base problem. The problem is that you are saving values into a string from where they are difficult to extract.
#interface StringWithValue
#property (nonatomic, copy, readwrite) NSString* text;
#property (nonatomic, assign, readwrite) NSUInteger value;
- (id)initWithText:(NSString*)text;
- (NSComparisonResult)compare:(StringWithValue*)anotherString;
#end
#implementation StringWithValue
#synthesize text = _text;
#synthesize value = _value;
- (id)initWithText:(NSString*)text {
self = [super init];
if (!self) {
return nil;
}
self.text = text;
self.value = [self calculateValueForText:text];
return self;
}
- (NSComparisonResult)compare:(StringWithValue*)anotherString {
if (self.value anotherString.value) {
return NSOrderedDescending;
}
else {
return NSOrderedSame;
}
}
- (NSString*)description {
return [NSString stringWithFormat:#"%# (%u)", self.text, self.value];
}
#end
Sorting the array then would be a simple use of sortUsingSelector:.
Note this will beat all other answers in performance as there is no need to parse the value with every comparison.

Build string with variable number of keys and formats

I have an NSDictionary object that contains my data. I am passing in an array of key names and a display format for a string representation of my data.
[self displayMyDataWithTheseKeys:myKeyArray inThisFormat:myFormat];
where, for example,
myKeyArray = [NSArray arrayWithObjects: #"Key1", #"Key2", nil];
myFormat = [NSString stringWithString: #"%# to the %# degree"];
However, myFormat may change and the number of keys in the array may vary as well.
If the number of elements in the array was always 2, this would be trivial. However, how can I handle a variable number of elements?
There isn't really a built-in method for this, but it's relatively easy to parse format strings with NSScanner. Here's a simple example, it only handles %# format specifiers, but as all elements in an NSArray are objects and not primitive types anyway, it shouldn't matter:
NSArray *myKeyArray = [NSArray arrayWithObjects: #"Key1", #"Key2", nil];
NSString *myFormat = [NSString stringWithString: #"%# to the %# degree"];
NSMutableString *result = [NSMutableString string];
NSScanner *scanner = [NSScanner scannerWithString:myFormat];
[scanner setCharactersToBeSkipped:[NSCharacterSet illegalCharacterSet]];
int i = 0;
while (![scanner isAtEnd]) {
BOOL scanned = [scanner scanString:#"%#" intoString:NULL];
if (scanned) {
if (i < [myKeyArray count]) {
[result appendString:[myKeyArray objectAtIndex:i]];
i++;
} else {
//Handle error: Number of format specifiers doesn't
//match number of keys in array...
}
}
NSString *chunk = nil;
[scanner scanUpToString:#"%#" intoString:&chunk];
if (chunk) {
[result appendString:chunk];
}
}
Use: stringByAppendingString
Here's an example on how to use it:
NSString *someString = #"String";
someString = [someString stringByAppendingString:[NSString stringWithFormat:#"%#",variable1]];
someString = [someString stringByAppendingString:[NSString stringWithFormat:#"%#",variable2]];
someString = [someString stringByAppendingString:[NSString stringWithFormat:#"%#",variable3]];
...and so on
If you have an array of keys which you want to put in a string:
NSString *string = #"And the keys are:\n";
for(int i = 0; i < [array count]; i++)
{
NSString *thisKey = (NSString *)[array objectAtIndex:i];
string = [string stringByAppendingString:[NSString stringWithFormat:#"Key number %d is %#",i,thisKey]];
}

Creating a long NSString causing memory issues

My code below is causing my app to quit i.e. get black screen and then see in debugger console: Program received signal: “0”.
Basically it is causing problem when my orderArray has count of 2000 or more. I am using iPhone 3GS with iOS 4.2
Question: Is there a more efficient and less memory consuming way to create my long outStr?
NSString *outStr = #"";
for (int i = 0; i < count; i++) {
NSDictionary *dict = [[ARAppDelegate sharedAppDelegate].orderArray objectAtIndex:i];
outStr = [outStr stringByAppendingFormat:#"%#,%#,%#,%#\n",
[dict valueForKey:#"CODE"],
[dict valueForKey:#"QTY"],
[[ARAppDelegate sharedAppDelegate].descDict valueForKey:[dict valueForKey:#"CODE"]],
[[ARAppDelegate sharedAppDelegate].priceDict valueForKey:[dict valueForKey:#"CODE"]]];
}
Update: Thanks to very kind people who helped, below is my modified code:
NSArray *orderA = [ARAppDelegate sharedAppDelegate].orderArray;
NSDictionary *descD = [ARAppDelegate sharedAppDelegate].descDict;
NSDictionary *priceD = [ARAppDelegate sharedAppDelegate].priceDict;
NSMutableString *outStr = [[[NSMutableString alloc] init] autorelease];
for (int i = 0; i < [orderA count]; i++) {
NSDictionary *dict = [orderA objectAtIndex:i];
NSString *code = [dict valueForKey:#"CODE"];
[outStr appendFormat:#"%#,%#,%#,%#\n",
code,
[dict valueForKey:#"QTY"],
[descD valueForKey:code],
[priceD valueForKey:code]];
}
[self emailTxtFile:[NSString stringWithString:outStr]];
// This reaches end of method
The problem is that in every iteration a new string object is formed. This consumes a lot of memory. One solution could be to use a local autoreleasepool, but that's rather complicated here.
You should use an NSMutableString, like:
NSMutableString *outStr = [[[NSMutableString alloc] init] autorelease];
for (int i = 0; i < count; i++) {
NSDictionary *dict = [[ARAppDelegate sharedAppDelegate].orderArray objectAtIndex:i];
[outStr appendFormat:#"%#,%#,%#,%#\n",
[dict valueForKey:#"CODE"],
[dict valueForKey:#"QTY"],
[[ARAppDelegate sharedAppDelegate].descDict valueForKey:[dict valueForKey:#"CODE"]],
[[ARAppDelegate sharedAppDelegate].priceDict valueForKey:[dict valueForKey:#"CODE"]]];
}
Then you can use outStr, just as if it was an NSString. As Tom points out in the comments, you could turn the NSMutableString into an NSString when you're finished, using:
NSString *result = [NSString stringWithString:outStr];
[outStr release]; // <-- add this line and remove the autorelease
// from the outStr alloc/init line
making your code re-usable and easier to maintain.

Finding the maximum elements in an NSArray (or NSMutableArray)

I am having a bit of trouble navigating around an NSArray.
My array:
Element[0] = "ElementA"
Element[1] = "ElementA"
Element[2] = "ElementA"
Element[3] = "ElementA"
Element[4] = "ElementB"
Element[5] = "ElementC"
Are there any methods in Objective-C that will help me find the "median" element? In this case, the "median" would be "ElementA", or the value that occurs the maximum number of times.
In C# this would be a single call, but I can't find an equivalent in Objective-C.
Many thanks,
Brett
Here's how I'd do it:
NSArray * elements = ...; //your array of elements:
NSCountedSet * counts = [NSCountedSet setWithArray:elements]:
id modeObject = nil;
NSUInteger modeCount = 0;
for (id element in counts) {
if ([counts countForObject:element] > modeCount) {
modeCount = [counts countForObject:element];
modeObject = element;
}
}
NSLog(#"element with highest frequency: %#", modeObject);
An NSCountedSet is an NSMutableSet that also remembers how many times its elements have been added to the array.
Wrote this just for you :)
- (NSString *) findModeString: (NSArray *) array {
NSMutableDictionary *stats = [[NSMutableDictionary alloc] init];
for(NSString *str in array) {
if(![stats objectForKey:str]) {
[stats setObject: [NSNumber numberWithInt:1] forKey:str];
} else {
[stats setObject: [NSNumber numberWithInt:[[stats objectForKey:str] intValue] + 1] forKey:str];
}
}
NSInteger maxOccurrences = 0;
NSString *max;
for(NSString *key in stats) {
if([[stats objectForKey:key] intValue] > maxOccurrences) {
max = key;
maxOccurrences = [[stats objectForKey:key] intValue];
}
}
[stats release];
return max;
}
EDIT: Although my solution works, you should upvote/accept #Dave DeLong's answer, it is much much better.
Couldn't you just use:
[myarray length] /2