2016-10-03 2 views
1

J'ai la liste des ingrédients comme ci-dessous dans NSArray comme objet NSString.Comment trier les ingrédients dans NSString? Ex. 1/2 tasse de lait, 1/4 cuillère de poudre

"1/2 Cup Milk", 
"125 grams Cashew", 
"2 green onions", 
"1/4 Cup Sugar", 
"1/6 Spoon Salt", 
"3/2 XYZ", 
"One cup water", 
"Almond oil" 

Et je veux les trier comme ci-dessous.

"1/6 Spoon Salt", 
"1/4 Cup Sugar", 
"1/2 Cup Milk", 
"3/2 XYZ", 
"2 green onions", 
"125 grams Cashew", 
"Almond oil", 
"One cup water", 

Tentative 1: Trier en utilisant localizedStandardCompare & NSNumericSearch les résultats sont les mêmes.

Résultat:

"1/2 Cup Milk", 
"1/4 Cup Sugar", 
"1/6 Spoon Salt", 
"2 green onions", 
"3/2 XYZ", 
"125 grams Cashew", 
"Almond oil", 
"One cup water" 

Je sais qu'il est possible, mais je suis en quelque sorte incapable de comprendre.

Si quelqu'un a fait une chose similaire, vous pouvez me guider.

Merci, D'avance.

+0

Avez-vous essayé de faire un tri numérique sur le tableau de chaînes (il y a beaucoup d'exemples montrant comment)? Je ne sais pas s'il traite correctement les fractions. – rmaddy

+0

Voir http://stackoverflow.com/questions/2846301/how-to-do-a-natural-sort-on-an-nsarray et dites-nous si cela fonctionne avec vos fractions. – rmaddy

+0

@rmaddy Ok, laissez-moi essayer celui-ci. –

Répondre

2

Vous pouvez définir une catégorie sur NSString de comparer les chiffres (y compris les fractions):

@interface NSString (Number) 
- (NSNumber * _Nullable)numberValue; 
- (NSComparisonResult)compareNumber:(NSString *)string; 
@end 

@implementation NSString (Number) 

- (NSNumber *)numberValue { 
    NSError *error; 
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^\\s*(\\d+)\\s*/\\s*(\\d+)" options:0 error:&error]; 
    NSAssert(regex, @"%@", error.localizedDescription); 

    NSTextCheckingResult *match = [regex firstMatchInString:self options:0 range:NSMakeRange(0, self.length)]; 
    if (match) { 
     float numerator = [[self substringWithRange:[match rangeAtIndex:1]] floatValue]; 
     float denominator = [[self substringWithRange:[match rangeAtIndex:2]] floatValue]; 
     return denominator ? @(numerator/denominator) : nil; 
    } 

    regex = [NSRegularExpression regularExpressionWithPattern:@"^\\s*(\\d+)" options:0 error:&error]; 
    match = [regex firstMatchInString:self options:0 range:NSMakeRange(0, self.length)]; 
    if (match) { 
     return @([self floatValue]); 
    } 

    // if you don't want it to recognize spelt out numbers, comment the following bit of code 

    regex = [NSRegularExpression regularExpressionWithPattern:@"^\\s*(\\S+)" options:0 error:&error]; 
    match = [regex firstMatchInString:self options:0 range:NSMakeRange(0, self.length)]; 
    if (match) { 
     NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; 
     formatter.numberStyle = NSNumberFormatterSpellOutStyle; 
     return [formatter numberFromString:[[self substringWithRange:[match rangeAtIndex:1]] lowercaseString]]; 
    } 

    return nil; 
} 

- (NSComparisonResult)compareNumber:(NSString *)string { 
    NSNumber *number1 = [self numberValue]; 
    NSNumber *number2 = [string numberValue]; 

    if (number1 && !number2) { 
     return NSOrderedAscending; 
    } 

    if (number2 && !number1) { 
     return NSOrderedDescending; 
    } 

    if (number1 && number2) { 
     NSComparisonResult numericComparison = [number1 compare:number2]; 
     if (numericComparison != NSOrderedSame) { 
      return numericComparison; 
     } 
    } 

    return [self caseInsensitiveCompare:string]; 
} 

@end 

Vous pouvez ensuite trier les ingrédients comme ceci:

NSArray *ingredients = @[@"1/2 Cup Milk", 
         @"125 grams Cashew", 
         @"2 green onions", 
         @"1/4 Cup Sugar", 
         @"1/6 Spoon Salt", 
         @"3/2 XYZ", 
         @"One cup water", 
         @"Almond oil"]; 

NSArray *sorted = [ingredients sortedArrayUsingComparator:^NSComparisonResult(NSString * _Nonnull obj1, NSString * _Nonnull obj2) { 
    return [obj1 compareNumber:obj2]; 
}]; 

qui donne:

"1/6 Spoon Salt", 
"1/4 Cup Sugar", 
"1/2 Cup Milk", 
"One cup water", 
"3/2 XYZ", 
"2 green onions", 
"125 grams Cashew", 
"Almond oil" 

Je dois avouer que la logique NSNumberFormatter pour les nombres épelés isn 't robuste (il reconnaît "Trente-deux", mais pas "Trente-deux"), mais vous pouvez jouer avec si vous le souhaitez. Ou vous pourriez tirer cela entièrement.

+0

Pouah! Je viens de passer les 20 dernières minutes à écrire le même code pour trouver votre réponse ici. – rmaddy

+1

C'est un frein. Désolé :( – Rob

+1

Pas de problème, c'était une petite distraction amusante, j'ai posté la mienne quand même pour montrer une légère variation – rmaddy

2

Il semble que Rob a posté sa bonne réponse pendant que j'étais occupé à écrire le mien. Depuis que j'ai passé le temps je peux aussi bien poster le mien. Ils sont similaires mais différents.

J'ai commencé avec une extension de NSString ainsi:

@interface NSString (NumberSort) 

- (nullable NSNumber *)leadingNumber; 

@end 

@implementation NSString (NumberSort) 

- (nullable NSNumber *)leadingNumber { 
    if (self.length < 1) return nil; 

    // See if the string starts with a digit 
    unichar first = [self characterAtIndex:0]; 
    if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:first]) { 
     // It does so now get the first word (number) 
     NSString *numStr = self; 
     NSRange spaceRange = [self rangeOfString:@" "]; 
     if (spaceRange.location != NSNotFound) { 
      numStr = [self substringToIndex:spaceRange.location]; 
     } 

     // Now see if the leading number is actually a fraction 
     NSRange slashRange = [numStr rangeOfString:@"/"]; 
     if (slashRange.location != NSNotFound) { 
      // It's a fraction. Compute its value 
      NSString *numeratorStr = [[numStr substringToIndex:slashRange.location] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 
      NSString *denominatorStr = [[numStr substringFromIndex:slashRange.location + slashRange.length] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 

      NSNumberFormatter *fmt = [[NSNumberFormatter alloc] init]; 
      fmt.numberStyle = NSNumberFormatterDecimalStyle; 
      NSNumber *numerator = [fmt numberFromString:numeratorStr]; 
      NSNumber *denominator = [fmt numberFromString:denominatorStr]; 
      if (numerator && denominator) { 
       return @([numerator doubleValue]/[denominator doubleValue]); 
      } 
     } else { 
      // Not a fraction, convert number string to number 
      NSNumberFormatter *fmt = [[NSNumberFormatter alloc] init]; 
      fmt.numberStyle = NSNumberFormatterDecimalStyle; 
      NSNumber *num = [fmt numberFromString:numStr]; 

      return num; 
     } 
    } else { 
     // See if string starts with spelled out number 
     NSString *numStr = self; 
     NSRange spaceRange = [self rangeOfString:@" "]; 
     if (spaceRange.location != NSNotFound) { 
      numStr = [self substringToIndex:spaceRange.location]; 
     } 

     NSNumberFormatter *fmt = [[NSNumberFormatter alloc] init]; 
     fmt.numberStyle = NSNumberFormatterSpellOutStyle; 
     NSNumber *num = [fmt numberFromString:[numStr lowercaseString]]; 

     return num; 
    } 

    return nil; 
} 

@end 

J'ai ensuite utilisé d'un simple bloc de comparaison pour traiter le tableau:

NSArray *ingrediants = @[ 
    @"1/2 Cup Milk", 
    @"125 grams Cashew", 
    @"2 green onions", 
    @"1/4 Cup Sugar", 
    @"1/6 Spoon Salt", 
    @"3/2 XYZ", 
    @"One cup water", 
    @"Almond oil" 
]; 

NSArray *sorted = [ingrediants sortedArrayUsingComparator:^NSComparisonResult(NSString * _Nonnull str1, NSString * _Nonnull str2) { 
    NSNumber *num1 = [str1 leadingNumber]; 
    NSNumber *num2 = [str2 leadingNumber]; 

    if (num1) { 
     if (num2) { 
      return [num1 compare:num2]; 
     } else { 
      return NSOrderedAscending; 
     } 
    } else { 
     if (num2) { 
      return NSOrderedDescending; 
     } else { 
      return [str1 compare:str2 options:NSCaseInsensitiveSearch]; 
     } 
    } 
}]; 

NSLog(@"Ordered: %@", sorted); 

Sortie:

 
Ordered: (
    "1/6 Spoon Salt", 
    "1/4 Cup Sugar", 
    "1/2 Cup Milk", 
    "One cup water", 
    "3/2 XYZ", 
    "2 green onions", 
    "125 grams Cashew", 
    "Almond oil" 
) 

Mon le code souffre d'un problème similaire quand il s'agit de gérer des nombres épelés. Mon code, tel qu'il est, ne gère que les nombres d'un mot. Il ne faudrait pas trop de temps pour gérer des nombres épelés en plusieurs mots si nécessaire.

Mon code exige également qu'il n'y ait aucun espace dans aucune fraction. Encore une fois, un peu de travail pourrait contourner cette limitation.

+0

merci pour votre temps.Vous avez également raison –

+0

heureux d'aider.N'oubliez pas que vous pouvez voter jusqu'à un certain nombre de réponses en plus d'accepter un. – rmaddy

+0

Ouais cela a fonctionné :) –