Токенизация NSString в Objective-C

Как лучше всего токенизировать / разделить NSString в Objective-C?


person Community    schedule 03.11.2008    source источник


Ответы (9)


Нашел это на http://borkware.com/quickies/one?topic=NSString ( полезная ссылка):

NSString *string = @"oop:ack:bork:greeble:ponies";
NSArray *chunks = [string componentsSeparatedByString: @":"];

Надеюсь это поможет!

Адам

person Adam Alexander    schedule 03.11.2008
comment
Обращаясь к будущим читателям, хочу отметить, что [anArray componentsJoinedByString:@":"]; наоборот. - person Ivan Vučica; 06.02.2012
comment
спасибо, но как разделить NSString, разделенный большим количеством токенов? (Если вы понимаете, о чем я, я не очень хорошо говорю по-английски) @Adam - person 11684; 09.04.2012
comment
@ Адам, я думаю, что ты хотел componentsSeparatedByCharactersInSet. См. Ответ ниже. - person Wienke; 28.08.2012

Все упоминали componentsSeparatedByString:, но вы также можете использовать CFStringTokenizer (помните, что NSString и CFString взаимозаменяемы), которые также будут токенизировать естественные языки (например, китайский / японский, которые не разделяют слова на пробелы).

person Matt Gallagher    schedule 11.01.2009
comment
А в Mac OS X 10.6 и более поздних версиях NSString имеет методы enumerateLinesUsingBlock: и enumerateSubstringsInRange:options:usingBlock:, последний из которых является блочной версией CFStringTokenizer. developer.apple.com/mac/library/documentation/Cocoa/Reference/: developer.apple.com/mac/library/documentation/Cocoa / Справка /: - person Peter Hosey; 04.02.2010
comment
enumerate методы также доступны в iOS 4 и новее. - person bugloaf; 04.04.2013

Если вы просто хотите разделить строку, используйте -[NSString componentsSeparatedByString:]. Для более сложной токенизации используйте класс NSScanner.

person Chris Hanson    schedule 04.11.2008

Если ваши потребности в токенизации более сложные, ознакомьтесь с моим набором инструментов для токенизации / анализа строк Cocoa с открытым исходным кодом: ParseKit:

http://parsekit.com

Для простого разделения строк с использованием символа-разделителя (например, ':') ParseKit определенно будет излишним. Но опять же, для сложных нужд токенизации ParseKit чрезвычайно мощный / гибкий.

Также см. документацию по токенизации ParseKit.

person Todd Ditchendorf    schedule 06.11.2008
comment
Это все еще работает? Я попробовал и получил пару ошибок, которые не хочу исправлять сам. - person griotspeak; 09.04.2011
comment
Хм? В живых? Да, проект ParseKit активно поддерживается. Однако комментарии здесь - не лучшее место для сообщений об ошибках в проекте. Это как в Google Code, так и в Github, если вам нужно сообщать об ошибках. - person Todd Ditchendorf; 14.02.2012
comment
Звучит хорошо, но теперь я не могу снять свой голос против, пока вы как-нибудь не отредактируете ответ (правила сайта). Возможно, вы могли бы отметить, над какими версиями он работает, использует ли он ARC и т. Д.? Или вы можете просто добавить место где-нибудь, решать вам :) - person Dan Rosenstark; 14.02.2012

Если вы хотите токенизировать несколько символов, вы можете использовать componentsSeparatedByCharactersInSet из NSString. NSCharacterSet имеет несколько удобных готовых наборов, таких как whitespaceCharacterSet и illegalCharacterSet. И у него есть инициализаторы для диапазонов Unicode.

Вы также можете комбинировать наборы символов и использовать их для токенизации, например:

// Tokenize sSourceEntityName on both whitespace and punctuation.
NSMutableCharacterSet *mcharsetWhitePunc = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
[mcharsetWhitePunc formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
NSArray *sarrTokenizedName = [self.sSourceEntityName componentsSeparatedByCharactersInSet:mcharsetWhitePunc];
[mcharsetWhitePunc release];

Имейте в виду, что componentsSeparatedByCharactersInSet будет генерировать пустые строки, если встретит более одного члена charSet в строке, поэтому вы можете проверить длину меньше 1.

person Wienke    schedule 28.08.2012
comment
Не обращается к языкам, в которых пробелы вообще не разделяют все логические токены. Плохое решение. - person uchuugaka; 10.12.2013
comment
@uchuugaka В этом случае вы должны использовать другой набор символов или наборы для токенизации. Я просто использую конкретные примеры, чтобы проиллюстрировать общую концепцию. - person Wienke; 24.12.2013

Если вы хотите преобразовать строку в поисковые запросы с сохранением «цитируемых фраз», вот NSString категория, которая учитывает различные типы пар цитат: "" '' ‘’ “”

Использование:

NSArray *terms = [@"This is my \"search phrase\" I want to split" searchTerms];
// results in: ["This", "is", "my", "search phrase", "I", "want", "to", "split"]

Код:

@interface NSString (Search)
- (NSArray *)searchTerms;
@end

@implementation NSString (Search)

- (NSArray *)searchTerms {

    // Strip whitespace and setup scanner
    NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    NSString *searchString = [self stringByTrimmingCharactersInSet:whitespace];
    NSScanner *scanner = [NSScanner scannerWithString:searchString];
    [scanner setCharactersToBeSkipped:nil]; // we'll handle whitespace ourselves

    // A few types of quote pairs to check
    NSDictionary *quotePairs = @{@"\"": @"\"",
                                 @"'": @"'",
                                 @"\u2018": @"\u2019",
                                 @"\u201C": @"\u201D"};

    // Scan
    NSMutableArray *results = [[NSMutableArray alloc] init];
    NSString *substring = nil;
    while (scanner.scanLocation < searchString.length) {
        // Check for quote at beginning of string
        unichar unicharacter = [self characterAtIndex:scanner.scanLocation];
        NSString *startQuote = [NSString stringWithFormat:@"%C", unicharacter];
        NSString *endQuote = [quotePairs objectForKey:startQuote];
        if (endQuote != nil) { // if it's a valid start quote we'll have an end quote
            // Scan quoted phrase into substring (skipping start & end quotes)
            [scanner scanString:startQuote intoString:nil];
            [scanner scanUpToString:endQuote intoString:&substring];
            [scanner scanString:endQuote intoString:nil];
        } else {
            // Single word that is non-quoted
            [scanner scanUpToCharactersFromSet:whitespace intoString:&substring];
        }
        // Process and add the substring to results
        if (substring) {
            substring = [substring stringByTrimmingCharactersInSet:whitespace];
            if (substring.length) [results addObject:substring];
        }
        // Skip to next word
        [scanner scanCharactersFromSet:whitespace intoString:nil];
    }

    // Return non-mutable array
    return results.copy;

}

@end
person Michael Waterfall    schedule 31.03.2014

Если вы ищете разделение лингвистических функций строки (слов, абзацев, символов, предложений и строк), используйте перечисление строк:

NSString * string = @" \n word1!    word2,%$?'/word3.word4   ";

[string enumerateSubstringsInRange:NSMakeRange(0, string.length)
                           options:NSStringEnumerationByWords
                        usingBlock:
 ^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
     NSLog(@"Substring: '%@'", substring);
 }];

 // Logs:
 // Substring: 'word1'
 // Substring: 'word2'
 // Substring: 'word3'
 // Substring: 'word4' 

Этот api работает с другими языками, в которых пробелы не всегда являются разделителями (например, с японским). Также использование NSStringEnumerationByComposedCharacterSequences является правильным способом перечисления символов, так как многие не западные символы имеют длину более одного байта.

person Robert    schedule 03.08.2014
comment
Начиная с ios12, можно также рассмотреть NLTokenizer (developer.apple.com/documentation/naturallanguage/) - person qix; 11.11.2020

У меня был случай, когда мне пришлось разделить вывод консоли после запроса LDAP с ldapsearch. Сначала настройте и выполните NSTask (здесь я нашел хороший пример кода: Выполните команду терминала из приложения Какао). Но затем мне пришлось разделить и проанализировать вывод, чтобы извлечь только имена серверов печати из вывода Ldap-query-output. К сожалению, это довольно утомительное манипулирование строками, которое не было бы проблемой, если бы мы манипулировали C-строками / массивами с помощью простых операций с C-массивами. Итак, вот мой код, использующий объекты какао. Если у вас есть предложения получше, дайте мне знать.

//as the ldap query has to be done when the user selects one of our Active Directory Domains
//(an according comboBox should be populated with print-server names we discover from AD)
//my code is placed in the onSelectDomain event code

//the following variables are declared in the interface .h file as globals
@protected NSArray* aDomains;//domain combo list array
@protected NSMutableArray* aPrinters;//printer combo list array
@protected NSMutableArray* aPrintServers;//print server combo list array

@protected NSString* sLdapQueryCommand;//for LDAP Queries
@protected NSArray* aLdapQueryArgs;
@protected NSTask* tskLdapTask;
@protected NSPipe* pipeLdapTask;
@protected NSFileHandle* fhLdapTask;
@protected NSMutableData* mdLdapTask;

IBOutlet NSComboBox* comboDomain;
IBOutlet NSComboBox* comboPrinter;
IBOutlet NSComboBox* comboPrintServer;
//end of interface globals

//after collecting the print-server names they are displayed in an according drop-down comboBox
//as soon as the user selects one of the print-servers, we should start a new query to find all the
//print-queues on that server and display them in the comboPrinter drop-down list
//to find the shares/print queues of a windows print-server you need samba and the net -S command like this:
// net -S yourPrintServerName.yourBaseDomain.com -U yourLdapUser%yourLdapUserPassWord -W adm rpc share -l
//which dispalays a long list of the shares

- (IBAction)onSelectDomain:(id)sender
{
    static int indexOfLastItem = 0; //unfortunately we need to compare this because we are called also if the selection did not change!

    if ([comboDomain indexOfSelectedItem] != indexOfLastItem && ([comboDomain indexOfSelectedItem] != 0))
    {

        indexOfLastItem = [comboDomain indexOfSelectedItem]; //retain this index for next call

    //the print-servers-list has to be loaded on a per univeristy or domain basis from a file dynamically or from AN LDAP-QUERY

    //initialize an LDAP-Query-Task or console-command like this one with console output
    /*

     ldapsearch -LLL -s sub -D "cn=yourLdapUser,ou=yourOuWithLdapUserAccount,dc=yourDomain,dc=com" -h "yourLdapServer.com" -p 3268 -w "yourLdapUserPassWord" -b "dc=yourBaseDomainToSearchIn,dc=com" "(&(objectcategory=computer)(cn=ps*))" "dn"

//our print-server names start with ps* and we want the dn as result, wich comes like this:

     dn: CN=PSyourPrintServerName,CN=Computers,DC=yourBaseDomainToSearchIn,DC=com

     */

    sLdapQueryCommand = [[NSString alloc] initWithString: @"/usr/bin/ldapsearch"];


    if ([[comboDomain stringValue] compare: @"firstDomain"] == NSOrderedSame) {

      aLdapQueryArgs = [NSArray arrayWithObjects: @"-LLL",@"-s", @"sub",@"-D", @"cn=yourLdapUser,ou=yourOuWithLdapUserAccount,dc=yourDomain,dc=com",@"-h", @"yourLdapServer.com",@"-p",@"3268",@"-w",@"yourLdapUserPassWord",@"-b",@"dc=yourFirstDomainToSearchIn,dc=com",@"(&(objectcategory=computer)(cn=ps*))",@"dn",nil];
    }
    else {
      aLdapQueryArgs = [NSArray arrayWithObjects: @"-LLL",@"-s", @"sub",@"-D", @"cn=yourLdapUser,ou=yourOuWithLdapUserAccount,dc=yourDomain,dc=com",@"-h", @"yourLdapServer.com",@"-p",@"3268",@"-w",@"yourLdapUserPassWord",@"-b",@"dc=yourSecondDomainToSearchIn,dc=com",@"(&(objectcategory=computer)(cn=ps*))",@"dn",nil];

    }


    //prepare and execute ldap-query task

    tskLdapTask = [[NSTask alloc] init];
    pipeLdapTask = [[NSPipe alloc] init];//instead of [NSPipe pipe]
    [tskLdapTask setStandardOutput: pipeLdapTask];//hope to get the tasks output in this file/pipe

    //The magic line that keeps your log where it belongs, has to do with NSLog (see https://stackoverflow.com/questions/412562/execute-a-terminal-command-from-a-cocoa-app and here http://www.cocoadev.com/index.pl?NSTask )
    [tskLdapTask setStandardInput:[NSPipe pipe]];

    //fhLdapTask  = [[NSFileHandle alloc] init];//would be redundand here, next line seems to do the trick also
    fhLdapTask = [pipeLdapTask fileHandleForReading];
    mdLdapTask  = [NSMutableData dataWithCapacity:512];//prepare capturing the pipe buffer which is flushed on read and can overflow, start with 512 Bytes but it is mutable, so grows dynamically later
    [tskLdapTask setLaunchPath: sLdapQueryCommand];
    [tskLdapTask setArguments: aLdapQueryArgs];

#ifdef bDoDebug
    NSLog (@"sLdapQueryCommand: %@\n", sLdapQueryCommand);
    NSLog (@"aLdapQueryArgs: %@\n", aLdapQueryArgs );
    NSLog (@"tskLdapTask: %@\n", [tskLdapTask arguments]);
#endif

    [tskLdapTask launch];

    while ([tskLdapTask isRunning]) {
      [mdLdapTask appendData: [fhLdapTask readDataToEndOfFile]];
    }
    [tskLdapTask waitUntilExit];//might be redundant here.

    [mdLdapTask appendData: [fhLdapTask readDataToEndOfFile]];//add another read for safety after process/command stops

    NSString* sLdapOutput = [[NSString alloc] initWithData: mdLdapTask encoding: NSUTF8StringEncoding];//convert output to something readable, as NSData and NSMutableData are mere byte buffers

#ifdef bDoDebug
    NSLog(@"LdapQueryOutput: %@\n", sLdapOutput);
#endif

    //Ok now we have the printservers from Active Directory, lets parse the output and show the list to the user in its combo box
    //output is formatted as this, one printserver per line
    //dn: CN=PSyourPrintServer,OU=Computers,DC=yourBaseDomainToSearchIn,DC=com

    //so we have to search for "dn: CN=" to retrieve each printserver's name
    //unfortunately splitting this up will give us a first line containing only "" empty string, which we can replace with the word "choose"
    //appearing as first entry in the comboBox

    aPrintServers = (NSMutableArray*)[sLdapOutput componentsSeparatedByString:@"dn: CN="];//split output into single lines and store it in the NSMutableArray aPrintServers

#ifdef bDoDebug
    NSLog(@"aPrintServers: %@\n", aPrintServers);
#endif

    if ([[aPrintServers objectAtIndex: 0 ] compare: @"" options: NSLiteralSearch] == NSOrderedSame){
      [aPrintServers replaceObjectAtIndex: 0 withObject: slChoose];//replace with localized string "choose"

#ifdef bDoDebug
      NSLog(@"aPrintServers: %@\n", aPrintServers);
#endif

    }

//Now comes the tedious part to extract only the print-server-names from the single lines
    NSRange r;
    NSString* sTemp;

    for (int i = 1; i < [aPrintServers count]; i++) {//skip first line with "choose". To get rid of the rest of the line, we must isolate/preserve the print server's name to the delimiting comma and remove all the remaining characters
      sTemp = [aPrintServers objectAtIndex: i];
      sTemp = [sTemp stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];//remove newlines and line feeds

#ifdef bDoDebug
      NSLog(@"sTemp: %@\n", sTemp);
#endif
      r = [sTemp rangeOfString: @","];//now find first comma to remove the whole rest of the line
      //r.length = [sTemp lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
      r.length = [sTemp length] - r.location;//calculate number of chars between first comma found and lenght of string
#ifdef bDoDebug
      NSLog(@"range: %i, %i\n", r.location, r.length);
#endif

      sTemp = [sTemp stringByReplacingCharactersInRange:r withString: @"" ];//remove rest of line
#ifdef bDoDebug
      NSLog(@"sTemp after replace: %@\n", sTemp);
#endif

      [aPrintServers replaceObjectAtIndex: i withObject: sTemp];//put back string into array for display in comboBox

#ifdef bDoDebug
      NSLog(@"aPrintServer: %@\n", [aPrintServers objectAtIndex: i]);
#endif

    }

    [comboPrintServer removeAllItems];//reset combo box
    [comboPrintServer addItemsWithObjectValues:aPrintServers];
    [comboPrintServer setNumberOfVisibleItems:aPrintServers.count];
    [comboPrintServer selectItemAtIndex:0];

#ifdef bDoDebug
    NSLog(@"comboPrintServer reloaded with new values.");
#endif


//release memory we used for LdapTask
    [sLdapQueryCommand release];
    [aLdapQueryArgs release];
    [sLdapOutput release];

    [fhLdapTask release];

    [pipeLdapTask release];
//    [tskLdapTask release];//strangely can not be explicitely released, might be autorelease anyway
//    [mdLdapTask release];//strangely can not be explicitely released, might be autorelease anyway

    [sTemp release];

    }
}
person Rosario Carcò    schedule 01.05.2013

Я сам сталкивался с экземпляром, где было недостаточно просто разделить строку по компоненту для многих задач, таких как
1) Категоризация токена по типам
2) Добавление новых токенов
3) Разделение строки между настраиваемыми замыканиями, такими как все слова между "{" и "}"
Для любых таких требований я нашел Parse Kit спасательным средством.

Я использовал его для успешного анализа файлов .PGN (игровая нотация prtable), он очень быстрый и легкий.

person amar    schedule 15.01.2014