5

Currently I'm trying to find a compact way to average a matrix. The obvious solution is to sum the matrix, then divide by the number of elements. I have, however, come across a method on the apple developer website that claims this can be done in a simpler way using valueForKeyPath. This is linked here:

http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/CollectionOperators.html

Here is the example I'm currently working on to try and get it to work:

-(void)arrayAverager
{
   NSMutableArray *myArray = [NSMutableArray arrayWithCapacity:25];
   [myArray addObject:[NSNumber numberWithInteger:myValue]];
   NSNumber *averageValue = [myArray valueForKeyPath:@"@avg.self"];
   NSLog(@"avg  = %@", averageValue);

} 

The problem is: instead of averaging the array it merely prints out the elements in the array 1 by 1.

UPDATE

-(void) pixelAverager

{
    //Put x-coordinate value of all wanted pixels into an array
    NSMutableArray *xCoordinateArray = [NSMutableArray arrayWithCapacity:25];
    [xCoordinateArray addObject:[NSNumber numberWithInteger:xCoordinate]];
    NSLog(@"avg = %@", [xCoordinateArray valueForKeyPath:@"@avg.intValue"]);
 }
Tony Hematite
  • 149
  • 3
  • 14
  • I can't see where in the link you provided it says to use `@avg.self`. As far as I can tell the correct string is simple `@avg`. – freespace Jan 18 '13 at 12:19
  • When I just have @avg it throws the following error: 'NSUnknownKeyException', reason: '[<__NSArrayM 0x6c0c870> valueForUndefinedKey:]: this class is not key value coding-compliant for the key avg.' – Tony Hematite Jan 18 '13 at 12:25
  • What do you expect? You create an array with exactly one element! Then the average is the value of that element. - `[myArray valueForKeyPath:@"@avg.self"]` works and returns the average value of all elements of that array. – Martin R Jan 18 '13 at 12:53
  • I thought as xCoordinate was changing it would be added to the mutable array? – Tony Hematite Jan 18 '13 at 13:03
  • @TonyHematite: But you create a new (empty) `xCoordinateArray` in your method. - How to you call `pixelAverager` and how is your matrix stored? – Martin R Jan 18 '13 at 13:06
  • I call "pixelAverager" like this : `[self pixelAverager] `. What I'm essentially doing is trying to get the x coordinates of some pixels into an NSMutableArray. Like this : `//Put x-coordinate value of all wanted pixels into an array NSMutableArray *xCoordinateArray = [NSMutableArray arrayWithCapacity:25]; [xCoordinateArray addObject:[NSNumber numberWithInteger:xCoordinate]]; for (int v = 0 ; v < [xCoordinateArray count]; v++) { NSLog(@"avg = %@", [xCoordinateArray valueForKeyPath:@"@avg.intValue"]); } ` – Tony Hematite Jan 18 '13 at 13:19
  • I am aware I need a count to do so. And it was working before (printing what was in the matrix with the line : `NSLog(@"x-coordinate here is: %i", [[xCoordinateArray objectAtIndex:v]integerValue]);` – Tony Hematite Jan 18 '13 at 13:21
  • Your code adds exactly one element to the array! - And by the way, computing the average directly is faster than creating an array, adding all numbers to the array and then using `valueForKeyPath`. – Martin R Jan 18 '13 at 13:27
  • How would one compute the average directly? – Tony Hematite Jan 18 '13 at 13:28
  • @TonyHematite: I still don't understand: How are your pixel values stored? – Martin R Jan 18 '13 at 13:33
  • They're stored in the matrix "xCoordinateArray" and from tests I'm pretty sure they're stored in there. – Tony Hematite Jan 18 '13 at 13:39
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/22977/discussion-between-tony-hematite-and-martin-r) – Tony Hematite Jan 18 '13 at 13:40

2 Answers2

18

You need to use @avg.floatValue (or @avg.doubleValue, or what have you). The @avg operator will average the property of the objects in the array specified by the name after the dot. The documentation is confusing on this point, but that is what:

to get the values specified by the property specified by the key path to the right of the operator

Is saying. Since you have a collection of NSNumber objects, you use one of the *value accessors, e.g. floatValue to get the value of each object. As an example:

#include <Foundation/Foundation.h>

int main(void) {
  NSMutableArray *ma = [NSMutableArray array];
  [ma addObject:[NSNumber numberWithFloat:1.0]];
  [ma addObject:[NSNumber numberWithFloat:2.0]];
  [ma addObject:[NSNumber numberWithFloat:3.0]];

  NSLog(@"avg = %@", [ma valueForKeyPath:@"@avg.floatValue"]);

  return 0;
}

Compiling and running this code returns:

$ clang avg.m -framework Foundation -o avg
steve:~/code/tmp
$ ./avg 
2013-01-18 12:33:15.500 avg[32190:707] avg = 2
steve:~/code/tmp

The nice thing about this approach is that this work for any collection, homogenous or otherwise, as long as all objects respond to the specified method, @avg will work.

EDIT

As pointed in the comments, the OP's problem is that he is averaging a collection with one element, and thus it appears to simply print the contents of the collection. For a collection of NSNumber objects, @avg.self works just fine.

freespace
  • 16,529
  • 4
  • 36
  • 58
  • Thanks a lot, it's not currently working for my current code (applied example) so I've put that in the question. – Tony Hematite Jan 18 '13 at 12:48
  • 1
    `valueForKeyPath:@"@avg.self"` works perfectly to compute the average of an array of numbers. The OP's problem was that this was applied to arrays of a single element. - Your answer has already been accepted, so maybe you can update it so that future readers are not informed wrongly. – Martin R Jan 18 '13 at 14:41
  • @MartinR You are correct sir. I will update my answer accordingly. – freespace Jan 19 '13 at 17:58
  • @MartinR Just as an aside, the `@avg.self` does not work in predicates, but `@avg.floatValue` does, as pointed out in the answer to [this question](http://stackoverflow.com/q/16359155/644348) - so maybe the `@avg.self` construct is a bit flakey after all. As freespace said, the docs are somewhat unclear. – Monolo May 04 '13 at 07:27
  • @Monolo: I had already seen (and upvoted :-) your question. One difference is that in a predicate, `valueForKeyPath` is sent to each individual element of the array and not to the entire array. - Using `@avg.self` has the advantage that it avoids the conversion to floating point because (contrary to the documentation) the collection operators use decimal numbers internally (compare http://stackoverflow.com/a/15383265/1187415) and is faster (compare http://stackoverflow.com/a/15931719/1187415). - I tried to find a similar solution for your predicate question but failed :-) – Martin R May 04 '13 at 08:17
  • @MartinR Very nice research in that answer, much clearer! I actually filed a bug against the documentation, but I missed the double/NSDecimal part, which seems important. – Monolo May 04 '13 at 08:51
  • Suggestion: Use always the [obj valueForKeyPath:@"avg"] as float. Using [[obj valueForKeyPath:@"avg"] integerValue] gives 0 sometimes. – decades Aug 01 '15 at 19:49
-1

No, you can't do like this. The object Transaction is a Modal Class. This class is having three properties, namely

  • payee
  • amount
  • date

Each row in this image represents one Transaction modal object.

enter image description here

transactions is an array which is holding all these rows (Transaction Modal Objects).

In these transactions array, they are trying to calculate the Transaction Modal amount field average using the operator @avg. So, its like

NSNumber *transactionAverage = [transactions valueForKeyPath:@"@avg.amount"];

your array doesn't have the key self. So that's the problem

arthankamal
  • 6,341
  • 4
  • 36
  • 51
  • 4
    This is not correct. Each element in the array responds to the selector `self`, and `[myArray valueForKeyPath:@"@avg.self"]` works. – Martin R Jan 18 '13 at 12:57