Array contents: coercion problem.

Hello, I’d like to have some hints about one thing:

In an array, bound to a tableview column, I have some numeric (float) values. These values are entered (put into the array) using interface buttons, labelled “0”, “1”, “2” and so on. There is also a “C” button to cancel an entry.

Later, I have some calculations to do with this array. If no result was entered, the array’s object cannot be 0. “No value” is certainly better represented by “nil”. but you are not allowed to insert a nil object into an array (nil means “end of array”.

A NSArray contains objects. That is, here, these object must be NSNumbers. So I can initialize my array by filling it with [NSNumber numberWithDouble:.] double what? I cannot put nil here!

A true NSArray does not (can not) contain “holes”. But mine has to, if for example, the third item never received a value. How can I:

implement an “undefined” value for my NSNumber?
make the “C” button erase a previously entered result?
use the @avg key for the column’s key (which hates when NSStrings are part of the calculation)

There must be a solution. In ASOC I used a trick: I did the attribution in the method called by every button, using its title, so:

        if sender's title as string is "C" then
            set item theRow of gResult to missing value -- the row was the selected line, gResult was a AS list.
        else
            set item theRow of gResult to sender's title as real
        end if

What should I do here?

  • choose a special value (say -1) to indicate an undefined value, then use a NSPredicate to filter my array?
  • how could I turn the cell to “nothing” as the special value IS something?

I’m sure there is an elegant solution to this problem. But for now I just tear out what little hair I have left :confused:

Thank for help.

Hi,

use a NSNumber object with an unused value (like CGFLOAT_MAX or NSNotFound = NSIntegerMax for integer values)
or [NSNull null].

Hello Stefan.

My table view shows something in every case, even is awful. The only thing which is acceptable, from the table view as from the user, is the empty NSString. (indicating “Nothing has been entered”)

So I end up with an nice array but filled with strings. Bad for calculations, hmm?

In the ASOC version, I had to do

repeat with i from 1 to count of gScore
if item i of gScore is not missing value then set item i of gMark to normalize (gMaximalMark as real - (item i of gScore * gPointPerFault))
end repeat
calculateAverage() – another method which coerce a lot.

So : testing if the array item was not reset to nil (in AS you can put “missing value” into an array), then coerce the string to real. I’m looking for something more smart, like:

      table view                                                                             table view
     column "result"                                                                      column "mark"

item array gScore → array for calculation → array for result → array gMark


1 “23.5” 23.5 3.5 “3.5”
2 “” — (no result) “”
3 “12.0” 12.0 2.5 “2.5”
.

I’m not sure that translating the working ASOC code is a good idea. It’s a problem of cocoa’s knowledge here to get the work done, much more cleaner as the previous version.

EDIT:

  • (IBAction) numPadClicked: (id)sender {
    if ([[sender title] isEqualToString:@“C”]) {
    [[currentClassController selectedObjects] setValue:nil forKey:@“result”];
    } else {
    [[currentClassController selectedObjects] setValue:[NSNumber numberWithDouble:[[sender title]floatValue]] forKey:@“result”];
    };
    }

Curiously enough, setValue:nil seems to work, maybe because the value of the object is set to nil, not the object itself. The column is sorted correctly and the @max.result binding shows the correct value, but as soon as I try to enter a value from the keyboard (which was possible so far, as the column is editable) my app terminates abruptly (to put it nicely).

Sounds like a job for a custom value transformer to me. You can have the array populated with numbers (and NULLs), but use the transformer to show whatever you want, including strings. For your needs a very simple transformer like the example below will transform your [NSNull null] entries into an empty string (the class should be a subclass of NSValueTransformer).

#import "NullToStringTransformer.h"

@implementation NullToStringTransformer


+ (Class)transformedValueClass {
    return [NSString class];
}

- (id)transformedValue:(id)value {
    if (value == [NSNull null]) {
        return @"";
    }else{
        return value;
    }
}
@end

The value transformer should show up in the list of transformers in IB, so you can just choose it there when you set up your bindings for that column.

Ric

Hi Ric,

I was thinking to a reverse solution: my array bound to the table may contain strings looking like numbers (defined result) or empty strings (undefined result). Behind the scenes, I could have a filtered array to make calculations. But I’m stuck with the predicate. Should be something like

start array (strings) {“12.5”, “5”, “”, “22.5”} → final array (real numbers) {12.5, 5, 22.5}

I could use this final array, for example, to bind value like @count, @sum, @max… elsewhere in IB.

A second problem will be to re-attribute results (after calculation) to the start array. first signs of headache. :confused:

EDIT: The user first enters results in the table view (column “result”) then defines some calculations parameters and press the “calculate” button. The marks come in the column “mark”.

In an daydream, I saw the “calculate” button disappear. marks would be calculated synchronously.

I make this work:

  • (IBAction) numPadClicked: (id)sender {
    [[currentClassController selectedObjects] setValue:[sender title] forKey:@“result”];
    NSPredicate *emptyPredicate = [NSPredicate predicateWithFormat:@“result.length > 0”];
    self.filteredCurrentClass = [self.currentClass filteredArrayUsingPredicate:emptyPredicate];
    NSLog(@“%@”,self.filteredCurrentClass);
    }

New question: is it possible to set

self.filteredCurrentClass = [self.currentClass filteredArrayUsingPredicate:emptyPredicate];

once, and not each time the IBAction is triggered?

No, it has to be called each time you add a new value to keep everything updated – it doesn’t really matter though, filtering an array with a predicate is very fast.

Ric

Gosh, yes, it’s as fast as light! And so. evident when it works :slight_smile:

  • (IBAction) calculateImaginaryMark: (id)sender {
    for (NSDictionary *aPupil in filteredCurrentClass){
    [aPupil setValue: forKey:@“mark”];}
    }

Well. not too bad. Time to sleep now (03:40 am here.)

Thank you for being on this site!

I should have been very tired last night. :confused:

The result column, showing strings, is great for:

  • setting the value using a button title;
  • reseting the value by putting a blank string

but it becomes a nightmare when:

  • sorting the column (always the same alpha/numeric problem: “3” is > “21”)
  • doing calculations (becomes worse that ASOC for coercing, there is no “as string”/“as real” facility)
  • binding strings to numeric values by keys @avg, @sum etc become nonsense

I found the ASOC version of the app unnecessarily heavy and repetitive, I don’t want the Objective-C version to become even more complicated. :mad:

Ok, Ric, I’ll have a look on the valueTransformers. Sigh. That means:

  • transforming the IBAction to put the float value equivalent of the title into the array
  • put NSNull (or -1) value when the user presses “C”
  • change the NSPredicate to filter nulls
  • implement a value transformer to return “” when the value is null.

Oh, Lord, still sleepless nights in perspective. :rolleyes:

I think you’re making it sound a lot harder than it is. Changing strings into floats is as easy as adding floatValue to the end of however you’re getting your string, and the value transformer, I already showed you is all you need to do the value transformation for your table view. As far as the predicate goes, why do you want the filtered array? Is it so you can use bindings and things like @avg or @max. If you want to use these particular bindings, you will have to filter the array since @avg, at least won’t work with nulls in your array. Alternatively, you could do the average (and max/min) calculations yourself rather than use the bindings.

Ric

Hi Ric,

I finally solved this problem so:

The main controller’s array contains float values. The numeric pad now are used for their tag values, not for their titles anymore. So I have integer numbers (the pad only contains 0.9) to attribute to the main array.

The “C” button calls another IBAction, which sets the value to nil. The result is not shown, as I wanted.

Filtered array controller contains only legal numbers, no nils. They are filtered by the predicate. So I can used these @sum, @avg and so on. In ASOC version, I had to “filter” the array in a loop, coerce the strings to numbers, do the calculations and re-coerce results to strings. This code is now useless (about one third of the application).

I’m learning a lot with this “translation”. ASOC and ObjC have their own advantages. ASOC is really cool for the level of abstraction – properties is the better example. ObjC is a little harder (so was ASOC at the beginning) but I have come to like these long colored names. :slight_smile: and this (id) is really handy.

I understand what you or others said about “the good part of C”: apart from the control statements (if, for, while, switch, and so on) the ugliness of C is abstracted enough.

I can assert here that ASOC is certainly the best way to ObjC: the giant leap is not the “on myButton_(sender)” syntax, it’s the entrance to cocoa: docs are the same, you know how and where to search, and all the work you did in ASOC may be reused with Objective-C.

.that is, mainly, your work, Ric, Shane, Stefan, DJ and others! Thank you for your skills. and patience!