Como posso reverter um NSArray no Objective-C?

356

Eu preciso reverter o meu NSArray.

Como um exemplo:

[1,2,3,4,5] deve se tornar: [5,4,3,2,1]

Qual é a melhor maneira de conseguir isso?

Andy Jacobs
fonte
11
Também vale a pena olhar para isso: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Collections/Articles/sortingFilteringArrays.html, que mostra como classificar uma matriz em ordem inversa (o que geralmente é o que você está fazendo, por exemplo, usando uma matriz derivada do NSDictionary # allKeys e deseja que a ordem reversa de data / alfa sirva como agrupamento para UITable no iPhone, etc.).

Respostas:

305

Para obter uma cópia invertida de uma matriz, observe a solução de danielpunkass usando reverseObjectEnumerator.

Para reverter uma matriz mutável, você pode adicionar a seguinte categoria ao seu 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
fonte
15
Uma das coisas ruins da Enumeração Rápida é que novos caras como eu não aprendem sobre coisas legais como reverseObjectEnumerator. Uma maneira bem legal de fazer isso.
Brent Royal-Gordon
4
Como os iteradores C ++ têm uma sintaxe ainda pior, eles são feios.
Georg Schölly 22/04/2009
4
Você não deve copiar a matriz antes de devolvê-la?
CIFilter
12
@ Georg: Eu discordo de você neste caso. Se eu vir um método que retorna um objeto imutável, espero que ele realmente retorne um objeto imutável. Fazer com que pareça retornar um objeto imutável, mas o retorno real de um objeto mutável é uma prática perigosa para se entrar.
Christine
3
Sugerir reverseObjectEnumerator allObjects não é útil, pois não reverte a matriz mutável. Mesmo a adição de mutableCopy não ajudaria, pois a matriz original ainda não está mutada. A Apple documenta que a imutabilidade não deve ser testada em tempo de execução, mas deve ser assumida com base no tipo retornado; portanto, retornar um NSMutableArray nesse caso é o código perfeitamente correto.
Peter N Lewis
1286

Existe uma solução muito mais fácil, se você tirar proveito do reverseObjectEnumeratormétodo interno ativado NSArraye do allObjectsmétodo de NSEnumerator:

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

allObjectsestá documentado como retornando uma matriz com os objetos que ainda não foram atravessados nextObject, em ordem:

Essa matriz contém todos os demais objetos do enumerador em ordem enumerada .

danielpunkass
fonte
6
Há uma resposta aqui em baixo por Matt Williamson que deveria ser um comentário: não use a solução de danielpunkass. Usei-o pensando que era um ótimo atalho, mas agora passei apenas 3 horas tentando descobrir por que meu algoritmo A * estava quebrado. É porque retorna o conjunto errado!
Georg Schölly 12/03/10
11
O que quer dizer com 'conjunto errado'? Uma matriz que não está na ordem inversa?
Simo Salminen
31
Não sou mais capaz de reproduzir esse bug. Poderia ter sido o meu erro. Esta é uma solução muito elegante.
Matt Williamson
3
O pedido agora está garantido na documentação.
JSCs
Estou certo de que esta é a boa resposta, mas falhará nos Objetos Mutáveis. Como o NSEnumerator fornece o tipo de objeto somente leitura @property (somente leitura, cópia) NSArray <ObjectType> * allObjects;
Anurag Soni
49

Alguns benchmarks

1. reverseObjectEnumerator allObjects

Este é o método mais 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 um reverseObjectEnumerator

Isso fica entre 1,5x e 2,5x mais 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. selectedArrayUsingComparator

Isso fica entre 30x e 40x mais lento (sem surpresas aqui):

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

Assim [[anArray reverseObjectEnumerator] allObjects]é o vencedor claro quando se trata de velocidade e facilidade.

Johannes Fahrenkrug
fonte
E posso imaginar que será muito mais que 30-40x mais lento para um número maior de objetos. Eu não sei qual é a complexidade do algoritmo de classificação (melhor caso O (n * logn)?, Mas também está chamando indexOfObject, que provavelmente é O (n). Com a classificação, isso pode ser O (n ^ 2 * logn) ou algo Não é bom.!
Joseph Humfrey
11
Que tal um benchmark usando enumerateObjectsWithOptions:NSEnumerationReverse?
Brandoncript # 10/15
2
Acabei de fazer um benchmark usando enumerateObjectsWithOptions:NSEnumerationReverse- o primeiro concluído em 0.000072segundos, o método de bloqueio em 0.000009segundos.
Brandoncript # 10/15
Boa observação, faz sentido para mim, mas acho que os tempos de execução são muito curtos para concluir que o algoritmo X é Y vezes mais rápido que o outro. Para medir o desempenho da execução do algoritmo, precisamos cuidar de várias coisas, como: Existe algum processo em execução ao mesmo tempo ?. Por exemplo, talvez quando você executa o primeiro algoritmo, tenha mais memória cache disponível e assim por diante. Além disso, existe apenas um conjunto de dados, acho que devemos executar com vários conjuntos de dados (com tamanhos diferentes de composição) para concluir.
#
21

O DasBoot tem a abordagem correta, mas há alguns erros em seu código. Aqui está um trecho de código completamente genérico que reverterá qualquer NSMutableArray no local:

/* 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];
}

Você pode agrupar isso em uma função C ou, para pontos de bônus, use categorias para adicioná-lo ao NSMutableArray. (Nesse caso, 'array' se tornaria 'self'.) Você também pode otimizá-lo atribuindo [array count]uma variável antes do loop e usando essa variável, se desejar.

Se você possui apenas um NSArray regular, não há como revertê-lo, pois o NSArrays não pode ser modificado. Mas você pode fazer uma cópia invertida:

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

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

Ou use este pequeno truque para fazer isso em uma linha:

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

Se você quiser fazer um loop ao longo de uma matriz para trás, poderá usar um loop for/ , mas provavelmente será um pouco mais eficiente :in[array reverseObjectEnumerator]-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.)
}];

( Observação : substancialmente atualizado em 2014, com mais cinco anos de experiência da Fundação, um novo recurso do Objective-C ou dois e algumas dicas dos comentários.)

Brent Royal-Gordon
fonte
Isso funciona? Não acho que o NSMutableArray tenha um método setObject: atIndex:. Obrigado pela correção sugerida para o loop e usando o ID genérico em vez do NSNumber.
Himadri Choudhury
Você está certo, percebi isso quando li alguns dos outros exemplos. Corrigido agora.
Brent Royal-Gordon
2
[array count] é chamado toda vez que você faz um loop. Isso é muito caro. Existe até uma função que altera as posições de dois objetos.
Georg Schölly
8

Depois de revisar as respostas dos outros acima e encontrar a discussão de Matt Gallagher aqui

Eu proponho isso:

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

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

Como Matt observa:

No caso acima, você pode se perguntar se - [NSArray reverseObjectEnumerator] seria executado em todas as iterações do loop - potencialmente diminuindo a velocidade do código. <...>

Pouco tempo depois, ele responde assim:

<...> A expressão "coleção" é avaliada apenas uma vez, quando o loop for começa. Esse é o melhor caso, pois você pode colocar com segurança uma função cara na expressão "coleção" sem afetar o desempenho por loop do loop por iteração.

Aeronin
fonte
8

As categorias de Georg Schölly são muito agradáveis. No entanto, para NSMutableArray, o uso de NSUIntegers para os índices resulta em uma falha quando a matriz está vazia. O código correto é:

@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
fonte
8

A maneira mais eficiente de enumerar uma matriz ao contrário:

Use enumerateObjectsWithOptions:NSEnumerationReverse usingBlock. Usando o benchmark de @ JohannesFahrenkrug acima, isso foi concluído 8 vezes mais 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
fonte
7
NSMutableArray *objMyObject = [NSMutableArray arrayWithArray:[self reverseArray:objArrayToBeReversed]];

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

Inverter matriz e fazer um loop através dela:

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

Para atualizar isso, no Swift, isso pode ser feito facilmente com:

array.reverse()
Julio
fonte
1

Quanto a mim, você considerou como a matriz foi preenchida em primeiro lugar? Eu estava no processo de adicionar MUITOS objetos a uma matriz e decidi inserir cada um no início, empurrando todos os objetos existentes por um. Requer uma matriz mutável, neste caso.

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

Ou o Scala-way:

-(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
fonte
2
A recursão é uma péssima idéia em grandes estruturas de dados, em termos de memória.
Eran Goldin
Se compila com recursão de cauda não deve ser um problema
simple_code
1

Existe uma maneira fácil de fazer isso.

    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"]

Eu espero que isso ajude.

vroldan
fonte
0

Não conheço nenhum método embutido. Mas codificar à mão não é muito difícil. Supondo que os elementos da matriz com a qual você está lidando sejam objetos NSNumber do tipo inteiro, e 'arr' seja o NSMutableArray que você deseja reverter.

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];
}

Desde que você começa com um NSArray, é necessário criar a matriz mutável primeiro com o conteúdo do NSArray original ('origArray').

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

Edit: Fixed n -> n / 2 na contagem de loop e mudou NSNumber para o ID mais genérico devido às sugestões na resposta de Brent.

Himadri Choudhury
fonte
11
Não está faltando um release em c?
Clay Bridges
0

Se tudo o que você deseja fazer é iterar ao contrário, tente o seguinte:

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

Você pode fazer o [myArrayCount] uma vez e salvá-lo em uma variável local (acho que é cara), mas também acho que o compilador fará a mesma coisa com o código descrito acima.

DougPA
fonte
0

Sintaxe do Swift 3:

let reversedArray = array.reversed()
fethica
fonte
0

Tente o seguinte:

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
fonte
0

Aqui está uma boa macro que funcionará para NSMutableArray OU 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 apenas ligue: reverseArray(myArray);

Albert Renshaw
fonte