I have an array of a dictionary that it sortedArrayUsingComparator sorts them based on the key;
listPerc = [listItem sortedArrayUsingComparator: ^(id a, id b) {
NSString *first = [a objectForKey:#"perc"];
NSString *second = [b objectForKey:#"perc"];
return [first compare:second];
the problem is that this key is a numbers, and order is so for example: 1,10, 15, 17, 2, 23, etc.
it does not calculate the magnitude of the number.
how can I do?
Can't you return the comparison result like this,
listPerc = [listItem sortedArrayUsingComparator: ^(id a, id b) {
int first = [[a objectForKey:#"perc"] intValue];
int second = [[b objectForKey:#"perc"] intValue];
if ( first < second ) {
return (NSComparisonResult)NSOrderedAscending;
} else if ( first > second ) {
return (NSComparisonResult)NSOrderedDescending;
} else {
return (NSComparisonResult)NSOrderedSame;
}
}
A very short way of #(Deepak Danduprolu)'s answer
listPerc = [listItem sortedArrayUsingComparator: ^(id a, id b) {
NSNumber *first = #([[a objectForKey:#"perc"] intValue]);
NSNumber *second = #([[b objectForKey:#"perc"] intValue]);
return [first compare:second];
}
Make them into NSNumber objects and compare those instead. Either store them as NSNumbers to start with (preferred method) or convert them when comparing (slower).
Converting an NSString to an NSNumber can be done with:
NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
[f setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber * myNumber = [f numberFromString:string];
[f release];
Maybe you can do something like this.
- (void)setPlaces:(NSArray *)places
{
if(_places != places)
{
_places = [places sortedArrayUsingComparator:^(id obj1, id obj2){
return [[obj1 objectForKey:FLICKR_PLACE_NAME] caseInsensitiveCompare:[obj2 objectForKey:FLICKR_PLACE_NAME]];
}];
[self.tableView reloadData];
}
}
I did something that was a cross between yours and the above and it works:
- (void)setPlaces:(NSArray *)places
{
if(_places != places)
{
_places = [places sortedArrayUsingComparator:^(id a, id b){
NSString *first = [a objectForKey:FLICKR_PLACE_NAME];
NSString *second = [b objectForKey:FLICKR_PLACE_NAME];
return [first caseInsensitiveCompare:second];
}
];
if (self.tableView.window) [self.tableView reloadData];
} //if _places
} //set
Related
I am receiving an array of object in the function below, each item in that array contains a value, which is type of CGFloat, I am using this function below, but getting an error at the return line : "Expected identifier"
- (void)setupPieChartWithItems:(NSArray *)items {
NSArray *sortedArray = [items sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
CGFloat first = [(PNPieChartDataItem*)a value];
CGFloat second = [(PNPieChartDataItem*)b value];
return [first > second];
}];
}
Where is the error? Any idea?
Issue may be with the brackets in this [first > second]; as sortedArrayUsingComparator is required to return NSComparisonResult value not array of bool value
replace your code with
- (void)setupPieChartWithItems:(NSArray *)items {
NSArray *sortedArray = [items sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
CGFloat first = [(PNPieChartDataItem*)a value];
CGFloat second = [(PNPieChartDataItem*)b value];
return [[NSNumber numberWithFloat:first] compare:[NSNumber numberWithFloat:second]];
}];
}
Hope it is helpful
You can replace your code for sorting with this one :
NSSortDescriptor *highestToLowest = [NSSortDescriptor sortDescriptorWithKey:#"self" ascending:YES];
NSMutableArray *sortedArray = items.mutableCopy;
[sortedArray sortUsingDescriptors:[NSArray arrayWithObject:highestToLowest]];
Setting YES to ascending will result in 1.3, 2.6, 3.1… whereas setting NO will result in 3.1, 2.6, 1.3…
Tell me if you need any help :)
Remove the brackets from [first > second] to fix the compiler error, and the block must return a NSComparisonResult:
- (void)setupPieChartWithItems:(NSArray *)items {
NSArray *sortedArray = [items sortedArrayUsingComparator:^NSComparisonResult(PNPieChartDataItem *a, PNPieChartDataItem *b) {
CGFloat first = [a value];
CGFloat second = [b value];
if (first > second)
return NSOrderedDescending;
if (first < second)
return NSOrderedAscending;
return NSOrderedSame;
}];
}
Like in this answer how to sort an NSArray of float values?.
I'm breaking my head on why descending order sort is not working with the following code. I wanted to limit by top 5 scores and other logic. The scores would look like this: 22/30, 12/18, 34/38, 23/32 etc. I added/removed SortDescriptor to sort by descending order and it seems to work for the first 3 items but then is not sorting properly. Can somebody help?
- (NSMutableArray*) method1:(NSString *) mode byScore: (NSString *) score
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *array = [[defaults objectForKey:mode]mutableCopy];
if (!array)
{
array = [[NSMutableArray alloc] init];
}
NSLog(#"The content of array is%#", array);
if ([array count] < 5)
{
if (![array containsObject:score])
{
[array addObject:score];
// Need to sort here. But not too sure this is the right place
NSLog(#"The content of the sorted array upto 5 is%#", array);
}
}
else
{
if (![array containsObject:score])
{
if ([array lastObject] < score)
{
[array addObject:score];
// Need to sort here before I remove the last object
[array removeLastObject];
NSLog(#"The content of the sorted array else is%#",array);
}
}
}
[defaults setObject:array forKey:mode];
[defaults synchronize];
// I want the array in NSUserDefaults to be sorted in desc order
// don't know what to return here ==> the array object or the defaults object cast to NSMutableArray?
}
Helper function
static NSComparisonResult CompareFloats( float a, float b )
{
if ( a < b ) { return NSOrderedAscending ; }
else if ( a > b ) { return NSOrderedDescending ; }
return NSOrderedSame ;
}
Category on NSString
#implementation NSString (Stuff)
-(float)floatValueForFraction
{
NSArray * components = [ self componentsSeparatedByString:#"/" ] ;
return [ components[0] floatValue ] / [ components[1] floatValue ] ;
}
#end
Your method:
- (void)addScore:(NSString*)score forMode:(NSString*)mode
{
NSUserDefaults * defaults = [ NSUserDefaults standardUserDefaults ] ;
NSArray * scores = [ defaults objectForKey:mode ] ;
scores = scores ? [ scores arrayByAddingObject:score ] : #[ score ] ;
scores = [ scores sortedArrayUsingComparator:^(NSString * a, NSString * b){
return CompareFloats( [ a floatValueForFraction ], [ b floatValueForFraction ] ) ;
}]
if ( scores.count > 5 ) { scores = [ scores subarrayWithRange:(NSRange){ .length = 5 } ] ; }
[ default setObject:scores forKey:mode ] ;
}
If you want the updated high scores after calling this method, just use [ [ NSUserDefaults standardUserDefaults ] objectForKey:<mode> ]. It's better to have your methods just do one thing.
One approach to sorting array:
First define a block getNumeratorAndDenominatorFromScoreString as follows:
BOOL (^getNumeratorAndDenominatorFromScoreString)(NSString *, NSInteger *, NSInteger *) = ^(NSString *scoreString, NSInteger *numeratorOut, NSInteger *denominatorOut) {
BOOL res = NO;
NSArray *components = [scoreString componentsSeparatedByString:#"/"];
if (components &&
[components count] == 2) {
res = YES;
if (numeratorOut) {
NSNumber *numeratorNumber = [components objectAtIndex:0];
*numeratorOut = [numeratorNumber integerValue];
}
if (denominatorOut) {
NSNumber *denominatorNumber = [components objectAtIndex:1];
*denominatorOut = [denominatorNumber integerValue];
}
}
return res;
};
Then use this block together with -[NSArray sortedArrayUsingComparator] to sort array:
NSArray *sortedArray = [array sortedArrayUsingComparator: ^(id obj1, id obj2) {
NSComparisonResult res = NSOrderedSame;
NSString *score1 = (NSString *)obj1;
NSString *score2 = (NSString *)obj2;
NSInteger numerator1, denominator1, numerator2, denominator2;
BOOL res1, res2;
res1 = getNumeratorAndDenominatorFromScoreString(score1, &numerator1, &denominator1);
res2 = getNumeratorAndDenominatorFromScoreString(score2, &numerator2, &denominator2);
if (res1
&& res2) {
CGFloat value1 = ((CGFloat)numerator1)/((CGFloat)denominator1);
CGFloat value2 = ((CGFloat)numerator2)/((CGFloat)denominator2);
if (value1 > value2) {
res = NSOrderedDescending;
} else if (value1 < value2) {
res = NSOrderedAscending;
}
}
return res;
}];
This will order array from least to greatest. To order from greatest to least, just replace
if (value1 > value2) {
res = NSOrderedDescending;
} else if (value1 < value2) {
res = NSOrderedAscending;
}
with
if (value1 > value2) {
res = NSOrderedAscending;
} else if (value1 < value2) {
res = NSOrderedDescending;
}
A readable structure for this method would be, in [mostly not] pseudocode
- (void)addScoreToHighscores:(NSString *)score withMethod:(NSString *)mode
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *currentHighscores = [defaults arrayForKey:mode];
if (!currentHighscores) currentHighscores = [NSArray array];
if (![currentHighscores containsObject:score]) {
currentHighscores = [currentHighscores arrayByAddingObject:score];
//sort currentHighscores: adapt the above code so that we have
BOOL (^getNumeratorAndDenominatorFromScoreString)(NSString *, NSInteger *, NSInteger *) = //as above
NSArray *newHighscores = [currentHighscores sortedArrayUsingComparator:^(id obj1, id obj2) {
//as above
}];
//truncate newHighscores
if ([newHighscores count] > 5) {
newHighscores = [newHighscores subarrayWithRange:NSMakeRange(0,5)];
}
[defaults setObject:newHighscores forKey:mode];
} else {
//since score is already in currentHighscores, we're done.
return;
}
}
If you need to screen out scores the strings for which are not equal but the evaluations of the fractions for which are equal (#"1/2" and #"5/10"), you'll need to be more clever.
Here is the full code sketched out above:
- (void)addScoreToHighscores:(NSString *)score withMethod:(NSString *)mode
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *currentHighscores = [defaults arrayForKey:mode];
if (!currentHighscores) currentHighscores = [NSArray array];
if (![currentHighscores containsObject:score]) {
currentHighscores = [currentHighscores arrayByAddingObject:score];
//sort currentHighscores: adapt the above code so that we have
BOOL (^getNumeratorAndDenominatorFromScoreString)(NSString *, NSInteger *, NSInteger *) = ^(NSString *scoreString, NSInteger *numeratorOut, NSInteger *denominatorOut) {
BOOL res = NO;
NSArray *components = [scoreString componentsSeparatedByString:#"/"];
if (components &&
[components count] == 2) {
res = YES;
if (numeratorOut) {
NSNumber *numeratorNumber = [components objectAtIndex:0];
*numeratorOut = [numeratorNumber integerValue];
}
if (denominatorOut) {
NSNumber *denominatorNumber = [components objectAtIndex:1];
*denominatorOut = [denominatorNumber integerValue];
}
}
return res;
};
NSArray *newHighscores = [currentHighscores sortedArrayUsingComparator:^(id obj1, id obj2) {
NSComparisonResult res = NSOrderedSame;
NSString *score1 = (NSString *)obj1;
NSString *score2 = (NSString *)obj2;
NSInteger numerator1, denominator1, numerator2, denominator2;
BOOL res1, res2;
res1 = getNumeratorAndDenominatorFromScoreString(score1, &numerator1, &denominator1);
res2 = getNumeratorAndDenominatorFromScoreString(score2, &numerator2, &denominator2);
if (res1
&& res2) {
CGFloat value1 = ((CGFloat)numerator1)/((CGFloat)denominator1);
CGFloat value2 = ((CGFloat)numerator2)/((CGFloat)denominator2);
if (value1 > value2) {
res = NSOrderedDescending;
} else if (value1 < value2) {
res = NSOrderedAscending;
}
}
return res;
}];
//truncate newHighscores
if ([newHighscores count] > 5) {
newHighscores = [newHighscores subarrayWithRange:NSMakeRange(0,5)];
}
[defaults setObject:newHighscores forKey:mode];
} else {
//since score is already in currentHighscores, we're done.
return;
}
}
I have method called getRandomCar which is executed via NSTimer for 4 seconds. This method have an array like this
NSArray *cars = [NSArray arrayWithObjects:#"Mercedes", #"Opel", #"Ford", #"Mazda", nil];
The idea is when getRandomCar method is called the returned value to be different from the last returned i.e
Mescedes Opel Mercedes Mazda Ford Opel etc..
not
Mercedes Mercedes Mazda Opel Opel etc...
I have tried this but with no luck.
NSString *car = [[NSString alloc]initWithFormat:#"%#",
[carsArray objectAtIndex:arc4random() % [carsArray count]]];
if ([self.tmpCar isEqualToString:car])
{
car = [carsArray lastObject];
}
[self settmpCar:playerName];
Use this:
NSArray *carsArray = [NSArray arrayWithObjects:#"Mercedes", #"Opel", #"Ford", #"Mazda", nil];
NSMutableString *car = [[NSMutableString alloc] init];
NSMutableString *lastCar = [[NSMutableString alloc] init];
[lastCar setString:#"XXX"];
do {
[car setString:[NSString stringWithFormat:#"%#",
[carsArray objectAtIndex:arc4random() % [carsArray count]]]];
}
while ([lastCar isEqualToString:car]);
[lastCar setString:car];
I'd recommend you to remember the index of the last car. And when you generate a random number check if it's equal to the previous and, if so, generate another number. Sample code:
int test;
int lastValue;
for (int a = 0; a < 10; a++) {
test = arc4random() % 10;
if (test == lastValue) {
while (test == lastValue) {
test = arc4random();
NSLog(#"in While loop");
}
} else {
NSLog(#"%i", test);
}
lastValue = test;
}
Hope it helps
edit
Decided to add the code for you:
NSArray *cars = [NSArray arrayWithObjects:#"Mercedes", #"Opel", #"Ford", #"Mazda", nil];
int test;
int lastValue;
for (int a = 0; a < 20; a++) {
test = arc4random() % [cars count];
if (test == lastValue) {
while (test == lastValue) {
test = arc4random();
}
} else {
NSString *car = [cars objectAtIndex:test];
lastValue = test;
}
}
that will make 20 random non-repeatable cars
You can use another array to hold the cars that have already been picked up before. In the following example, once you exhaust the entire carsArray, method GetCar will return nil. Note that this may not be the most efficient way, but for a small size of carsArray it should work fine.
At the class level:
NSMutableArray *prevCars = [[NSMutableArray alloc] init];
Your method:
-(NSString*) GetCar {
if([prevCars count] == [carsArray count])
return nil;
NSMutableString *car = [[NSMutableString alloc] init];
do {
[car setString:[NSString stringWithFormat:#"%#",
[carsArray objectAtIndex:arc4random() % [carsArray count]]]];
}
while ([InArray:prevCars value:car])
[prevCars addObject:car];
return car;
}
And then:
-(BOOL)InArray:(NSArray *)arr value:(NSString *)val {
for (id Elem in arr) {
if([val isEqualToString:(NSString*)val])
return YES;
}
return NO;
}
Of course, make sure to free the memory when you're done.
I have an NSArrayController and I would like to sort the contents so that anything with English alphabets are sorted first and then anything with numbers and non English characters are sorted last.
For example: A, B , C ... Z, 1 , 2, 3 ... 9, 구, 결, ...
Currently I only know how to sort items in alphabetical order. Suggestions?
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[dataController setSortDescriptors: [NSArray arrayWithObject: sort]];
You can use sortedArrayUsingComparator to customize the sort algorithm to your needs. For instance, you can give precedence to symbols with this lines:
NSArray *assorted = [#"1 2 3 9 ; : 구 , 결 A B C Z ! á" componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSArray *sorted = [assorted sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
/* NSOrderedAscending, NSOrderedSame, NSOrderedDescending */
BOOL isPunct1 = [[NSCharacterSet punctuationCharacterSet] characterIsMember:[(NSString*)obj1 characterAtIndex:0]];
BOOL isPunct2 = [[NSCharacterSet punctuationCharacterSet] characterIsMember:[(NSString*)obj2 characterAtIndex:0]];
if (isPunct1 && !isPunct2) {
return NSOrderedAscending;
} else if (!isPunct1 && isPunct2) {
return NSOrderedDescending;
}
return [(NSString*)obj1 compare:obj2 options:NSDiacriticInsensitiveSearch|NSCaseInsensitiveSearch];
}];
To put English characters before non-English ones, it'd be enough to use NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch as options, no fancy algorithm required.
If you need to support iOS without blocks try sortedArrayUsingSelector.
Another solution by testing, if a string is latin1-encodeable:
test for both strings, if the first character is a latin (aka english) character
if both are, test if both starts either with a letter or a number. In both cases, leave it up the compare:
else, if one starts with the number and one with a letter, return NSOrderingAscending, if the one with letter its first, otherwise NSOrderingDescending
If both strings aren't latin, let compare: decide again
if one is latin, and one not, return NSOrderingAscending if the latin is first, otherwise NSOrderingDescending
the code
NSArray *array = [NSArray arrayWithObjects:#"peach",#"apple",#"7",#"banana",#"ananas",#"5", #"papaya",#"4",#"구",#"결",#"1" ,nil];
array = [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString *s1 = [obj1 substringToIndex:1];
NSString *s2 = [obj2 substringToIndex:1];
BOOL b1 = [s1 canBeConvertedToEncoding:NSISOLatin1StringEncoding];
BOOL b2 = [s2 canBeConvertedToEncoding:NSISOLatin1StringEncoding];
if ((b1 == b2) && b1) {//both number or latin char
NSRange r1 = [s1 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
NSRange r2 = [s2 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
if (r1.location == r2.location ) { // either both start with a number or both with a letter
return [obj1 compare:obj2 options:NSDiacriticInsensitiveSearch|NSCaseInsensitiveSearch];
} else { // one starts wit a letter, the other with a number
if ([s1 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location == NSNotFound) {
return NSOrderedAscending;
}
return NSOrderedDescending;
}
} else if((b1 == b2) && !b1){ // neither latin char
return [obj1 compare:obj2 options:NSDiacriticInsensitiveSearch|NSCaseInsensitiveSearch];
} else { //one is latin char, other not
if (b1) return NSOrderedAscending;
return NSOrderedDescending;
}
}];
for (NSString *s in array) NSLog(#"%#", s);
result
ananas
apple
banana
papaya
peach
1
4
5
7
구
결
I don't think you can do that kind of sorting without defining your own comparison function.
To this aim, you could use sortedArrayUsingFunction:
[array sortedArrayUsingFunction:f context:userContext];
where f is defined as:
NSInteger f(id num1, id num2, void *context)
{
int v1 = [num1 intValue];
int v2 = [num2 intValue];
if (...)
return NSOrderedAscending;
else if (...)
return NSOrderedDescending;
else
return NSOrderedSame;
}
If you prefer not creating function for doing this you could use the block-version of the method, sortedArrayUsingComparator:
[array sortedArrayUsingComparator: ^(id obj1, id obj2) {
return NSOrderedSame;
}];
A sort descriptor based on a comparator should do the trick (note: not tested).
NSComparator cmp = ^(id str1, id str2) {
// Make your sorting
if ( /* str1 before str2 */ )
return NSOrderedAscending
else if ( /* str2 after str1 */ )
return NSOrderedDescending
else
return NSOrderedSame
};
NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey: sortKey ascending: YES comparator: cmp];
NSArrayController *ac = // ...
[ac setSortDescriptor: sd];
You of course have to define your own sort order algorithm - but this example should show how to use a sort descriptor for an array controller.
One thing is missing to answer properly to the question : NSNumericSearch
NSArray *assorted = [#"1 2 3 9 ; : 구 , 결 A B C Z ! á" componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSArray *sorted = [assorted sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
/* NSOrderedAscending, NSOrderedSame, NSOrderedDescending */
BOOL isPunct1 = [[NSCharacterSet punctuationCharacterSet] characterIsMember:[(NSString*)obj1 characterAtIndex:0]];
BOOL isPunct2 = [[NSCharacterSet punctuationCharacterSet] characterIsMember:[(NSString*)obj2 characterAtIndex:0]];
if (isPunct1 && !isPunct2) {
return NSOrderedAscending;
} else if (!isPunct1 && isPunct2) {
return NSOrderedDescending;
}
return [(NSString*)obj1 compare:obj2 options:NSDiacriticInsensitiveSearch|NSCaseInsensitiveSearch|NSNumericSearch]|;
}];
I make an NSMutableArray(listId) (through a call to a sqlite database) with dictionaries(dizionario):
-(void)readItems {
NSString * query = #"SELECT * FROM item";
NSArray * arrayQuery = [[NSArray alloc] initWithObjects:#"id",#"title",#"note",#"icon",#"perc",#"complete",nil];
NSArray * arrayEle = [self caricaValoriDaDBStandard:query :arrayQuery];
for (int i = 0; i < [arrayEle count]; i++)
{
NSMutableDictionary *dizionario = [arrayEle objectAtIndex:i];
[listId addObject:dizionario];
}
}
How do I sort the array, for example, for the "id" key?
You can use the following code:
NSArray sortedArray = [listId sortedArrayUsingComparator: ^(id a, id b) {
NSString *first = [a objectForKey:#"id"];
NSString *second = [b objectForKey:#"id"];
return [first compare:second];
}];