Check if NSString exists in custom object in NSArray - objective-c

I have an NSArray with Store objects. Each Store object has two NSString objects; StoreID and Name.
I would like to check quickly if an ID exists in this NSArray with Store objects.
Example:
Store *s1 = [[Store alloc] init];
s1.name = #"Some Name";
s1.id = #"123ABC";
Store *s2 = [[Store alloc] init];
s2.name = #"Some Other Name";
s2.id = #"ABC123";
NSArray *array = [[NSArray alloc] initWithObjects:s1, s2, nil];
NSString *myIdOne = #"ABCDEF";
NSString *myIdTwo = #"123ABC";
BOOL myIdOneExists = ...?
BOOL myIdTwoExists = ...?
Its the ...? I need to figure out. I know I can do this using a for loop and break when found... but this seems to me like an nasty approach since the NSArray could contain thousands of objects,... theoretically.
So I would like to know about a better solution.

Here's the thing: No matter what solution you go with, it will more or less boil down to "loop over the array and return whether or not the object was found." There is no way to search an array more quickly than this unless very specific conditions are met (e.g. the array is already sorted by the value you're searching). You can use a predicate, you can use an enumerator, you can use fast enumeration or you can use a test block — under the hood, they all amount to "loop over the array and perform a test." That's just how arrays work.
If this is something you need to do often and performance is a problem with the naive solution, a sensible solution might be to cache your IDs in an NSSet. Sets are tuned for fast member detection, so you should be able to get your answer much more quickly than you could with an array.
My personal "loop-over-the-array" solution:
BOOL idExists = NSNotFound != [stores indexOfObjectPassingTest:^(Store *store, NSUInteger idx, BOOL *stop) {
return [store.id isEqualToString:#"whatever"];
}];
(Written in the browser, so, y'know, caveat compilor.)

Try this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == %#",#"id", myID];
NSArray *filteredArray = [array filteredArrayUsingPredicate:predicate];
if (filteredArray.count > 0)
Store *store = [filteredArray objectAtIndex:0];

Simplest solution, just use KVC:
NSArray *results = [array valueForKey:#"id"];
BOOL myIdOneExists = [results containsObject:myIdOne];
BOOL myIdTwoExists = [results containsObject:myIdTwo];

-(BOOL) id:(NSString*) theId existsInArray:(NSArray*) theArray {
for (Store* theStore in theArray) {
if ([theStore.id isEqualToString theId]) {
return YES;
}
}
return NO;
}
Another approach is to implement the isEqual method in Store to compare IDs only. Then construct a dummy Store object with the ID you're looking for and use indexOfObject or containsObject, referencing your dummy Store object.

Related

Modify Object while Iterating over a NSMutableArray

I was trying to modify an object from an array while iterating over it and couldn't find a nice way of doing it... This is what I've done, is there a simpler way of doing this? I've been googling for while but I couldn't find anything...
NSMutableArray *tempArray = [[NSMutableArray alloc]init];
NSArray *days = [restaurant.hours componentsSeparatedByString:#","];
for (NSString *day in days) {
NSString *dayWithOutSpace = [day stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[tempArray addObject:dayWithOutSpace];
}
days = [NSArray arrayWithArray:tempArray];
Thanks!
As suggested by others there might be better ways to accomplish the exact task in the question, but as a general pattern there is nothing wrong with your approach - build a new array.
However if you need to modify a mutable array, say because multiple objects reference it, there is nothing wrong with that either - that is why it is mutable after all! You just need to use standard iteration rather than enumeration - the latter is just the wrong tool for the job. E.g.:
NSMutableArray *anArray = ...
NSUInteger itemCount = [anArray count];
for(NSUInteger ix = 0; ix < itemCount; ix++)
{
// read from anArray[ix] and store into anArray[ix] as required
}
The way you do it is OK, since you are not modifying the array you are looping through.
Here is another way, a little less intuitive and probably not faster:
NSArray* days = [[[restaurant.hours componentsSeparatedByString:#" "] componentsJoinedByString:#""] componentsSeparatedByString:#","];
Considering your hours string is like: 2, 5, 6, 7 etc. you can use the string as #", " directly.
NSArray *days = [restaurant.hours componentsSeparatedByString:#", "];
Maybe it is better to eliminate all white spaces before separation.
NSString *daysWithOutSpaces = [restaurant.hours stringByReplacingOccurrencesOfString:#"[\\s\\n]" withString:#"" options:NSRegularExpressionSearch range:NSMakeRange(0, restaurant.hours.length)];
NSArray *days = [daysWithOutSpaces componentsSeparatedByString:#","];

Achieve NSArray merging more elegantly with enumerateObjectsUsingBlock:?

My challenge this week has been to come to terms with blocks in objective-c. There is something about the syntax that does my head in. Getting there.
I have the following code to achieve a merge of two arrays in a specific way (see comment in code below).
NSArray *keys = #[#"name", #"age"];
NSArray *data = #[
#[#"mark", #"36 years"],
#[#"matt", #"35 years"],
#[#"zoe", #"7 years"]
];
// desired outcome is
// # { #"name" : #[#"mark", #"matt", #"zoe"],
// #"age" : #[#"36 years", #"35 years", #"7 years"]
// }
NSMutableArray *mergedData = [[NSMutableArray alloc] initWithCapacity:keys.count];
for (NSString *key in keys) {
NSLog(#"key: %#", key);
NSInteger keyIndex = [keys indexOfObject:key];
NSMutableArray *dataItemsForKey = [[NSMutableArray alloc] initWithCapacity:data.count];
for (NSArray *row in data) {
// double check the array count for row equals the expected count for keys - otherwise we have a 'match up' issue
if (row.count == keys.count) {
[dataItemsForKey addObject:[row objectAtIndex:keyIndex]];
}
}
[mergedData addObject:[NSDictionary dictionaryWithObject:dataItemsForKey forKey:key]];
}
NSLog (#"mergedData: %#", mergedData);
While this code works fine, in the interest of my challenge and learning, I was wondering if there is a more 'elegant' (aka less code, easier to read) way to do this using enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) ??
I can't quite see a way to make it work, but in the interests of self-education, wonder if those more learned in blocks and arrays may have a more elegant solution.
The first issue that I notice is that you are asking for the index of the current object while enumerating the array. This is a waste of operations, because at every loop iteration you have to look over all array elements (potentially O(N)) to find where the object is.
You could instead do this:
for(NSUInteger i=0; i<keys.count; i++)
{
NSString* key= keys[i];
<Rest of the code>
}
Or just keep track of the index manually incrementing it:
NSUInteger i=0;
for (NSString *key in keys)
{
<Your code>
i++;
}
Or like you wanted, with enumerateObjectsUsingBlock:, which is IMO the most elegant way to do it in this case. Here is an example:
NSMutableDictionary* dict=[NSMutableDictionary new];
[keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
NSMutableArray* fields=[NSMutableArray new];
for(NSArray* array in data)
{
[fields addObject: array[idx]];
}
[dict setObject: fields forKey: obj];
}];
In the case you haven't understood how it works, here is a further explanation:
This way at every execution of the block you can know which is the current object (obj) and it's index (idx). stop is just used to stop enumerating the array, but you don't need it in this case (say that you want to stop the enumeration, you set *stop=YES). In my code I just took every element at the index idx of data, and build an array which is the value that I put into the dictionary, that has obj (what you called key in your code) as key. For any further doubt feel free to ask any clarification through a comment.
The first thing to say is your code does not produce the desired output. You get an array with two dictionaries each with one key.
One way to solve the problem is like this:
NSMutableDictionary* mergedData = [[NSMutableDictionary alloc] init];
[keys enumerateObjectsUsingBlock: ^(id key, NSUInteger keyIndex, BOOL *stop)
{
NSMutableArray* keyValues = [[NSMutableArray alloc] init];
for (NSArray* row in data)
{
[keyValues addObject: [row objectAtIndex: keyIndex]];
}
[mergedData setObject: keyValues forKey: key];
}];
The above will throw an exception if a row doesn't have enough objects in it. You could either check it beforehand or allow the program to crash, it's up to you.

Searching on the properties of objects in an array

I have an NSArray of objects with properties such as firstName, lastName, clientID etc. and i would like to perform a search on the array based on a search keyword. This keyword must be checked against the first name and the last name properties and return a subset of the original array that contains only those objects whose first/last name contain the search term. Is there any efficient/fast way to do this?
As a second thought, I think -filteredArrayUsingPredicate: might be better for you.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K = %#", #"firstName", #"Bob"];
NSArray *filteredArray = [array filteredArrayUsingPredicate:predicate];
This returns a sub-array of objects from the array that have the first name of "Bob".
I think your looking for -indexesOfObjectsPassingTest:
NSIndexSet *indexSet = [array indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
MyObject *myObject = (MyObject *)obj;
return [myObject.firstName isEqualToString:#"Bob"];
}];
This returns an index set of all the objects in the array with the first name of "Bob".
Another approach returning a new array containing only matching objects:
-(NSArray *)matchingClientsFromArray:(NSArray *)objects withFirstName:(NSString *)firstName andLastName:(NSString *)lastName{
NSMutableArray *objectArray = [NSMutableArray new];
for (Client *client in objectArray){
if ([client.firstName isEqualToString:firstName] &&
[client.lastName isEqualToString:lastName]) {
[objectArray addObject:client];
}
}
return [objectArray copy];
}

Better solution for this 2x fast-enumeration?

I'm looping through an array and comparing the objects tag property in this array with the objects in another array.
Here's my code:
NSArray *objectsArray = ...;
NSArray *anotherObjectArray = ...;
NSMutableArray *mutableArray = ...;
for (ObjectA *objectA in objectsArray) {
for (ObjectZ *objectZ in anotherObjectArray) {
if ([objectA.tag isEqualToString:objectZ.tag]) {
[mutableArray addObject:objectA];
}
}
}
Is there a better way to do this?
Please note the tag property is not an integer, so have to compare strings.
You can do this by iterating over each array once, rather than nesting:
NSMutableSet *tagSet = [NSMutableSet setWithCapacity:[anotherObjectArray count]];
for(ObjectZ *objectZ in antherObjectArray) {
[tagSet addObject:objectZ.tag];
}
NSMutableArray *output = [NSMutableArray mutableArray];
for(ObjectA *objectA in objectsArray) {
if([tagSet containsObject:objectA.tag]) {
[output addObject:objectA];
}
}
May be you can use [NSArray filteredArrayUsingPredicate:]; - http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html
But you may have to tweak for property tag yourself.
NSArray *objectsArray = [NSArray arrayWithObjects:#"Miguel", #"Ben", #"Adam", #"Melissa", nil];
NSArray *tagsArray = [NSArray arrayWithObjects:#"Miguel", #"Adam", nil];
NSPredicate *sPredicate = [NSPredicate predicateWithFormat:#"SELF IN %#", tagsArray];
NSArray *results = [objectsArray filteredArrayUsingPredicate:sPredicate];
NSLog(#"Matched %d", [results count]);
for (id a in results) {
NSLog(#"Object is %#", a);
}
Hope this helps
Well, the simplest change (as there can only be one match per objectA) then you could do a break after your [mutableArray addObject:objectA]. When a match occurs, that would reduce the inner loop by 50%.
More dramatically, if you're doing this a lot and the order of anotherObjectArray doesn't matter, would be to invert your anotherObjectArray data structure and use a dictionary, storing the objects by tag. Then you just iterate over objectA asking if its tag is in the dictionary of ObjectZs.
Thanks for all the answers. While I have accepted the NSMutableSet solution, I actually ended up going with the following, as it turned out it was a tiny bit faster:
NSMutableDictionary *tagDictionary = [NSMutableDictionary dictionaryWithCapacity:[anotherObjectArray count]];
for (ObjectZ *objectZ in anotherObjectArray) {
[tagDictionary setObject:objectZ.tag forKey:objectZ.tag];
}
for (ObjectA *objectA in objectsArray) {
if ([tagDictionary objectForKey:objectA.tag]) {
[direction addObject:objectA];
}
}

storing an array

I am trying to store the outcome of a string from the array hypothesis into myArray
am I doing anything wrong?
myArray = [NSString stringWithFormat:#"%#", hypothesis];
Update
NSMutableArray *myArray
NSMutableArray *urArray
// this is where my words are converted into strings. e.g. if I said "HELLO"
- (void) pocketsphinxDidReceiveHypothesis:(NSString *)hypothesis{
if (x==1){
// I am trying to store "HELLO" into myArray for comparison later.
myArray = [NSString stringWithFormat:#"%#", hypothesis];
// this would print "HELLO"
self.textview.text = [NSString stringWithFormat:#"You said %#",hypothesis];
}
else {
urArray = [NSString stringWithFormat:#"%#", hypothesis];
}
}
this is basically it. after that I will compare myArray == urArray in an ifelse statement.
Try...
myArray = [NSArray arrayWithObject:[NSString stringWithFormat:#"%#", hypothesis]];
Yes, you are doing several things wrong.
you have a method -pocketsphinxDidReceiveHypothesis which you have defined as taking a single argument of type NSString*, the argument is called hypothesis.
In your question you say
I am trying to store the outcome of a
string from the array hypothesis
Which suggests that you know hypothesis is an array, but we can't verify this as you don't show that piece of code.
hypothesis must be a String or an Array. It cannot be both, you cannot be unsure.
If hypothesis is a String, ie. if you do something like:
NSString *input = #"Hello World";
[foo pocketsphinxDidReceiveHypothesis: input];
Then these two lines make no sense:-
NSMutableArray *myArray = [NSString stringWithFormat:#"%#", hypothesis];
NSMutableArray *urArray = [NSString stringWithFormat:#"%#", hypothesis];
Because, well look..
NSString *hypothesis = #"Hello World";
newString = [NSString stringWithFormat:#"%#", hypothesis];
This code does nothing, hypothesis and newString are identical, as you haven't even provided any arguments for the format. [NSString stringWithFormat:#"%#", hypothesis] is no different to just using hypothesis. So what you actually have is
NSString *hypothesis;
NSMutableArray *myArray = hypothesis;
NSMutableArray *urArray = hypothesis;
This is broken, you cant assign a String to an Array (well, i'm fairly certain you don't mean to anyhow). To use an Array you must use of of the several Array creation methods to give you, well, an Array. A String isn't an Array and can't pretend to be one.
Now, apologies if hypothesis isn't a String but is infact an Array (it would have helped if you had shown that piece of code). If it is an Array, ie. you do something like this..
NSArray *input = [NSArray arrayWithObject:#"Hello World"];
[foo pocketsphinxDidReceiveHypothesis: input];
Then your method definition is broken because you have defined it as taking a String argument
- (void)pocketsphinxDidReceiveHypothesis:(NSString *)hypothesis
When you need it to take an Array argument
- (void)pocketsphinxDidReceiveHypothesis:(NSArray *)hypothesis
Then the following two lines make no sense:-
NSMutableArray *myArray = [NSString stringWithFormat:#"%#", hypothesis];
NSMutableArray *urArray = [NSString stringWithFormat:#"%#", hypothesis];
[NSString stringWithFormat:#"%#", hypothesis] is exactly the same as [hypothesis description], which returns a String, so what you are effectively doing is:
NSArray *hypothesis;
NSString *hypothesisDescription = [hypothesis description];
NSMutableArray *myArray = hypothesisDescription;
NSMutableArray *urArray = hypothesisDescription;
So again, assigning a String to an Array variable - almost certainly not going to do what you want or need.
If hypothesis is a String and you meant to add it to an array, you must first make sure the array is initialized (ie it has to be a valid array). Something like NSMutableArray *myArray = [[NSMutableArray alloc] init] will do the trick. Then you can use one of NSMutableArray's methods to store your String, eg. [myArray addObject:hypothesis].
If hypothesis is an Array would you like to store the it in myArray as is or would you first like to transform it into a String?
Then you go on to say:-
I will compare myArray == urArray in
an ifelse statement
Given the confusion surrounding the preceding code it is not clear why you want to do this or what you hope to achieve. You have nowhere included a description of what this code is supposed to do. It is not clear whether you are aware that two Arrays that contain identical objects are not == (as the are two different arrays and have their own identity), but two pointers to the same Array are ==, eg:
NSArray *aSimpleArray = [NSArray arrayWithObjects:#"one", #"two", #"three", nil];
NSArray *foo = aSimpleArray;
BOOL result = (aSimpleArray==foo); // These are equal, result is true
NSArray *anotherSimpleArray = [NSArray arrayWithObjects:#"one", #"two", #"three", nil];
BOOL result = (aSimpleArray==anotherSimpleArray); // These are not equal, result is false
So unless you want to test if you have two pointers to the same Array (and not just two arrays with the same objects) == probably doesn't do what you want. Note there are methods to help compare Arrays, such as -isEqualToArray, so that:
NSArray *aSimpleArray1 = [NSArray arrayWithObjects:#"one", #"two", #"three", nil];
NSArray *aSimpleArray2 = [NSArray arrayWithObjects:#"one", #"two", #"three", nil];
BOOL areEqual1 = aSimpleArray1==aSimpleArray2; // FALSE
BOO areEqual2 = [aSimpleArray1 isEqualToArray:aSimpleArray2]; // TRUE
In general you have to be familiar with the interfaces of all the objects you are going to use
http://developer.apple.com/library/ios/#documentation/cocoa/reference/foundation/Classes/NSArray_Class/NSArray.html
The Apple documentation is excellent but i recommend a good book. There are many posts about book recommendations on SO so i leave that to you. Otherwise Apple provides hundreds of simple sample projects that you should study.
Are you trying to add hypothesis to one of your arrays (i.e. your arrays are arrays of multiple hypotheses)? If so then you can use addObject:
[myArray addObject:hypothesis];
If the hypothesis string is actually supposed to represent the entire array then you'll need to explain how the elements are encoded into this string before we can help you.
I am trying to store the same text
that hypothesis is holding into
myArray.
In that case:
NSArray *myArray = nil;
…
myArray = [NSArray arrayWithObject:hypthesis];
But mainly I would suggest that you go through some good Objective-C tutorial, since you seem to be confusing many basic ideas and it’s hard to get somewhere without knowing the basics.