¿Cómo puedo revertir un NSArray en Objective-C?

356

Necesito revertir mi NSArray.

Como ejemplo:

[1,2,3,4,5] debe convertirse en: [5,4,3,2,1]

¿Cuál es la mejor manera de lograr esto?

Andy Jacobs
fuente
1
También vale la pena ver esto: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Collections/Articles/sortingFilteringArrays.html que le indica cómo ordenar una matriz en orden inverso (que comúnmente es lo que lo está haciendo, por ejemplo, al usar una matriz derivada de NSDictionary # allKeys, y desea que la fecha inversa / orden alfa sirva como agrupación para UITable en iPhone, etc.).

Respuestas:

305

Para obtener una copia invertida de una matriz, mira la solución de danielpunkass usando reverseObjectEnumerator.

Para invertir una matriz mutable, puede agregar la siguiente categoría a su código:

@implementation NSMutableArray (Reverse)

- (void)reverse {
    if ([self count] <= 1)
        return;
    NSUInteger i = 0;
    NSUInteger j = [self count] - 1;
    while (i < j) {
        [self exchangeObjectAtIndex:i
                  withObjectAtIndex:j];

        i++;
        j--;
    }
}

@end
Georg Schölly
fuente
15
Una de las cosas malas de Fast Enumeration es que los nuevos tipos como yo no aprenden cosas interesantes como reverseObjectEnumerator. Muy buena forma de hacerlo.
Brent Royal-Gordon
44
Debido a que los iteradores de C ++ tienen una sintaxis aún peor, son feos.
Georg Schölly
44
¿No deberías copiar la matriz antes de devolverla?
CIFilter
12
@ Georgia: No estoy de acuerdo contigo en este caso. Si veo un método que devuelve un objeto inmutable, espero que en realidad devuelva un objeto inmutable. Hacer que parezca que devuelve un objeto inmutable pero que devuelve un objeto mutable es una práctica peligrosa.
Christine
3
Sugerir reverseObjectEnumerator allObjects no es útil ya que no invierte la matriz mutable, incluso agregar mutableCopy no ayudaría, ya que aún la matriz original no está mutada. Apple documenta que no se debe probar la inmutabilidad en tiempo de ejecución, sino que se debe suponer en función del tipo devuelto, por lo que devolver un NSMutableArray en este caso es un código perfectamente correcto.
Peter N Lewis
1286

Hay una solución mucho más fácil, si aprovecha el reverseObjectEnumeratormétodo incorporado NSArrayy el allObjectsmétodo de NSEnumerator:

NSArray* reversedArray = [[startArray reverseObjectEnumerator] allObjects];

allObjectsestá documentado como devolver una matriz con los objetos que aún no se han recorrido nextObject, en orden:

Esta matriz contiene todos los objetos restantes del enumerador en orden enumerado .

danielpunkass
fuente
66
Hay una respuesta más abajo aquí por Matt Williamson que debería ser un comentario: no use la solución de danielpunkass. Lo usé pensando que era un gran atajo, pero ahora solo he pasado 3 horas tratando de descubrir por qué mi algoritmo A * estaba roto. ¡Es porque devuelve el conjunto incorrecto!
Georg Schölly
1
¿Qué quiere decir con 'conjunto incorrecto'? ¿Una matriz que no está en orden inverso?
Simo Salminen
31
Ya no puedo reproducir ese error. Pudo haber sido mi error. Esta es una solución muy elegante.
Matt Williamson
3
El pedido ahora está garantizado en la documentación.
jscs
Estoy seguro de que esta es la buena respuesta, pero fallará para los objetos mutables. Porque NSEnumerator proporciona el tipo de objeto de solo lectura @property (solo lectura, copia) NSArray <ObjectType> * allObjects;
Anurag Soni
49

Algunos puntos de referencia

1. reverseObjectEnumerator allObjects

Este es el método más rápido:

NSArray *anArray = @[@"aa", @"ab", @"ac", @"ad", @"ae", @"af", @"ag",
        @"ah", @"ai", @"aj", @"ak", @"al", @"am", @"an", @"ao", @"ap", @"aq", @"ar", @"as", @"at",
        @"au", @"av", @"aw", @"ax", @"ay", @"az", @"ba", @"bb", @"bc", @"bd", @"bf", @"bg", @"bh",
        @"bi", @"bj", @"bk", @"bl", @"bm", @"bn", @"bo", @"bp", @"bq", @"br", @"bs", @"bt", @"bu",
        @"bv", @"bw", @"bx", @"by", @"bz", @"ca", @"cb", @"cc", @"cd", @"ce", @"cf", @"cg", @"ch",
        @"ci", @"cj", @"ck", @"cl", @"cm", @"cn", @"co", @"cp", @"cq", @"cr", @"cs", @"ct", @"cu",
        @"cv", @"cw", @"cx", @"cy", @"cz"];

NSDate *methodStart = [NSDate date];

NSArray *reversed = [[anArray reverseObjectEnumerator] allObjects];

NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
NSLog(@"executionTime = %f", executionTime);

Resultado: executionTime = 0.000026

2. Iterando sobre un reverseObjectEnumerator

Esto es entre 1.5x y 2.5x más lento:

NSDate *methodStart = [NSDate date];
NSMutableArray *array = [NSMutableArray arrayWithCapacity:[anArray count]];
NSEnumerator *enumerator = [anArray reverseObjectEnumerator];
for (id element in enumerator) {
    [array addObject:element];
}
NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
NSLog(@"executionTime = %f", executionTime);

Resultado: executionTime = 0.000071

3. sortedArrayUsingComparator

Esto es entre 30x y 40x más lento (sin sorpresas aquí):

NSDate *methodStart = [NSDate date];
NSArray *reversed = [anArray sortedArrayUsingComparator: ^(id obj1, id obj2) {
    return [anArray indexOfObject:obj1] < [anArray indexOfObject:obj2] ? NSOrderedDescending : NSOrderedAscending;
}];

NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
NSLog(@"executionTime = %f", executionTime);

Resultado: executionTime = 0.001100

Así [[anArray reverseObjectEnumerator] allObjects]es el claro ganador cuando se trata de velocidad y facilidad.

Johannes Fahrenkrug
fuente
Y me imagino que será mucho más lento de 30 a 40 veces para un mayor número de objetos. No sé cuál es la complejidad del algoritmo de clasificación (¿el mejor caso es O (n * logn) ?, pero también está llamando a indexOfObject, que probablemente sea O (n). Con la clasificación, eso podría ser O (n ^ 2) * logn) o algo así. ¡No es bueno!
Joseph Humfrey
1
¿Qué pasa con un punto de referencia utilizando enumerateObjectsWithOptions:NSEnumerationReverse?
brandonscript
2
Acabo de hacer un punto de referencia usando enumerateObjectsWithOptions:NSEnumerationReverse: el primero completado en 0.000072segundos, el método de bloqueo en 0.000009segundos.
brandonscript
Buena observación, tiene sentido para mí, pero creo que los tiempos de ejecución son demasiado cortos para concluir que el algoritmo X es Y veces más rápido que otro. Para medir el rendimiento de la ejecución del algoritmo, tenemos que ocuparnos de varias cosas, como ¿Hay algún proceso que se esté ejecutando al mismo tiempo? Por ejemplo, tal vez cuando ejecuta el primer algoritmo tiene más memoria caché disponible, y así sucesivamente. Además, solo hay un conjunto de datos, creo que deberíamos ejecutar con varios conjuntos de datos (con diferentes tamaños son composiciones) para concluir.
pcambre
21

DasBoot tiene el enfoque correcto, pero hay algunos errores en su código. Aquí hay un fragmento de código completamente genérico que revertirá cualquier NSMutableArray en su lugar:

/* Algorithm: swap the object N elements from the top with the object N 
 * elements from the bottom. Integer division will wrap down, leaving 
 * the middle element untouched if count is odd.
 */
for(int i = 0; i < [array count] / 2; i++) {
    int j = [array count] - i - 1;

    [array exchangeObjectAtIndex:i withObjectAtIndex:j];
}

Puede ajustar eso en una función C, o para puntos de bonificación, use categorías para agregarlo a NSMutableArray. (En ese caso, 'array' se convertiría en 'self'). También puede optimizarlo asignándolo [array count]a una variable antes del ciclo y utilizando esa variable, si lo desea.

Si solo tiene un NSArray regular, no hay forma de revertirlo en su lugar, porque los NSArray no pueden modificarse. Pero puedes hacer una copia invertida:

NSMutableArray * copy = [NSMutableArray arrayWithCapacity:[array count]];

for(int i = 0; i < [array count]; i++) {
    [copy addObject:[array objectAtIndex:[array count] - i - 1]];
}

O use este pequeño truco para hacerlo en una línea:

NSArray * copy = [[array reverseObjectEnumerator] allObjects];

Si solo desea recorrer una matriz hacia atrás, puede usar un for/ inloop con [array reverseObjectEnumerator], pero es probable que sea un poco más eficiente de usar -enumerateObjectsWithOptions:usingBlock::

[array enumerateObjectsWithOptions:NSEnumerationReverse
                        usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // This is your loop body. Use the object in obj here. 
    // If you need the index, it's in idx.
    // (This is the best feature of this method, IMHO.)
    // Instead of using 'continue', use 'return'.
    // Instead of using 'break', set '*stop = YES' and then 'return'.
    // Making the surrounding method/block return is tricky and probably
    // requires a '__block' variable.
    // (This is the worst feature of this method, IMHO.)
}];

( Nota : sustancialmente actualizado en 2014 con cinco años más de experiencia de la Fundación, una nueva característica de Objective-C o dos, y un par de consejos de los comentarios).

Brent Royal-Gordon
fuente
¿Eso funciona? No creo que NSMutableArray tenga un método setObject: atIndex: Sin embargo, gracias por la solución sugerida para el bucle y por usar una identificación genérica en lugar de NSNumber.
Himadri Choudhury
Tienes razón, lo entendí cuando leí algunos de los otros ejemplos. Corregido ahora.
Brent Royal-Gordon
2
[recuento de matriz] se llama cada vez que realiza un bucle. Esto es muy costoso. Incluso hay una función que cambia las posiciones de dos objetos.
Georg Schölly
8

Después de revisar las respuestas del otro arriba y encontrar la discusión de Matt Gallagher aquí

Propongo esto:

NSMutableArray * reverseArray = [NSMutableArray arrayWithCapacity:[myArray count]]; 

for (id element in [myArray reverseObjectEnumerator]) {
    [reverseArray addObject:element];
}

Como Matt observa:

En el caso anterior, puede preguntarse si: [NSArray reverseObjectEnumerator] se ejecutaría en cada iteración del bucle, lo que podría ralentizar el código. <...>

Poco después, responde así:

<...> La expresión "colección" solo se evalúa una vez, cuando comienza el ciclo for. Este es el mejor caso, ya que puede poner de manera segura una función costosa en la expresión "colección" sin afectar el rendimiento por iteración del bucle.

Aeronin
fuente
8

Las categorías de Georg Schölly son muy agradables. Sin embargo, para NSMutableArray, el uso de NSUIntegers para los índices da como resultado un bloqueo cuando la matriz está vacía. El código correcto es:

@implementation NSMutableArray (Reverse)

- (void)reverse {
    NSInteger i = 0;
    NSInteger j = [self count] - 1;
    while (i < j) {
        [self exchangeObjectAtIndex:i
                  withObjectAtIndex:j];

        i++;
        j--;
    }
}

@end
Werner Jainek
fuente
8

La forma más eficiente de enumerar una matriz en reversa:

Uso enumerateObjectsWithOptions:NSEnumerationReverse usingBlock. Usando el punto de referencia de @ JohannesFahrenkrug anterior, esto se completó 8 veces más rápido que [[array reverseObjectEnumerator] allObjects];:

NSDate *methodStart = [NSDate date];

[anArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    //
}];

NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
NSLog(@"executionTime = %f", executionTime);
Brandonscript
fuente
7
NSMutableArray *objMyObject = [NSMutableArray arrayWithArray:[self reverseArray:objArrayToBeReversed]];

// Function reverseArray 
-(NSArray *) reverseArray : (NSArray *) myArray {   
    return [[myArray reverseObjectEnumerator] allObjects];
}
Jayprakash Dubey
fuente
3

Matriz inversa y recorrerla:

[[[startArray reverseObjectEnumerator] allObjects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    ...
}];
Aqib Mumtaz
fuente
2

Para actualizar esto, en Swift se puede hacer fácilmente con:

array.reverse()
Julio
fuente
1

En cuanto a mí, ¿has considerado cómo se pobló la matriz en primer lugar? Estaba en el proceso de agregar MUCHOS objetos a una matriz, y decidí insertar cada uno al principio, empujando los objetos existentes hacia arriba uno por uno. Requiere una matriz mutable, en este caso.

NSMutableArray *myMutableArray = [[NSMutableArray alloc] initWithCapacity:1];
[myMutableArray insertObject:aNewObject atIndex:0];
James Perih
fuente
1

O el camino de escala:

-(NSArray *)reverse
{
    if ( self.count < 2 )
        return self;
    else
        return [[self.tail reverse] concat:[NSArray arrayWithObject:self.head]];
}

-(id)head
{
    return self.firstObject;
}

-(NSArray *)tail
{
    if ( self.count > 1 )
        return [self subarrayWithRange:NSMakeRange(1, self.count - 1)];
    else
        return @[];
}
Hugo Stieglitz
fuente
2
La recursión es una idea terrible en grandes estructuras de datos, en cuanto a memoria.
Eran Goldin
Si se compila con la recursión de cola, no debería ser un problema
simple_code
1

Hay una manera fácil de hacerlo.

    NSArray *myArray = @[@"5",@"4",@"3",@"2",@"1"];
    NSMutableArray *myNewArray = [[NSMutableArray alloc] init]; //this object is going to be your new array with inverse order.
    for(int i=0; i<[myNewArray count]; i++){
        [myNewArray insertObject:[myNewArray objectAtIndex:i] atIndex:0];
    }
    //other way to do it
    for(NSString *eachValue in myArray){
        [myNewArray insertObject:eachValue atIndex:0];
    }

    //in both cases your new array will look like this
    NSLog(@"myNewArray: %@", myNewArray);
    //[@"1",@"2",@"3",@"4",@"5"]

Espero que esto ayude.

vroldan
fuente
0

No conozco ningún método incorporado. Pero, la codificación a mano no es demasiado difícil. Suponiendo que los elementos de la matriz con la que está tratando son objetos NSNumber de tipo entero, y 'arr' es el NSMutableArray que desea revertir.

int n = [arr count];
for (int i=0; i<n/2; ++i) {
  id c  = [[arr objectAtIndex:i] retain];
  [arr replaceObjectAtIndex:i withObject:[arr objectAtIndex:n-i-1]];
  [arr replaceObjectAtIndex:n-i-1 withObject:c];
}

Como comienza con un NSArray, primero debe crear la matriz mutable con el contenido del NSArray original ('origArray').

NSMutableArray * arr = [[NSMutableArray alloc] init];
[arr setArray:origArray];

Editar: Se corrigió n -> n / 2 en el recuento del bucle y se cambió NSNumber a la identificación más genérica debido a las sugerencias en la respuesta de Brent.

Himadri Choudhury
fuente
1
¿No le falta un lanzamiento en c?
Clay Bridges
0

Si todo lo que quiere hacer es iterar en reversa, intente esto:

// iterate backwards
nextIndex = (currentIndex == 0) ? [myArray count] - 1 : (currentIndex - 1) % [myArray count];

Puede hacer [myArrayCount] una vez y guardarlo en una variable local (creo que es costoso), pero también supongo que el compilador hará lo mismo con el código que se escribió anteriormente.

DougPA
fuente
0

Sintaxis de Swift 3:

let reversedArray = array.reversed()
fethica
fuente
0

Prueba esto:

for (int i = 0; i < [arr count]; i++)
{
    NSString *str1 = [arr objectAtIndex:[arr count]-1];
    [arr insertObject:str1 atIndex:i];
    [arr removeObjectAtIndex:[arr count]-1];
}
Shashank shree
fuente
0

Aquí hay una buena macro que funcionará para NSMutableArray O NSArray:

#define reverseArray(__theArray) {\
    if ([__theArray isKindOfClass:[NSMutableArray class]]) {\
        if ([(NSMutableArray *)__theArray count] > 1) {\
            NSUInteger i = 0;\
            NSUInteger j = [(NSMutableArray *)__theArray count]-1;\
            while (i < j) {\
                [(NSMutableArray *)__theArray exchangeObjectAtIndex:i\
                                                withObjectAtIndex:j];\
                i++;\
                j--;\
            }\
        }\
    } else if ([__theArray isKindOfClass:[NSArray class]]) {\
        __theArray = [[NSArray alloc] initWithArray:[[(NSArray *)__theArray reverseObjectEnumerator] allObjects]];\
    }\
}

Para usar solo llame: reverseArray(myArray);

Albert Renshaw
fuente