Related
I have to 2 NSMutableArrays containing NSMutableDictionarys. I need to verify the dictionaries in one of the arrays exists in the other array. How can I do this?
I'm not sure if this is the best approach but it is an easy one. I created a method to verify if a NSDictionary is present inside a NSArray. Inside this function, I convert the NSDictionarys to NSStrings and compare them.
- (BOOL)verifyIfDictionary:(NSDictionary *)dict existsInsideArray:(NSArray *)array
{
BOOL result = NO;
NSString *dictStr = [NSString stringWithFormat:#"%#",dict]; // convert dictionary to string
for (NSDictionary *d in arrayOfDictionaries)
{
NSString *dStr = [NSString stringWithFormat:#"%#",dict]; // same conversion as above conversion
// now, I just have to compare the resulting strings
if ([dictStr isEqualToString:dStr])
result = YES;
}
return result;
}
Now you just have to iterate through one of your NSArrays and use this method, like this:
NSArray *arrayOfDictionaries1;
NSArray *arrayOfDictionaries2;
// initialize them, fill with data, do your processing.. etc, etc...
// then, when you want to verify:
for (NSDictionary *dict in arrayOfDictionaries1)
{
if ([self verifyIfDictionary:dict existsInsideArray:arrayOfDictionaries2])
{
NSLog(#"exists!");
}
}
To check if array1 contains all of the items in array2:
NSMutableArray *mutableArrayToCheck = [array2 mutableCopy];
[mutableArrayToCheck removeObjectsInArray:array1];
if (array2.count > 0 && mutableArrayToCheck.count == 0) {
// array2 contains all the items in array1
}
Ignore the type of the contents of the arrays for a second, just think of numbers. We have 2 arrays:
array1 = [ 1, 2, 3 ]
array2 = [ 1, 3 ]
If we remove the items in array1 from array2, and the result is an empty array, then we know that all of the items in array2 were also in array1.
removeObjectsInArray: does this for us by searching through the array and comparing each item. We just need to check the result.
This works no matter what the contents of the array are, so long as they implement hash and isEqual:.
I am not sure how to go about this. I have an NSMutableArray (addList) which holds all the items to be added to my datasource NSMutableArray.
I now want to check if the object to be added from the addList array already exists in the datasource array. If it does not exist add the item, if exists ignore.
Both the objects have a string variable called iName which i want to compare.
Here is my code snippet
-(void)doneClicked{
for (Item *item in addList){
/*
Here i want to loop through the datasource array
*/
for(Item *existingItem in appDelegate.list){
if([existingItem.iName isEqualToString:item.iName]){
// Do not add
}
else{
[appDelegate insertItem:item];
}
}
}
But i find the item to be added even if it exists.
What am i doing wrong ?
There is a very useful method for this in NSArray i.e. containsObject.
NSArray *array;
array = [NSArray arrayWithObjects: #"Nicola", #"Margherita", #"Luciano", #"Silvia", nil];
if ([array containsObject: #"Nicola"]) // YES
{
// Do something
}
I found a solution, may not be the most efficient of all, but atleast works
NSMutableArray *add=[[NSMutableArray alloc]init];
for (Item *item in addList){
if ([appDelegate.list containsObject:item])
{}
else
[add addObject:item];
}
Then I iterate over the add array and insert items.
Use NSPredicate.
NSArray *list = [[appDelegate.list copy] autorelease];
for (Item *item in addList) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"iName MATCHES %#", item.iName];
NSArray *filteredArray = [list filteredArrayUsingPredicate:predicate];
if ([filteredArray count] > 0) [appDelegate insertItem:item];
}
Did you try indexOfObject:?
-(void)doneClicked{
for (Item *item in addList){
if([appDelegate.list indexOfObject:item] == NSNotFound){
[appDelegate insertItem:item];
}
}
UPDATE: You have a logical mistake, not mistake in code. assume the first array is ['a', 'b', 'c'], and the second is ['a', 'x', 'y', 'z']. When you iterate with 'a' through the second array it won't add 'a' to second array in the first iteration (compare 'a' with 'a') but will add during the second (compare 'a' with 'x'). That is why you should implement isEqual: method (see below) in your 'Item' object and use the code above.
- (BOOL)isEqual:(id)anObject {
if ([anObject isKindOfClass:[Item class]])
return ([self.iName isEqualToString:((Item *)anObject).iName]);
else
return NO;
}
Have a look at NSSet. You can add objects and the object will only be added if the object is unique. You can create a NSSet from an NSArray or vise versa.
You can override isEquals and hash on the object so that it returns a YES / NO based on the comparison of the iName property.
Once you have that you can use...
- (void)removeObjectsInArray:(NSArray *)otherArray
To clean the list before adding all the remaining objects.
NR4TR said correctly but i think one break statement is sufficient
if([existingItem.iName isEqualToString:item.iName]){
// Do not add
break;
}
Convert Lowercase and Trim whitespace and then check..
[string lowercaseString];
and
NSString *trim = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
You compare the addList's first object and appDelegate.list's first object, if they are not equal, you insert the addList's object. The logic is wrong, you should compare one addList's object with every appDelegate.list's object.
Given an NSArray of NSDictionary objects (containing similar objects and keys) is it possible to write perform a map to an array of specified key? For example, in Ruby it can be done with:
array.map(&:name)
It only saves a couple lines, but I use a category on NSArray. You need to ensure your block never returns nil, but other than that it's a time saver for cases where -[NSArray valueForKey:] won't work.
#interface NSArray (Map)
- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block;
#end
#implementation NSArray (Map)
- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block {
NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[result addObject:block(obj, idx)];
}];
return result;
}
#end
Usage is much like -[NSArray enumerateObjectsWithBlock:]:
NSArray *people = #[
#{ #"name": #"Bob", #"city": #"Boston" },
#{ #"name": #"Rob", #"city": #"Cambridge" },
#{ #"name": #"Robert", #"city": #"Somerville" }
];
// per the original question
NSArray *names = [people mapObjectsUsingBlock:^(id obj, NSUInteger idx) {
return obj[#"name"];
}];
// (Bob, Rob, Robert)
// you can do just about anything in a block
NSArray *fancyNames = [people mapObjectsUsingBlock:^(id obj, NSUInteger idx) {
return [NSString stringWithFormat:#"%# of %#", obj[#"name"], obj[#"city"]];
}];
// (Bob of Boston, Rob of Cambridge, Robert of Somerville)
I've no idea what that bit of Ruby does but I think you are looking for NSArray's implementation of -valueForKey:. This sends -valueForKey: to every element of the array and returns an array of the results. If the elements in the receiving array are NSDictionaries, -valueForKey: is nearly the same as -objectForKey:. It will work as long as the key doesn't start with an #
To summarize all other answers:
Ruby (as in the question):
array.map{|o| o.name}
Obj-C (with valueForKey):
[array valueForKey:#"name"];
Obj-C (with valueForKeyPath, see KVC Collection Operators):
[array valueForKeyPath:#"[collect].name"];
Obj-C (with enumerateObjectsUsingBlock):
NSMutableArray *newArray = [NSMutableArray array];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[newArray addObject:[obj name]];
}];
Swift (with map, see closures)
array.map { $0.name }
And, there are a couple of libraries that allow you to handle arrays in a more functional way. CocoaPods is recommended to install other libraries.
Update: If you're using Swift, see map.
BlocksKit is an option:
NSArray *new = [stringArray bk_map:^id(NSString *obj) {
return [obj stringByAppendingString:#".png"];
}];
Underscore is another option. There is a map function, here is an example from the website:
NSArray *tweets = Underscore.array(results)
// Let's make sure that we only operate on NSDictionaries, you never
// know with these APIs ;-)
.filter(Underscore.isDictionary)
// Remove all tweets that are in English
.reject(^BOOL (NSDictionary *tweet) {
return [tweet[#"iso_language_code"] isEqualToString:#"en"];
})
// Create a simple string representation for every tweet
.map(^NSString *(NSDictionary *tweet) {
NSString *name = tweet[#"from_user_name"];
NSString *text = tweet[#"text"];
return [NSString stringWithFormat:#"%#: %#", name, text];
})
.unwrap;
I think valueForKeyPath is a good choice.
Sit below has very cool examples. Hopes it is helpful.
http://kickingbear.com/blog/archives/9
Some example:
NSArray *names = [allEmployees valueForKeyPath: #"[collect].{daysOff<10}.name"];
NSArray *albumCovers = [records valueForKeyPath:#"[collect].{artist like 'Bon Iver'}.<NSUnarchiveFromDataTransformerName>.albumCoverImageData"];
I'm no Ruby expert so I'm not 100% confident I'm answering correctly, but based on the interpretation that 'map' does something to everything in the array and produces a new array with the results, I think what you probably want is something like:
NSMutableArray *replacementArray = [NSMutableArray array];
[existingArray enumerateObjectsUsingBlock:
^(NSDictionary *dictionary, NSUInteger idx, BOOL *stop)
{
NewObjectType *newObject = [something created from 'dictionary' somehow];
[replacementArray addObject:newObject];
}
];
So you're using the new support for 'blocks' (which are closures in more general parlance) in OS X 10.6/iOS 4.0 to perform the stuff in the block on everything in the array. You're choosing to do some operation and then add the result to a separate array.
If you're looking to support 10.5 or iOS 3.x, you probably want to look into putting the relevant code into the object and using makeObjectsPerformSelector: or, at worst, doing a manual iteration of the array using for(NSDictionary *dictionary in existingArray).
#implementation NSArray (BlockRockinBeats)
- (NSArray*)mappedWithBlock:(id (^)(id obj, NSUInteger idx))block {
NSMutableArray* result = [NSMutableArray arrayWithCapacity:self.count];
[self enumerateObjectsUsingBlock:^(id currentObject, NSUInteger index, BOOL *stop) {
id mappedCurrentObject = block(currentObject, index);
if (mappedCurrentObject)
{
[result addObject:mappedCurrentObject];
}
}];
return result;
}
#end
A slight improvement upon a couple of the answers posted.
Checks for nil—you can use nil to remove objects as you're mapping
Method name better reflects that the method doesn't modify the array it's called on
This is more a style thing but I've IMO improved the argument names of the block
Dot syntax for count
For Objective-C, I would add the ObjectiveSugar library to this list of answers: https://github.com/supermarin/ObjectiveSugar
Plus, its tagline is "ObjectiveC additions for humans. Ruby style." which should suit OP well ;-)
My most common use-case is mapping an dictionary returned by a server call to an array of simpler objects e.g. getting an NSArray of NSString IDs from your NSDictionary posts:
NSArray *postIds = [results map:^NSString*(NSDictionary* post) {
return [post objectForKey:#"post_id"];
}];
For Objective-C, I would add the Higher-Order-Functions to this list of answers: https://github.com/fanpyi/Higher-Order-Functions;
There is a JSON array studentJSONList like this:
[
{"number":"100366","name":"Alice","age":14,"score":80,"gender":"female"},
{"number":"100368","name":"Scarlett","age":15,"score":90,"gender":"female"},
{"number":"100370","name":"Morgan","age":16,"score":69.5,"gender":"male"},
{"number":"100359","name":"Taylor","age":14,"score":86,"gender":"female"},
{"number":"100381","name":"John","age":17,"score":72,"gender":"male"}
]
//studentJSONList map to NSArray<Student *>
NSArray *students = [studentJSONList map:^id(id obj) {
return [[Student alloc]initWithDictionary:obj];
}];
// use reduce to get average score
NSNumber *sum = [students reduce:#0 combine:^id(id accumulator, id item) {
Student *std = (Student *)item;
return #([accumulator floatValue] + std.score);
}];
float averageScore = sum.floatValue/students.count;
// use filter to find all student of score greater than 70
NSArray *greaterthan = [students filter:^BOOL(id obj) {
Student *std = (Student *)obj;
return std.score > 70;
}];
//use contains check students whether contain the student named 'Alice'
BOOL contains = [students contains:^BOOL(id obj) {
Student *std = (Student *)obj;
return [std.name isEqual:#"Alice"];
}];
There is a special key-path operator for this: #unionOfObjects. Probably it replaced [collect] from previous versions.
Imagine a Transaction class with payee property:
NSArray *payees = [self.transactions valueForKeyPath:#"#unionOfObjects.payee"];
Apple docs on Array Operators in Key-Value coding.
Swift introduces a new map function.
Here is an example from the documentation:
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
let strings = numbers.map {
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
return output
}
// strings is inferred to be of type String[]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]
The map function takes a closure which returns a value of any type and maps the existing values in the array to instances of this new type.
I am trying to detect if an array isn't empty in order to be able to do a certain call.
I tried using if (![array ==nil]) however that doesn't compile.
I'm sure there is a really easy explanation to this.
Update
If array is empty I want to do this:
array = [[NSMutableArray alloc]init];
If it has an object I want to do this:
array = [[userDefaults arrayForKey:#"MyFavorites"] mutableCopy];
If you declared it but did not assign anything to it at all:
NSMutableArray *array;
Then the array will be nil, meaning it isn't there at all so you can't say if it's empty or not, so you can't check anything.
If you did assign something to it, and you want to find out if the existing array is empty or not, that would depend on how you created it first.
If the array was assigned from some convenience method, it's autoreleased, so just do this:
if ([array count] == 0) {
array = [[NSMutableArray alloc] init];
} else {
array = [[userDefaults arrayForKey:#"MyFavorites"] mutableCopy];
}
If the array was assigned from an init or copy method, or it was retained previously, store the count in a temporary variable, release the array and use the temporary variable to decide what to do:
NSInteger count = [array count];
[array release];
if (count == 0) {
array = [[NSMutableArray alloc] init];
} else {
array = [[userDefaults arrayForKey:#"MyFavorites"] mutableCopy];
}
In your case I'd always use without differentation
array = [[userDefaults arrayForKey:#"MyFavorites"] mutableCopy];
and set the default value in the user defaults to an empty array right away at program start before accessing the defaults (from Apple's example):
+ (void)initialize{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *appDefaults = [NSDictionary
dictionaryWithObject:[NSArray array] forKey:#"MyFavorites"];
[defaults registerDefaults:appDefaults];
}
See Apple's doc on this.
Supposing you are talking about NSArray, if myArray has not been properly alloced+initialized (what you are trying to check) its reference will be nil, so you can do:
if(myArray) //or even if(myArray != nil) since myArray will be a pointer
{
//properly inizialized
}
else
{
//not properly inited
}
If it's been inited on the other hand, you can test its emptiness by checking the count property which returns the number of elements it contains
if([myArray > 0])
//there is at least one element
}
else
{
//no elements
}
you can use count function of NSArray. it will work on NSMutableArray too....
syntext will be,
int ct=[array count];
ct will have number of items in array.
if it us empty it will be Zero
I have a NSMutableArray which contains a few NSString objects. How can I test if the array contains a particular string literal?
I tried [array containsObject:#"teststring"] but that doesn't work.
What you're doing should work fine. For example
NSArray *a = [NSArray arrayWithObjects:#"Foo", #"Bar", #"Baz", nil];
NSLog(#"At index %i", [a indexOfObject:#"Bar"]);
Correctly logs "At index 1" for me. Two possible foibles:
indexOfObject sends isEqual messages to do the comparison - you've not replaced this method in a category?
Make sure you're testing against NSNotFound for failure to locate, and not (say) 0.
[array indexOfObject:object] != NSNotFound
Comparing against string literals only works in code examples. In the real world you often need to compare against NSString* instances in e.g. an array, in which case containsObject fails because it compares against the object, not the value.
You could add a category to your implementation which extends NS(Mutable)Array with a method to check wether it contains the string (or whatever other type you need to compare against);
#implementation NSMutableArray (ContainsString)
-(BOOL) containsString:(NSString*)string
{
for (NSString* str in self) {
if ([str isEqualToString:string])
return YES;
}
return NO;
}
#end
You may also use a predicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF IN %#", theArray];
BOOL result = [predicate evaluateWithObject:theString];
for every object
[(NSString *) [array objectAtIndex:i] isEqualToString:#"teststring"];