2011-01-02 15 views
9

Moim celem jest posiadanie tablicy zawierającej wszystkie nazwy plików określonego rozszerzenia, ale bez rozszerzenia.Czy istnieje algorytm Objective-C, jak "transformacja" C++ STL?

Jest elegant solution to get all filenames of a specific extension przy użyciu filtru predykatów i instructions on how to split a path into filename and extension, ale aby je połączyć, musiałbym napisać pętlę (nie straszną, ale też nie elegancką).

Czy istnieje sposób z Objective-C (może być podobny do mechanizmu predykatów), aby zastosować jakąś funkcję do każdego elementu tablicy i umieścić wyniki w drugiej tablicy, np. Algorytmie C++ STL z transform?

Co chciałbym napisać:

// let's pretend 'anArray' was filled by querying the filesystem and not hardcoded 
NSArray* anArray = [[NSArray alloc] initWithObjects:@"one.ext", @"two.ext", nil]; 

// that's what I liked to write (pseudo code) 
NSArray* transformed = [anArray transform: stringByDeletingPathExtension]; 

// Yuji's answer below proposes this (which may be as close as you can get 
// to my wish with Objective C) 
NSArray* transformed = [anArray my_arrayByApplyingBlock:^(id x){ 
          return [x stringByDeletingPathExtension]; 
         }]; 
+2

Biorąc pod uwagę wszystkie 'transform' naprawdę jest pętla nad elementami, nie jestem naprawdę widząc niewykwitność tego robić samemu ... –

+0

chcesz zrobić to w miejscu lub stworzyć nową tablicę? Czy musisz również usunąć ścieżki do plików? Przykładowe dane (pokazujące początkowe elementy tablicy i wyniki, które mają być) będą pomocne. Najlepszą rzeczą przy pisaniu pytania jest dostarczenie szerokiej gamy informacji przy zachowaniu jej zwięzłości. – outis

+1

@Nicholas: chodzi o wyższe poziomy abstrakcji i łatwość konserwacji. Kiedy możesz wyrazić swój zamiar na jednej linii i nie musisz pisać (lub kopiować/wklejać) tej samej pętli wiele razy, kod jest łatwiejszy do zrozumienia i utrzymania. Jeśli pojedyncza linia jest wystarczająco zaawansowana (a.k.a. zbyt sprytna dla przeciętnego programisty), to wolałbym też pętlę. – pesche

Odpowiedz

9

To jest temat o nazwie wyższego rzędu Messaging w kakao, a opracowane przez wielu ludzi w sieci. Zacznij od numeru here i wypróbuj więcej. Dodają metodę kategorię do NSArray tak, że można zrobić

NSArray*transformed=[[anArray map] stringByDeletingPathExtension]; 

Idea jest następująca:

  • [anArray map] tworzy tymczasowy obiekt (słownie hom)
  • hom odbiera komunikat stringByDeletingPathExtension
  • hom ponownie wysyła wiadomość do wszystkich elementów anArray
  • hom zbiera wyniki i zwraca wynikową tablicę.

Jeśli chcesz po prostu szybkie przetwarzanie, chciałbym zdefiniować metodę Kategoria:

@interface NSArray (myTransformingAddition) 
-(NSArray*)my_arrayByApplyingBlock:(id(^)(id))block; 
@end 

@implementation NSArray (myTransformingAddition) 
-(NSArray*)my_arrayByApplyingBlock:(id(^)(id))block{ 
    NSMutableArray*result=[NSMutableArray array]; 
    for(id x in self){ 
      [result addObject:block(x)]; 
    } 
    return result; 
} 
@end 

Następnie można zrobić

NSArray* transformed=[anArray my_arrayByApplyingBlock:^id(id x){return [x stringByDeletingPathExtension];}]; 

Uwaga konstrukt ^ return-type (arguments) { ...} która tworzy blok. Zwracany typ można pominąć, a clang jest całkiem sprytny, jeśli chodzi o odgadywanie, ale gcc jest dość ścisły i musi zostać określony w pewnym momencie. (W tym przypadku, to domyślić z rachunku return który ma [x stringBy...] która zwraca NSString*. Więc GCC zgaduje typ zwracanej bloku być NSString* zamiast id, który uważa, GCC jest niezgodna, co przychodzi błąd.)

W systemie OS X Leopard lub iOS 3 można używać funkcji PLBlocks do obsługi bloków. Moją osobistą subiektywną opinią jest to, że ludzie, którym zależy na nowym oprogramowaniu, zwykle uaktualniają się do najnowszego systemu operacyjnego, więc wsparcie najnowszego systemu operacyjnego powinno być w porządku; obsługa starszego systemu operacyjnego nie zwiększy liczby klientów o dwa ...

, KTÓRA POWIEDZIAŁA, istnieje już dobre środowisko open-source, które robi wszystko, co powiedziałem powyżej. Zobacz dyskusję here, a zwłaszcza FunctionalKit połączony tam.


Dodatek dodatkowy: w rzeczywistości łatwo jest zrealizować pseudokod [array transform:stringByDeletingPathExtension].

@interface NSArray (myTransformingAddition) 
-(NSArray*)my_transformUsingSelector:(SEL)sel; 
@end 

@implementation NSArray (myTransformingAddition) 
-(NSArray*)my_transformUsingSelector:(SEL)sel;{ 
    NSMutableArray*result=[NSMutableArray array]; 
    for(id x in self){ 
      [result addObject:[x performSelector:sel withObject:nil]]; 
    } 
    return result; 
} 
@end 

Wtedy można go używać w następujący sposób:

NSArray*transformed=[array my_transformUsingSelector:@selector(stringByDeletingPathExtension)]; 

Jednak nie podoba mi się tak bardzo; aby móc korzystać z tej metody, musisz mieć już zdefiniowaną metodę na obiekcie w tablicy. Na przykład, jeśli NSString nie ma operacji, którą chcesz wykonać jako metodę, co byś zrobił w tym przypadku? Trzeba najpierw dodać go do NSString poprzez kategoriach:

@interface NSString (myHack) 
-(NSString*)my_NiceTransformation; 
@end 

@implementation NSString (myHack) 
-(NSString*)my_NiceTransformation{ 
    ... computes the return value from self ... 
    return something; 
} 
@end 

Następnie można użyć

NSArray*transformed=[array my_transformUsingSelector:@selector(my_NiceTransformation)]; 

Ale to wydaje się być bardzo gadatliwy, bo trzeba zdefiniować metody w innych miejscach w pierwszej kolejności. Wolę dostarczanie co chcę działać bezpośrednio w miejscu połączenia, jak w

NSArray*transformed=[array my_arrayByApplyingBlock:^id(id x){ 
    ... computes the return value from x ... 
    return something;  
}]; 

Wreszcie metod kategorii nigdy add które nie zaczynają się od przedrostka jak my_ czy cokolwiek innego. Na przykład w przyszłości Apple może dostarczyć fajną metodę o nazwie transform, która robi dokładnie to, co chcesz. Ale jeśli masz już w tej kategorii metodę o nazwie transform, doprowadzi to do niezdefiniowanego zachowania. W rzeczywistości może się zdarzyć, że istnieje prywatna metoda Apple już w klasie.

+0

Dzięki za wprowadzenie mnie do bloków Objective-C. Nigdy dotąd nie widziałem tej drażliwej rzeczy ... Kiedy próbuję użyć twojego kodu (dodanego jako edycja do pytania), pojawia się następujący błąd kompilatora: 'niekompatybilne typy wskaźników blokujących inicjalizujące 'struct NSString * (^) (struct objc_object *) ', oczekiwano' struct objc_object * (^) (struct objc_object *) ''. Nie opanowałem jeszcze wystarczająco bloków, żeby wiedzieć, co jest nie tak. – pesche

+0

[Dokumentacja Apple na blokach] (http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html) stwierdza, że ​​Bloki mogą być używane na iOS 4 i nowszych. Co się stanie, gdy spróbuję uruchomić aplikację z iOS 3.1.x? – pesche

+0

Ach, przepraszam za to. Zobacz edytowaną odpowiedź. – Yuji

11

W rzeczywistości istnieje bardzo prosty sposób. Istnieje od 2003 roku i ma słabą nazwę.

NSArray *array = [NSArray arrayWithObjects:@"one.ext", @"two.ext", nil]; 

// short solution 
NSArray *transformed = [array valueForKey:@"stringByDeletingPathExtension"]; 

// long solution (more robust against refactoring) 
NSString *key = NSStringFromSelector(@selector(stringByDeletingPathExtension)); 
NSArray *transformed = [array valueForKey:key]; 

Zarówno produkować wyjście:

(
    one, 
    two 
)