Replacing elements in an array

Hello,

I don’t how to achieve this. I’d like the user be able to set thresholds into a continuously degressive array. Results and thresholds appear in parallel columns (the table view has a controller).
Ex:
Key: “result” Key:“threshold”
20.0
19.5 ← first threshold here
19.0
18.5 ← second threshold here
18.0

In an ASOC app, using a tableview, an external (reverse) array, some radio controls and a data source, I managed to do it. Heavily, but with a good processor it’s fast enough. :confused:

In Objective-C and array controllers, it should be a better solution. In fact, it’s almost working. My only problem is: when the user moves a threshold, I should be able to erase the previous location (that was the goal of the (reverse) array, storing threshold locations – in the example above, the array had contained {19.5, 18.5.}.

Is it possible to retrieve a particular dictionary into an array, using only a particular value of one of its keys?

Regards,

Of course you can do that, but I don’t think I understand what you’re trying to do. To find a particular object in an array you could use indexOfObjectPassingTest:, and in the block, query the value of the particular key you’re interested in. Here is a code snippet that shows, in general, how to use that method:

NSUInteger indx = [array indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [[obj valueForKey:@"key"] isEqualToString:@"your search string here"];
    }];

The return in the block method returns the index of the object whose valueForKey:@“key” is equal to the search string.

Ric

Well, imagine you want to attribute the «Fair» comment if the result is in range 12…16, «Good» if the result is in range 17…23 and «Excellent» if it’s above 23. These ranges depend of the maximal result (and the user appreciation). I want him to be able to fix the thresholds manually.

The best way is to put these thresholds on the same line than a certain result, so the tableview seems to fit this usage.

The user chooses a certain threshold (using a radio button) and clicks on the desired line. The tableview stores this threshold. So far, so good. The ObjC version works.

And now if the user changes his mind: letting the same radio button enabled, he clicks on another line. I have to retrieve where the threshold was, erase it, and put it on the new line.

My problem is. to retrieve this line. That’s why I used a sort of “pointers table” in ASOC, saying “this threshold is new” or “already stored in line xy”.

I would do it the object oriented way (very simple version)

Define a custom class MyClass
.h


#import <Foundation/Foundation.h>

@interface MyClass : NSObject

+ (void)setLowerThreshold:(float)val;
+ (void)setHigherThreshold:(float)val;

@property (nonatomic, copy) NSString *result;
@property (nonatomic, readonly) NSString *comment;

@end


.m


#import "MyClass.h"

static float lowerThreshold = 16.0;
static float higherThreshold = 23.0;

@implementation MyClass
@synthesize result;

+ (void)setLowerThreshold:(float)val
{
    lowerThreshold = val;
    [[NSNotificationCenter defaultCenter] postNotificationName:@"didChangeThresholdNotification" object:nil];
}

+ (void)setHigherThreshold:(float)val
{
    higherThreshold = val;
   [[NSNotificationCenter defaultCenter] postNotificationName:@"didChangeThresholdNotification" object:nil];
}

- (id)init
{
	self = [super init];
    if (self) {
        self.result = @"0.0";
    }
	return self;
}


- (NSString *)comment
{
	float floatResult = [self.result floatValue];
    if (floatResult < lowerThreshold)
        return @"Fair";
    else if (floatResult < higherThreshold)
        return @"Good";
    else
        return @"Excellent";
}


- (void)dealloc {
    self.result = nil;
    [super dealloc];
}

@end

set the class name of the Object Controller to MyClass and
bind the table view columns to the keys result and comment

In the class which controls the array controller, add in awakeFromNib

   [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didChangeThreshold:)
                                                 name:@"didChangeThresholdNotification"
                                               object:nil];

in dealloc add

[[NSNotificationCenter defaultCenter] removeObserver:self];

and this custom method (arrayController is the NSArrayController instance)


- (void)didChangeThreshold:(NSNotification *)notification
{
    [arrayController rearrangeObjects];
}

Then you can set the threshold values with e.g.

[MyClass setLowerThreshold:14.0];

the values in the comment column change automatically depending on the threshold values

[Edit} PS: you can download a sample project here

I guess this is what I don’t understand. What are you actually showing in the table? Don’t you want the same threshold to apply to all rows of the table? Are you showing “Fair”, “Good”, “Excellent” in one column, and what are you showing next to it in another column? Raw scores?

Ric

Yes, as in my first post. In ASOC version (with a unique -and large- IF test into the tableView_objectValueForTableColumn_row_ method) I could even note each scored result, so:

Column1 | Column2 | Column3
Thresholds | Points | Scores
| 20.0 |
| 19.5 | ¢ → This one is rated “Excellent”
| 19.0 |
Excellent | 18.5 | ¢¢ → These two are rated “Excellent”
| 18.0 | ¢ → This one is rated “Good”
| 17.5 | ¢¢¢¢ → These four are “Good” too
Good | 17.0 |

As soon as I shall be able to create objects (I work hard but only make Xcode show me yellow, rose and red warnings and errors) I’ll populate my apps with even useless objects! Abstracted, semi-abstarcted and visual objects! But here, a threshold inspires me more a encapsulated C-array like in a @implementation ArrayOfFloats.
Note that a threshold is a single bound value, not a range.

Anyway, thank you for your time. I’ll keep these examples in mind and sure be happy to retrieve them later!

Regards,

There are always many different ways to tackle a particular problem. One option would be to use a value transformer, to return the “Fair”,“Good”, “Excellent” strings based on the point values and your thresholds. In the example below, both columns of the table are bound to arrangedObjects.num, but the column with the rating strings also has a value transformer in the bindings. I set up the transformer and the array in the app delegate. I used 3 text fields for entering the thresholds (but radio buttons would work as well), and have an action method for them that just forces the table to reload its data. The name that a is supplied in the setValueTransformer:forName: method is the name you use in the value transformer field in IB for the bindings:

@synthesize window = _window,theData,low,middle,high,tv;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    //Instatiate the value transformer and register its name
    PointsToRatingTransformer *transformer = [[PointsToRatingTransformer alloc]init];
    [PointsToRatingTransformer setValueTransformer:transformer forName:@"ScoreToRating"];
    
    //Set some initial values for the thresholds
    self.low = [NSNumber numberWithFloat:0];
    self.middle = [NSNumber numberWithFloat:12];
    self.high = [NSNumber numberWithFloat:25];
    
    // Create the array of point values from 30 to 1 in 0.5 point increments
    self.theData = [NSMutableArray array];
    float points = 30;
    while (points > 1) {
        NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:points] forKey:@"num"];
        [self.theData addObject:dict];
        points -= .5;
    }
    self.theData = theData;
}

// Action method for the 3 text fields where threshhold values are changed
// The values of these text fields are bound to low, middle, and high
-(IBAction)updateTable:(id)sender { 
    [self.tv reloadData];
}

And the code for the subclass of NSValueTransformer is :

#import "PointsToRatingTransformer.h"
#import "AppDelegate.h"

@implementation PointsToRatingTransformer

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

- (id)transformedValue:(NSNumber *)value {
    if (value.floatValue == [[NSApp delegate] low].floatValue) {
        return @"Fair";
    }else if (value.floatValue == [[NSApp delegate] middle].floatValue){
        return @"Good";
    }else if (value.floatValue == [[NSApp delegate] high].floatValue){
        return @"Excellent";
    }
    return @"";
}

Ric

Hi Ric,

As you say, there are many way to solve this problem. I have to confess I felt back to procedural habits.

I finally used an other method than in my ASOC app. I fill up ALL values between thresholds, using an (ugly) C float array (all procedural languages use similar structures, so I had this reflex).

As thresholds are float values, the in-between values are float too. I made an (even more ugly) mix, filling a C array using Objective-C methods. :confused:

I end up with something like this:

Then when it comes to attributes a score, I only set the “result” field of the table to a particular value of the attrib array.

Well. Not a “state of the art” solution, I have to admit. Your solution is much closer to my ASOC solution, forcing the table to reload its data at each threshold modification. Mine just “reload” the C array before the attribution of the scores. And for the attribution, look at the ASOC way compared to the new one:

ASOC loop:

repeat with i from 1 to count of gResult
	if item i of gResult is not missing value then
		repeat with n from 2 to 12
			if item n of gThresholds is not missing value then
				if item i of gResult < (gMaximalPoints as real) - ((item n of gThresholds) * 0.5) then
					set item i of gMark to normalize((n - 1) / 2)
					exit repeat
				end if
			end if
			set item i of gMark to gMaximalMark
		end repeat
	end if
end repeat

C/Objective-C loop:

Ok, maybe it’s not a language problem, but a programmer problem. :stuck_out_tongue: But I understand what DJ said (in another post) about applications written in C/C++.

Thanks, best regards,