Let's say I have an object with some number of properties and I load up 1000s of these objects into an array. Next, I perform a series of valueForKeyPaths against these properties:
result.property1 = [array valueForKeyPath:#"#sum.property1"];
result.property2 = [array valueForKeyPath:#"#sum.property2"];
result.property3 = [array valueForKeyPath:#"#sum.property3"];
etc...
Summing these properties individually seems pretty inefficient. Is there a better way besides fast enumerating over the properties and summing them manually? i.e.
for(Foo* foo in array) {
result.property1 += foo.property1;
result.property2 += foo.property2;
result.property3 += foo.property3;
}
KVC requires keys to be strings:
A key is a string that identifies a specific property of an object. Typically, a key corresponds to the name of an accessor method or instance variable in the receiving object. Keys must use ASCII encoding, begin with a lowercase letter, and may not contain whitespace.
So the answer as far as I know is unfortunately you can't do this with valueForKeyPath: you would have to manually do it or enumerate over it.
Related
I have an array which has two types of objects: Awarded and non-awarded. I simply want to sort my array so that awarded objects are placed first, and the rest are placed afterwards.
Here is the code that defines the key "awarded":
if let awarded = achievements[indexPath.row].userRelation["awarded"] as? String where awarded != "<null>" { }
Within those brackets I'd like to use my unwrapped value "awarded" to sort through the achievements and add those to the beginning, and the rest at the end.
How would I go about doing that?
You should experiment with sort function, it is very useful.
let sortedAchievements = achievements.sorted{ $0.userRelation["awarded"] < $1.userRelation["awarded"] }
To sort in place use this:
achievements.sort{ $0.userRelation["awarded"] < $1.userRelation["awarded"] }
I would recommend to refactor your model. It's better to use objects or structs instead of dictionaries. Also awarded should be a Bool property, not a String, right?
I learnt the hard way that you can't remove objects from an NSMutableArray when you are looping through the objects in it.
Looping through [< array_object > copy] instead of < array_object > fixes it.
However, I have a few unanswered questions that I would like input on from the Objective-C gurus.
In this first for loop, I expected each of the nextObjects to point to different memory (i.e I thought the msgDetail array will have a list of pointers, each pointing to the address of the NSDictionary that a particular array index contains). But all of the %p nextObject prints are giving the same value. Why is that?
for(NSDictionary *nextObject in msgDetailArray)
{
NSLog(#"Address = %p, value = %#",&nextObject,nextObject) ;
//Doing [msgDetailArray removeObject:nextObject] here based on some condition fails
}
In the second for loop, the address of the nextObject NSDictionarys are different from that printed in the first for loop.
for(id nextObject in [msgDetailArray copy])
{
NSLog(#"In copy Address = %p, value = %#",&nextObject,nextObject) ;
//Doing [msgDetailArray removeObject:nextObject] here based on some condition succeeds and it also removes it from the original array even though I am looping through a copy
}
However, when I loop through [msgDetailArray copy], and then do a removeObject:, it removes it from the original msgDetailArray. How does removeObject: do this? Does it actually use the contents of the dictionary and remove an object that matches the content? I thought all it does is to check if there is an object that is in the same memory location and remove it (Based on 2, I assume the memory address containing the dictionaries in [msgDetailArray copy] are not the same as the addresses in the original msgDetailArray). If it is actually using contents, I will have to be very careful in case there are duplicate entries.
for(id nextObject in msgDetailArray)
{
NSLog(#"Address = %p, value = %#",&nextObject,nextObject) ;
//test what is left in msgDetailArray. I see that doing removeObject on [msgDetailArray copy] does remove it from the original too. How is removeObject working (is it actually contents of dictionary)
}
Copying an array does a "shallow copy". It creates a new array object, then stores pointers to the objects in the first array. It does not create new objects.
Think of an array like an address book. It lists the addresses of your friends' houses. If I make a copy of your address book, then I have a copy of the list of the addresses. The address books do not contain houses.
If I erase an entry from my copy of the address book, it does not erase the same entry from your address book. Nor does it destroy any houses.
In your loop 2 code, you are looping through the copy, then telling your original array to delete certain objects. They are deleted from the original array, but not from the copy. (Which is good, because as you say, mutating an array as you iterate through it causes a crash.)
Note that arrays can contain more than one pointer to the same object, just like addresses can contain the same address more than once. (If a friend changes her name when she gets married, you might write her into your address under he married name and leave the entry under her maiden name as well. Both addresses point to the same house.)
Note that the removeObject method that you are using removes ALL entries for an object. It's like you're saying "erase every entry in my address book for 123 Elm street". If you only want to remove one entry of a duplicate set then you need to use removeObjectAtIndex instead, and you would need to change your code to make that work.
The copy operation copies the container, not its contents. You have a new list of pointers to the same objects.
&nextObject is the address of the variable, not the object. The address of the object is the value of the variable: NSLog(#"%p", nextObject);
removeObject: compares its argument with the contents of the collection using isEqual:, which generally does not compare by address, but by some kind of semantic valuation defined by the class.
Even leaving aside the prohibition on mutating inside a fast enumeration loop, you wouldn't be able to remove objects from the copy, because copy makes an immutable copy. You need mutableCopy in order to be able to change the new instance.
But all of the %ps prints are giving the same value. Why is that?
Because &nextObject is the address of the nextObject variable, a loop variable used in your code example; it's type is a pointer to a pointer. nextObject itself is a pointer, too, so if you would like to print the address of the object, all you need to do is casting it to void*:
NSLog(#"Address = %p, value = %#", (void*)nextObject, nextObject);
In the second for loop, the address of the nextObject NSDictionaries are different from that printed in the first for loop
Because that's the address of a different variable.
when I loop through [msgDetailArray copy], and then do a removeObject, it removes it from the original msgDetailArray.
The copy through which you are looping is in an invisible temporary object. It is an immutable copy backed by the original array.
The call [msgDetailArray removeObject:nextObject] operates on the original array. If you do not wish this effect, store the copy in a variable, and delete items from it instead:
NSMutableArray *mutableCopy = [msgDetailArray mutableCopy];
...
[mutableCopy removeObject:nextObject];
This will not touch the original array. However, you may not iterate mutableCopy at the same time as you delete items from it.
I have a fairly large number of NSManagedObjects in an NSArray and need to check whether do any of them have the same value for a property. The obvious way is nested for loops however it will take ages to go through all of them as there are about a 1000 objects in the array.
for (NSManagedObject *object in array) {
for (NSManagedObject *secondObject in array {
if ([[object valueForKey:#"key"] isEqualTo:[secondObject valueForKey:#"key"]] &&
object != secondObject) {
NSLog(#"Sharing a property");
}
}
}
Any better way to do this? If there are 1000 objects that accounts to 1 000 000 comparisons, that might take some time.
You could use an NSDictionary. Each entry would be made from the following pair:
key would be equal to the selected NSManagedObjects attribute
value would be an NSArray of NSManagedObjects, that share this attribute's value
Get the list of key values for the objects in the array, then turn that into a set. If the size of the set is the same as that of the original array, there are no matches.
If you need to know which objects match, use a dictionary to create a multiset -- each key has an array of the objects as its value.
Creating your own keyed set class is also an option.
You can sort the array according to the values of that property.
Then a single loop over
the array is sufficient to find objects sharing the same value of the property.
I have two NSMutableArrays. The content of the first is numerically, which is paired to the content of the second one:
First Array Second Array
45 Test45
3 Test3
1 Test1
10 Test10
20 Test20
That's the look of both arrays. Now how could I order them so numerically so they end up like:
First Array Second Array
1 Test1
3 Test3
10 Test10
20 Test20
45 Test45
Thanks!
I would put the two arrays into a dictionary as keys and values. Then you can sort the first array (acting as keys in the dictionary) and quickly access the dictionary's values in the same order. Note that this will only work if the objects in the first array support NSCopying because that's how NSDictionary works.
The following code should do it. It's actually quite short because NSDictionary offers some nice convenience methods.
// Put the two arrays into a dictionary as keys and values
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:secondArray forKeys:firstArray];
// Sort the first array
NSArray *sortedFirstArray = [[dictionary allKeys] sortedArrayUsingSelector:#selector(compare:)];
// Sort the second array based on the sorted first array
NSArray *sortedSecondArray = [dictionary objectsForKeys:sortedFirstArray notFoundMarker:[NSNull null]];
Rather than keep two parallel arrays, I'd keep a single array of model objects. Each number from the first array would be the value of one property, and each string from the second array would be the value of the other property. You could then sort on either or both properties using sort descriptors.
Generally, in Cocoa and Cocoa Touch, parallel arrays make work while model objects save work. Prefer the latter over the former wherever you can.
I am collecting the values for a specific column from a named_scope as follows:
a = survey_job.survey_responses.collect(&:base_pay)
This gives me a numeric array for example (1,2,3,4,5). I can then pass this array into various functions I have created to retrieve the mean, median, standard deviation of the number set. This all works fine however I now need to start combining multiple columns of data to carry out the same types of calculation.
I need to collect the details of perhaps three fields as follows:
survey_job.survey_responses.collect(&:base_pay)
survey_job.survey_responses.collect(&:bonus_pay)
survey_job.survey_responses.collect(&:overtime_pay)
This will give me 3 arrays. I then need to combine these into a single array by adding each of the matching values together - i.e. add the first result from each array, the second result from each array and so on so I have an array of the totals.
How do I create a method which will collect all of this data together and how do I call it from the view template?
Really appreciate any help on this one...
Thanks
Simon
s = survey_job.survey_responses
pay = s.collect(&:base_pay).zip(s.collect(&:bonus_pay), s.collect(&:overtime_pay))
pay.map{|i| i.compact.inject(&:+) }
Do that, but with meaningful variable names and I think it will work.
Define a normal method in app/helpers/_helper.rb and it will work in the view
Edit: now it works if they contain nil or are of different sizes (as long as the longest array is the one on which zip is called.
Here's a method that will combine an arbitrary number of arrays by taking the sum at each index. It'll allow each array to be of different length, too.
def combine(*arrays)
# Get the length of the largest array, that'll be the number of iterations needed
maxlen = arrays.map(&:length).max
out = []
maxlen.times do |i|
# Push the sum of all array elements at a given index to the result array
out.push( arrays.map{|a| a[i]}.inject(0) { |memo, value| memo += value.to_i } )
end
out
end
Then, in the controller, you could do
base_pay = survey_job.survey_responses.collect(&:base_pay)
bonus_pay = survey_job.survey_responses.collect(&:bonus_pay)
overtime_pay = survey_job.survey_responses.collect(&:overtime_pay)
#total_pay = combine(base_pay, bonus_pay, overtime_pay)
And then refer to #total_pay as needed in your view.