Pregunta Enumerar caracteres NSString mediante puntero


¿Cómo puedo enumerar NSString sacando cada unichar de él? Puedo usar characterAtIndex, pero eso es más lento que hacerlo mediante un unichar incremental *. No vi nada en la documentación de Apple que no requiriera copiar la cadena en un segundo buffer.

Algo como esto sería ideal:

for (unichar c in string) { ... }

o

unichar* ptr = (unichar*)string;

5
2018-04-17 20:48


origen


Respuestas:


Puedes acelerar -characterAtIndex: convirtiéndolo primero en su forma IMP:

NSString *str = @"This is a test";

NSUInteger len = [str length]; // only calling [str length] once speeds up the process as well
SEL sel = @selector(characterAtIndex:);

// using typeof to save my fingers from typing more
unichar (*charAtIdx)(id, SEL, NSUInteger) = (typeof(charAtIdx)) [str methodForSelector:sel];

for (int i = 0; i < len; i++) {
    unichar c = charAtIdx(str, sel, i);
    // do something with C
    NSLog(@"%C", c);
}  

EDITAR: parece que el CFString Referencia contiene el siguiente método:

const UniChar *CFStringGetCharactersPtr(CFStringRef theString);

Esto significa que puedes hacer lo siguiente:

const unichar *chars = CFStringGetCharactersPtr((__bridge CFStringRef) theString);

while (*chars)
{
    // do something with *chars
    chars++;
}

Si no desea asignar memoria para hacer frente al búfer, este es el camino a seguir.


11
2018-04-17 21:05



Su única opción es copiar los caracteres en un nuevo búfer. Esto se debe a que la clase NSString no garantiza que haya un búfer interno que pueda usar. La mejor manera de hacer esto es usar el getCharacters:range: método.

NSUInteger i, length = [string length];
unichar *buffer = malloc(sizeof(unichar) * length);
NSRange range = {0,length};
[string getCharacters:buffer range:range];
for(i = 0; i < length; ++i) {
    unichar c = buffer[i];
}

Si está utilizando cadenas potencialmente muy largas, sería mejor asignar un búfer de tamaño fijo y enumerar la cadena en fragmentos (esta es la velocidad de la enumeración).


4
2018-04-17 20:55



Creé un método de enumeración de estilo de bloque que usa getCharacters:range: con un buffer de tamaño fijo, según la sugerencia de ughoavgfhw en su respuesta. Evita la situación donde CFStringGetCharactersPtr devuelve null y no tiene que malloc un gran buffer. Puede colocarlo en una categoría NSString, o modificarlo para tomar una cadena como parámetro si lo desea.

-(void)enumerateCharactersWithBlock:(void (^)(unichar, NSUInteger, BOOL *))block
{
    const NSInteger bufferSize = 16;
    const NSInteger length = [self length];
    unichar buffer[bufferSize];
    NSInteger bufferLoops = (length - 1) / bufferSize + 1;
    BOOL stop = NO;
    for (int i = 0; i < bufferLoops; i++) {
        NSInteger bufferOffset = i * bufferSize;
        NSInteger charsInBuffer = MIN(length - bufferOffset, bufferSize);
        [self getCharacters:buffer range:NSMakeRange(bufferOffset, charsInBuffer)];
        for (int j = 0; j < charsInBuffer; j++) {
            block(buffer[j], j + bufferOffset, &stop);
            if (stop) {
                return;
            }
        }
    }
}

1
2018-02-20 20:44



No creo que puedas hacer esto. NSString es una interfaz abstracta para una multitud de clases que no garantizan el almacenamiento interno de los datos de caracteres, por lo que es muy posible que no haya una matriz de caracteres para obtener un puntero.

Si ninguna de las opciones mencionadas en su pregunta es adecuada para su aplicación, le recomiendo que cree su propia clase de cadena para este fin, o que use matrices unichar en malloc sin procesar en lugar de objetos de cadena.


0
2018-04-17 20:54



Esto funcionará:

char *s = [string UTF8String];
for (char *t = s; *t; t++)
  /* use as */ *t;

[Editar] Y si realmente necesitas caracteres Unicode, entonces no tienes más opción que usar longitud y characterAtIndex. De la documentación:

La clase NSString tiene dos métodos primitivos: length y characterAtIndex: que proporcionan la base para todos los otros métodos en su interfaz. El método de longitud devuelve la cantidad total de caracteres Unicode en la cadena. characterAtIndex: da acceso a cada carácter en la cadena por índice, con valores de índice comenzando en 0.

Entonces tu código sería:

  for (int index = 0; index < string.length; index++)
    { 
      unichar c = [string characterAtIndex: index];
      /* ... */
    }

[editar 2]

Además, no olvide que NSString tiene un 'puente libre' para CFString y, por lo tanto, todas las funciones de interfaz C-code directas que no sean Objective-C son utilizables. El relevante sería CFStringGetCharacterAtIndex


0
2018-04-17 20:53



La forma más rápida y confiable de enumerar personajes en un NSString Lo que sé es usar esta gema Core Foundation relativamente poco conocida escondida a la vista (CFString.h).

NSString *string = <#initialize your string#>
NSUInteger stringLength = string.length;
CFStringInlineBuffer buf;
CFStringInitInlineBuffer((__bridge CFStringRef) string, &buf, (CFRange) { 0, stringLength });

for (NSUInteger charIndex = 0; charIndex < stringLength; charIndex++) {
    unichar c = CFStringGetCharacterFromInlineBuffer(&buf, charIndex);
}

Si miras el código fuente de estas funciones en línea, CFStringInitInlineBuffer() y CFStringGetCharacterFromInlineBuffer(), verás que manejan todos los detalles desagradables como CFStringGetCharactersPtr() regresando NULL, CFStringGetCStringPtr() regresando NULL, por defecto a más lento CFStringGetCharacters() y el almacenamiento en caché de los caracteres en una matriz C para un acceso más rápido posible. Esta API realmente merece más publicidad.

La advertencia es que si inicializas el CFStringInlineBuffer en un desplazamiento distinto de cero, debe pasar un índice de caracteres relativos a CFStringInlineBuffer(), como se indica en los comentarios del encabezado:

Las siguientes dos funciones permiten un acceso rápido al contenido de una cadena, asumiendo que está haciendo accesos secuenciales o localizados. Para usar, llame CFStringInitInlineBuffer() con un CFStringInlineBuffer (en la pila, por ejemplo), y un rango en la cadena para mirar. Luego llame CFStringGetCharacterFromInlineBuffer() tantas veces como quieras con un índice en ese rango (relativo al inicio de ese rango). Estas son funciones INLINE y terminarán llamando CFString solo de vez en cuando, para llenar un buffer. CFStringGetCharacterFromInlineBuffer() devuelve 0 si se especifica una ubicación fuera del rango original.


0
2017-09-19 09:04