I am trying to grab a relationship property of an object from a mutable array.
theNewItems[0].step is giving the error, Property 'step' found on object of type id.
Here is how I created the array:
NSMutableArray* theNewItems = [NSMutableArray arrayWithCapacity:20];
[theNewItems addObject:_itemsOnLoad[0]];
[theNewItems addObject:_itemsOnLoad[1]];
[theNewItems addObject:_itemsOnLoad[2]];
And here is how the array logs out
<Items: 0x1706842e0> (entity: Items; id: 0xd000000001900004 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Steps/p100> ; data: {
item = "0xd000000016000000 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Dare/p1408>";
sid = 545;
step = "step three";
wasdeleted = nil;
whenadded = nil;
}),
<Items: 0x170684330> (entity: Items; id: 0xd000000001840004 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Steps/p97> ; data: {
item = "0xd000000016000000 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Dare/p1408>";
sid = 544;
step = "step two";
wasdeleted = nil;
whenadded = nil;
}),
<Items: 0x170684380> (entity: Items; id: 0xd000000001780004 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Steps/p94> ; data: {
item = "0xd000000016000000 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Dare/p1408>";
sid = 543;
step = "step one";
wasdeleted = nil;
whenadded = nil;
})
)}
Should I be creating the mutablearray differently? Or how can I grab the property "step"?
Thanks in advance for any suggestions.
For clarification...
An NSArray (mutable or not) can hold objects of any type. So, when you "get" an object from the array, the compiler needs to know what you are getting.
Example:
NSMutableArray *a = [NSMutableArray arrayWithCapacity:40];
[a addObject:[UIView new]]; // add a UIView
[a addObject:#"A string"]; // add a NSString
[a addObject:#100]; // add a NSNumber
You now have an array with a View, a String and a Number. If you try to do this:
UIView *v = a[0];
NSString *s = a[1];
NSNumber *n = a[2];
You'll get warnings because while the types are correct, the compiler doesn't know that.
To actually use the objects you've stored in the array, you have to cast them. So, with the same example data:
UIView *v = (USView *)a[0];
NSString *s = (NSString *)a[1];
NSNumber *n = (NSNumber *)a[2];
is fine... you can use your v s and n objects as you'd expect.
For your specific object type of Items, you could:
Items *thisItem = (Items *)theNewItems[0];
NSString *theStep = thisItem.step;
or, more concisely:
NSString *theStep = ((Items *)theNewItems[0]).step;
In 2015, Apple introduced "Lightweight Generics" into Objective-C. This allows you to declare an array of type:
NSMutableArray <Items *> *theNewItems = [NSMutableArray arrayWithCapacity:20];
[theNewItems addObject:_itemsOnLoad[0]];
[theNewItems addObject:_itemsOnLoad[1]];
[theNewItems addObject:_itemsOnLoad[2]];
NSString *theStep = theNewItems[0].step;
And no more casting. Note that you still add your Items objects to the array in the same manner.
Another note: Reading around you'll find some debate about arrayWithCapacity. The most reliable info I've found explains that it perhaps used to make memory management a bit more efficient, but these days it's simply a "hint" and, really, only makes for readability as in:
"When I review my code, I see that I'm expecting this array to hold 40 objects."
It does not, however, pre-allocate memory... nor does it limit the array to 40 elements - the array will still expand as you continue to add objects to it.
You don't need to use arrayWithCapacity you can just make an array using [[NSMutableArray alloc] init]; which will have no limit on capacity.
To get the property just say ((Items *)theNewItems[x]).step, x being the index at which you want the property. Also if you want to skip the casting step when pulling the object out of the array define your array as NSMutableArray<Items *> * theNewItems = [[NSMutablearray alloc] init] then you can just say theNewItems[x].step
Related
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];
Is there better way for
_studentArray = [[NSMutableArray alloc] initWithObjects:student0, student1, student2, student3, student4, student5, student6, student7, student8, student9, student10, student11, student12, student13, student14, student15, student16, student17, student18, student19, nil];
using FOR loop?
As the others have pointed out, it's probably best to not create 20 different objects in uniquely named variables (student0, student1, student2, etc) in the first place. Better to create them in an array in the first place, as others have pointed out.
If you MUST deal with lots of instance variables like that, you should be able to use valueForKey to find them all:
_studentArray = [NSMutableArray arrayWithCapacity: 20];
for (int i=0; i< 20; i++)
{
NSString *keyName = [NSString stringWithFormat: #"student%d", i];
id aStudentVar = [self objectForKey: keyName];
_studentArray[i] = aStudentVar;
}
I haven't tested this, but it should work. Again, though, having that many sequentially named instance variables is a bad "code smell". There is almost certainly a better way to do whatever it is you're doing.
Depends on the source from where and at a time how many objects you are getting
if you are getting all the object in a go you can add as above or by using a for loop to add individual objects.
If you are getting a single object at a time add it and add other objects as well.
It depends where the student objects came from, how they were created etc. But I think you should re-think your code to NOT have a reference for each student like you did. Just think about how hard would it be if facebook had done the same as you did to refere to each user?
If you are creating the student objects by yourself, try an approach like this:
- (void)viewDidLoad {
_studentArray = [NSMutableArray new];
for (int i = 0; i < 20; i++) {
Student *student = [[Student alloc] init];
[_studentArray addObject:student];
}
//At this point you will have the same that you did.
//To access the 2nd student for example, you could do
Student *theSecond = _studentArray[1]; //(because array index starts from 0)
theSecond.name = #"Peter";
}
or if you want to create and add to _studentsArray at the same time:
- (void)viewDidLoad {
_studentArray = [NSMutableArray new];
[self registerStudentWithName:#"Peter"];
[self registerStudentWithName:#"John"];
//and so on...
Student *firstStudent = _studentArray[0];
NSLog(#"%#", firstStudent.name); //Prints Peter
}
- (void)registerStudentWithName:(NSString *)name {
Student *student = [[Student alloc] init];
student.name = name;
[_studentArray addObject:student];
}
Its very simple, all you have to do is start for loop for some count.
for(int i=0;i<20;i++) {
NSString *obj = [[NSString alloc]init];
[_studentArray addObject:obj];
}
I have a UITableView and am displaying contents from my NSMutableArray. Following is array format
(
{
Name = "ANS";
VersionNo = 6;
},
{
Name = "O-Hydro";
Version = 6;
},
{
Name = "ANS";
Version = 6;
},
{
Name = "ANTIChorosAnticholinergic";
Version = 6;
}
)
From this I need to display only unique "Name" (like in this I can see 2 "ANS" I need only one).
How can I do this in iOS?
I tried following but its not working
uniqueArray= [[NSMutableSet setWithArray: groupDetails] allObjects];
but in this way I can do only for NSArray not NSMutableArray.
Pls help me
You can use following line of code to convert your NSArray to NSMutableArray,
NSArray *uniqueArray= [[NSMutableSet setWithArray:groupDetails] allObjects];
NSMutableArray *myMutableArray = [[NSMutableArray alloc] initWithArray:uniqueArray];
You could simply add mutableCopy.
But wait, before you do it. Arrays and sets have two differences:
Arrays can contain duplicates, sets cannot.
Arrays are ordered, sets are not.
So doing what you are doing, you lose the duplicates (intentionally), but the order, too (probably not intentionally).
I do not know, whether this is important for you, but for other readers it might be. So it is the better approach to do that with NSOrderedSet instead of NSSet:
NSOrderedSet *uniqueList = [NSOrderedSet orderedSetWithArray:array];
In many cases an ordered set is exactly what you want. (Probably it has been from the very beginning and the usage of NSArray was wrong. But sometimes you get an array.) If you really want an array at the end of the day, you can reconvert it:
array = [uniqueList.array mutableCopy];
If you just want an array of unique name values, you can use #distinctUnionOfObjects with valueForKeyPath -
NSArray *uniqueArray=[groupDetails valueForKeyPath:#"#distinctUnionOfObjects.name"];
But if you want the array to contain the dictionaries that correspond to the unique names then you need to do a little more work -
NSMutableArray *uniqueArray=[NSMutableArray new];
NSMutableSet *nameSet=[NSMutableSet new];
for (NSDictionary *dict in groupDetails) {
NSString *name=dict[#"name"];
if (![nameSet containsObject:name]) {
[uniqueArray addObject:dict];
[nameSet addObject:name];
}
}
I have an array which contains multiple Dictionaries each one with 3 keys (#"date", #"username", #"text").
What I want to check for, is whether the same user (#"username") exists in more than one dictionary in that Array. And, if she does, combine the text for those "duplicates" into one dictionary.
I have considered this answer to check for duplicates and this one
but I cannot figure out how to combine these two.
Jumping in here because although I think you should work on the code yourself first, I think Miro's answer is more complicated than the issue requires and though I like the idea of using predicates in Greg's answer, here's a 3rd solution that (1) wouldn't require you to change your data structure and (2) references the necessary loops...
The way I'd do it: Create an NSMutableArray then start adding the usernames in order. If the NSMutableArray already contains the username though, don't add another instance of the username, but instead merge the dictionary info.
ex.
// Note: I'm calling your array of user dictionaries userArray.
// Create a username array to store the usernames and check for duplicates
NSMutableArray *usernames = [[NSMutableArray alloc] init];
// Create a new userArray to store the updated dictionary info, merged
// entries et. al.
NSMutableArray *newUserArray = [[NSMutableArray alloc] init];
// Go through the array of user dictionaries
for (NSDictionary *userDict in userArray) {
// If the usernames array doesn't already contain the username,
// add it to both the usernames array and the newUserArray as is
if (![usernames containsObject:[userDict objectForKey:#"username"]]) {
[usernames addObject:[userDict objectForKey:#"username"]];
[newUserArray addObject:userDict];
}
// Otherwise, merge the userArray entries
else {
// Get a mutable copy of the dictionary entry at the first instance
// with this username
int indexOfFirstInstance = [usernames indexOfObject:[userDict objectForKey:#"username"]];
NSMutableDictionary *entry = [[newUserArray objectAtIndex:indexOfFirstInstance] mutableCopy];
// Then combine the "text" or whatever other values you wanted to combine
// by replacing the "text" value with the combined text.
// (I've done so with a comma, but you could also store the value in an array)
[entry setValue:[[entry objectForKey:#"text"] stringByAppendingString:[NSString stringWithFormat:#", %#", [userDict objectForKey:#"text"]]] forKey:#"text"];
// Then replace this newly merged dictionary with the one at the
// first instance
[newUserArray replaceObjectAtIndex:indexOfFirstInstance withObject:entry];
}
}
Maybe something like this [untested] example? Loop through, maintain a hash of existing items, and if a duplicate is found then combine with existing and remove.
NSMutableArray main; // this should exist, with content
NSMutableDictionary *hash = [[NSMutableDictionary alloc] init];
// loop through, backwards, as we're attempting to modify array in place (risky)
for(int i = [main count] - 1; i >= 0; i--){
// check for existing
if(hash[main[i][#"username"]] != nil){
int existingIdx = [hash[main[i][#"username"]] integerValue]; // get existing location
main[existingIdx][#"text"] = [main[existingIdx][#"text"] stringByAppendingString:main[i][#"text"]]; // "combine text" .. or however you'd like to
[main removeObjectAtIndex:i]; // remove duplicate
} else {
[hash setValue:[[NSNumber alloc] initWithInt:i] forKey:main[i][#"username"]]; // mark existance, with location
}
}
If you use NSMutableDictionary, NSMutableArray and NSMutableString you can do it with predicate like that:
NSMutableDictionary *d1 = [#{#"username": #"Greg", #"text" : [#"text 1" mutableCopy]} mutableCopy];
NSMutableDictionary *d2 = [#{#"username": #"Greg", #"text" : [#"text 2" mutableCopy]} mutableCopy];
NSMutableDictionary *d3 = [#{#"username": #"John", #"text" : [#"text 3" mutableCopy]} mutableCopy];
NSMutableArray *array = [#[d1, d2, d3] mutableCopy];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"username = %#", #"Greg"];
NSArray *filterArray = [array filteredArrayUsingPredicate:predicate];
NSMutableDictionary * firstDict = filterArray[0];
for (NSDictionary *d in filterArray)
{
if (firstDict != d)
{
[firstDict[#"text"] appendString:d[#"text"]];
[array removeObject:d];
}
}
I'm pretty new to Objective-C and iOS so I've been playing around with the Picker View. I've defined a Person Class so that when you create a new Person it automatically gives that person a name and age.
#import "Person.h"
#implementation Person
#synthesize personName, age;
-(id)init
{
self = [super init];
if(self)
{
personName = [self randomName];
age = [self randomAge];
}
return self;
}
-(NSString *) randomName
{
NSString* name;
NSArray* nameArr = [NSArray arrayWithObjects: #"Jill Valentine", #"Peter Griffin", #"Meg Griffin", #"Jack Lolwut",
#"Mike Roflcoptor", #"Cindy Woods", #"Jessica Windmill", #"Alexander The Great",
#"Sarah Peterson", #"Scott Scottland", #"Geoff Fanta", #"Amanda Pope", #"Michael Meyers",
#"Richard Biggus", #"Montey Python", #"Mike Wut", #"Fake Person", #"Chair",
nil];
NSUInteger randomIndex = arc4random() % [nameArr count];
name = [nameArr objectAtIndex: randomIndex];
return name;
}
-(NSInteger *) randomAge
{
//lowerBound + arc4random() % (upperBound - lowerBound);
NSInteger* num = (NSInteger*)(1 + arc4random() % (99 - 1));
return num;
}
#end
Now I want to make an array of Persons so I can throw a bunch into the picker, pick one Person and show their age. First though I need to make an array of Persons. How do I make an array of objects, initialize and allocate them?
There is also a shorthand of doing this:
NSArray *persons = #[person1, person2, person3];
It's equivalent to
NSArray *persons = [NSArray arrayWithObjects:person1, person2, person3, nil];
As iiFreeman said, you still need to do proper memory management if you're not using ARC.
NSMutableArray *persons = [NSMutableArray array];
for (int i = 0; i < myPersonsCount; i++) {
[persons addObject:[[Person alloc] init]];
}
NSArray *arrayOfPersons = [NSArray arrayWithArray:persons]; // if you want immutable array
also you can reach this without using NSMutableArray:
NSArray *persons = [NSArray array];
for (int i = 0; i < myPersonsCount; i++) {
persons = [persons arrayByAddingObject:[[Person alloc] init]];
}
One more thing - it's valid for ARC enabled environment, if you going to use it without ARC don't forget to add autoreleased objects into array!
[persons addObject:[[[Person alloc] init] autorelease];
No one commenting on the randomAge method?
This is so awfully wrong, it couldn't be any wronger.
NSInteger is a primitive type - it is most likely typedef'd as int or long.
In the randomAge method, you calculate a number from about 1 to 98.
Then you can cast that number to an NSNumber. You had to add a cast because the compiler gave you a warning that you didn't understand. That made the warning go away, but left you with an awful bug: That number was forced to be a pointer, so now you have a pointer to an integer somewhere in the first 100 bytes of memory.
If you access an NSInteger through the pointer, your program will crash. If you write through the pointer, your program will crash. If you put it into an array or dictionary, your program will crash.
Change it either to NSInteger or int, which is probably the best, or to NSNumber if you need an object for some reason. Then create the object by calling [NSNumber numberWithInteger:99] or whatever number you want.
This might not really answer the question, but just in case someone just need to quickly send a string value to a function that require a NSArray parameter.
NSArray *data = #[#"The String Value"];
if you need to send more than just 1 string value, you could also use
NSArray *data = #[#"The String Value", #"Second String", #"Third etc"];
then you can send it to the function like below
theFunction(data);
This way you can Create NSArray, NSMutableArray.
NSArray keys =[NSArray arrayWithObjects:#"key1",#"key2",#"key3",nil];
NSArray objects =[NSArray arrayWithObjects:#"value1",#"value2",#"value3",nil];