วิธีที่ดีที่สุดในการ tokenize / แยก NSString ใน Objective-C คืออะไร?
วิธีที่ดีที่สุดในการ tokenize / แยก NSString ใน Objective-C คืออะไร?
คำตอบ:
พบสิ่งนี้ได้ที่http://borkware.com/quickies/one?topic=NSString (ลิงค์ที่มีประโยชน์):
NSString *string = @"oop:ack:bork:greeble:ponies";
NSArray *chunks = [string componentsSeparatedByString: @":"];
หวังว่านี่จะช่วยได้!
อาดัม
componentsSeparatedByCharactersInSet
ผมคิดว่าสิ่งที่คุณต้องการก็คือ ดูคำตอบด้านล่าง
ทุกคนได้กล่าวถึงcomponentsSeparatedByString:
แต่คุณยังสามารถใช้CFStringTokenizer
(โปรดจำไว้ว่าNSString
และCFString
สามารถใช้แทนกันได้) ซึ่งจะทำหน้าที่เป็นภาษาธรรมชาติด้วยเช่นกัน (เช่นจีน / ญี่ปุ่นซึ่งไม่แบ่งคำในช่องว่าง)
enumerateLinesUsingBlock:
และenumerateSubstringsInRange:options:usingBlock:
ส่วนหลังซึ่งเป็น CFStringTokenizer เวอร์ชันบล็อกที่ใช้บล็อก developer.apple.com/mac/library/documentation/Cocoa/Reference/... : developer.apple.com/mac/library/documentation/Cocoa/Reference/... :
enumerate
วิธีการที่มีอยู่ใน iOS 4 และต่อมาอีกด้วย
-[NSString componentsSeparatedByString:]
ถ้าคุณเพียงต้องการที่จะแยกสตริงใช้ สำหรับโทเค็นที่ซับซ้อนมากขึ้นให้ใช้คลาส NSScanner
หากความต้องการโทเค็นของคุณซับซ้อนมากขึ้นลองดูชุดเครื่องมือโกโก้ / การแยกวิเคราะห์โอเพนซอร์สของฉัน: ParseKit:
สำหรับการแยกสตริงอย่างง่ายโดยใช้ตัวคั่นถ่าน (เช่น ':') ParseKit จะเป็น overkill อย่างแน่นอน แต่อีกครั้งสำหรับความต้องการโทเค็นที่ซับซ้อน ParseKit นั้นทรงพลัง / ยืดหยุ่นอย่างมาก
ยังเห็นเอกสาร ParseKit Tokenization
หากคุณต้องการ tokenize ตัวละครหลาย ๆ คุณสามารถใช้ของ componentsSeparatedByCharactersInSet
NSString NSCharacterSet มีบางชุดที่ทำไว้ล่วงหน้าที่มีประโยชน์เช่นและwhitespaceCharacterSet
illegalCharacterSet
และมันมี initializers สำหรับช่วง 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
หากคุณต้องการโทเค็นสตริงลงในคำค้นหาในขณะที่รักษา "วลีที่ยกมา" นี่เป็น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
หากคุณกำลังมองหาการแยกคุณสมบัติทางภาษาของสตริง (คำ, ย่อหน้า, ตัวอักษร, ประโยคและบรรทัด) ให้ใช้การแจงนับสตริง:
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
เป็นวิธีที่เหมาะสมในการแจกแจงตัวละครเนื่องจากอักขระที่ไม่ใช่ตะวันตกหลายตัวมีความยาวมากกว่าหนึ่งไบต์
ฉันมีกรณีที่ฉันต้องแยกเอาต์พุตคอนโซลหลังเคียวรี LDAP ด้วย ldapsearch ก่อนอื่นให้ตั้งค่าและดำเนินการ NSTask (ฉันพบตัวอย่างโค้ดที่ดีที่นี่: เรียกใช้งานคำสั่งเทอร์มินัลจากแอป Cocoa ) แต่แล้วฉันก็ต้องแยกและแยกวิเคราะห์ผลลัพธ์เพื่อแยกเฉพาะชื่อเซิร์ฟเวอร์เครื่องพิมพ์ออกจาก Ldap-query-output น่าเสียดายที่การจัดการสตริงค่อนข้างน่าเบื่อซึ่งจะไม่มีปัญหาเลยหากเราต้องจัดการ C-strings / arrays ด้วยการทำงานของ C-array อย่างง่าย นี่คือรหัสของฉันโดยใช้วัตถุโกโก้ หากคุณมีข้อเสนอแนะที่ดีกว่าแจ้งให้เราทราบ
//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 /programming/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];
}
}
ฉันมีตัวเองเจออินสแตนซ์ที่มันไม่เพียงพอที่จะแยกสตริงโดยองค์ประกอบหลายอย่างเช่น
1) การจัดประเภทโทเค็นเป็นประเภท
2) การเพิ่มโทเค็นใหม่
3) การแยกโทเค็น3) การแยกสตริงระหว่าง "
สำหรับความต้องการใด ๆ ที่ฉันพบParse Kitช่วยชีวิต
ฉันใช้มันในการแยกวิเคราะห์ไฟล์. PGN (สัญกรณ์เล่นเกม prtable) ประสบความสำเร็จอย่างรวดเร็วและ lite
[anArray componentsJoinedByString:@":"];
เป็นข้อมูลอ้างอิงให้กับผู้อ่านในอนาคตผมอยากจะทราบว่าตรงข้ามเป็น