filtrowanie NSArray do nowego NSArray w Objective-C

121

Mam NSArrayi chciałbym utworzyć nowy NSArrayz obiektami z oryginalnej tablicy, które spełniają określone kryteria. O kryteriach decyduje funkcja, która zwraca a BOOL.

Mogę utworzyć NSMutableArray, iterować przez tablicę źródłową i kopiować obiekty, które akceptuje funkcja filtrująca, a następnie tworzyć niezmienną jej wersję.

Czy jest lepszy sposób?

lajos
źródło

Odpowiedzi:

140

NSArrayi NSMutableArrayzapewniają metody filtrowania zawartości tablicy. NSArrayzapewnia filterArrayUsingPredicate: która zwraca nową tablicę zawierającą obiekty w odbiorniku, które pasują do określonego predykatu. NSMutableArraydodaje filterUsingPredicate: która ocenia zawartość odbiorcy względem określonego predykatu i pozostawia tylko obiekty, które pasują. Poniższy przykład ilustruje te metody.

NSMutableArray *array =
    [NSMutableArray arrayWithObjects:@"Bill", @"Ben", @"Chris", @"Melissa", nil];

NSPredicate *bPredicate =
    [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
NSArray *beginWithB =
    [array filteredArrayUsingPredicate:bPredicate];
// beginWithB contains { @"Bill", @"Ben" }.

NSPredicate *sPredicate =
    [NSPredicate predicateWithFormat:@"SELF contains[c] 's'"];
[array filteredArrayUsingPredicate:sPredicate];
// array now contains { @"Chris", @"Melissa" }
lajos
źródło
84
Słuchałem podcastu Papa Smurf i Papa Smurf powiedział, że odpowiedzi powinny znajdować się w StackOverflow, aby społeczność mogła je oceniać i ulepszać.
willc2
10
@mmalc - Może bardziej odpowiednie, ale na pewno wygodniej jest przeglądać to tutaj.
Bryan
5
NSPredicate jest martwy, niech żyją bloki! por. moja odpowiedź poniżej.
Clay Bridges
Co znaczy zawiera [c]? Zawsze widzę [c], ale nie rozumiem, co robi?
user1007522
3
@ user1007522, [c] sprawia, że ​​dopasowanie nie uwzględnia wielkości liter
jonbauer
104

Można to zrobić na wiele sposobów, ale zdecydowanie najlepszym z nich jest użycie [NSPredicate predicateWithBlock:]:

NSArray *filteredArray = [array filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
    return [object shouldIKeepYou];  // Return YES for each object you want in filteredArray.
}]];

Myślę, że to jest tak zwięzłe, jak to tylko możliwe.


Szybki:

Osoby pracujące z NSArrayjęzykami Swift mogą preferować tę jeszcze bardziej zwięzłą wersję:

nsArray = nsArray.filter { $0.shouldIKeepYou() }

filterjest tylko metodą włączoną Array( NSArrayjest niejawnie połączona z językiem Swift Array). Przyjmuje jeden argument: zamknięcie, które przyjmuje jeden obiekt w tablicy i zwraca wartość Bool. W swoim zamknięciu po prostu wróć truedla dowolnych obiektów, które chcesz umieścić w filtrowanej tablicy.

Stuart
źródło
1
Jaką rolę odgrywają tutaj wiązania NSDictionary *?
Kaitain
Powiązania @Kaitain są wymagane przez NSPredicate predicateWithBlock:API.
ThomasW,
@Kaitain bindingsSłownik może zawierać zmienne powiązania dla szablonów.
Amin Negm-Awad
O wiele bardziej przydatna niż akceptowana odpowiedź przy złożonych obiektach :)
Ben Leggiero
47

Na podstawie odpowiedzi udzielonej przez Clay Bridges, oto przykład filtrowania za pomocą bloków (zmień yourArraynazwę zmiennej tablicy i testFuncnazwę funkcji testującej):

yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self testFunc:obj];
}]];
pckill
źródło
Na koniec odpowiedź nie tylko wspominająca o filtrowaniu za pomocą bloków, ale także podająca dobry przykład, jak to zrobić. Dzięki.
Krystian
Podoba mi się ta odpowiedź; nawet jeśli filteredArrayUsingPredicatejest szczuplejsza, fakt, że nie używasz żadnych predykatów, niejako przesłania cel.
James Perih,
To najnowocześniejszy sposób, aby to zrobić. Osobiście tęsknię za starym skrótem „objectsPassingTest”, który w pewnym momencie zniknął z API. Mimo to działa szybko i dobrze. Lubię NSPredicate - ale do innych rzeczy, gdzie cięższy młot jest potrzebny
Motti Shneor
Otrzymasz teraz błąd Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC... oczekuje NSIndexSets
anoop4real
@ anoop4real, myślę, że przyczyną ostrzeżenia, o którym wspomniałeś, jest to, że omyłkowo użyłeś indexOfObjectPassingTestzamiast indexesOfObjectsPassingTest. Łatwo przeoczyć, ale duża różnica :)
pckill
46

Jeśli korzystasz z systemu OS X 10.6 / iOS 4.0 lub nowszego, prawdopodobnie lepiej będzie Ci używać bloków niż NSPredicate. Zobacz -[NSArray indexesOfObjectsPassingTest:]lub napisz własną kategorię, aby dodać przydatne rozwiązanie -select:lub -filter:metodę ( przykład ).

Chcesz, żeby ktoś inny napisał tę kategorię, przetestował ją itp.? Sprawdź BlocksKit ( tablice dokumentów ). I istnieje wiele przykładów można znaleźć więcej za, powiedzmy, szukający np „kategoria NSArray blok wybierz” .

Clay Bridges
źródło
5
Czy mógłbyś rozszerzyć swoją odpowiedź na przykład? Witryny blogowe mają tendencję do umierania, gdy ich najbardziej potrzebujesz.
Dan Abramov
8
Ochrona przed zgnilizną linków polega na wyodrębnieniu odpowiedniego kodu i innych elementów z powiązanych artykułów, a nie na dodawaniu kolejnych linków. Zachowaj linki, ale dodaj przykładowy kod.
zestaw narzędzi
3
@ClayBridges Znalazłem to pytanie, szukając sposobów filtrowania w kakao. Ponieważ twoja odpowiedź nie zawiera żadnych próbek kodu, przekopanie się przez twoje linki zajęło mi około 5 minut, aby dowiedzieć się, że bloki nie osiągną tego, czego potrzebuję. Gdyby kod był w odpowiedzi, zajęłoby to może 30 sekund. Ta odpowiedź jest O wiele mniej przydatna bez rzeczywistego kodu.
N_A,
1
@mydogisbox et alia: tutaj są tylko 4 odpowiedzi, a zatem mnóstwo miejsca na własną, błyszczącą i doskonałą odpowiedź opartą na próbkach kodu. Jestem zadowolony ze swojego, więc proszę zostaw to w spokoju.
Clay Bridges
2
mydogisbox ma rację w tym punkcie; jeśli wszystko, co robisz, to udostępnianie linku, nie jest to tak naprawdę odpowiedź, nawet jeśli dodajesz więcej linków. Zobacz meta.stackexchange.com/questions/8231 . Papierkiem lakmusowym: czy Twoja odpowiedź może istnieć samodzielnie, czy też wymaga kliknięcia linków, aby miała jakąkolwiek wartość? W szczególności stwierdzasz, że "prawdopodobnie lepiej ci idzie z blokami niż NSPredicate", ale tak naprawdę nie wyjaśniasz dlaczego.
Robert Harvey
16

Zakładając, że wszystkie obiekty są podobnego typu, możesz dodać metodę jako kategorię ich klasy bazowej, która wywołuje funkcję, której używasz dla kryteriów. Następnie utwórz obiekt NSPredicate, który odwołuje się do tej metody.

W jakiejś kategorii zdefiniuj metodę, która używa Twojej funkcji

@implementation BaseClass (SomeCategory)
- (BOOL)myMethod {
    return someComparisonFunction(self, whatever);
}
@end

Następnie, gdziekolwiek będziesz filtrować:

- (NSArray *)myFilteredObjects {
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"myMethod = TRUE"];
    return [myArray filteredArrayUsingPredicate:pred];
}

Oczywiście, jeśli twoja funkcja porównuje tylko z właściwościami dostępnymi z poziomu twojej klasy, może być po prostu łatwiej przekonwertować warunki funkcji na łańcuch predykatu.

Ashley Clark
źródło
Podobała mi się ta odpowiedź, ponieważ jest wdzięczna i krótka, a na skróty wymaga ponownego nauczenia się, jak sformalizować NSPredicate dla najbardziej podstawowej rzeczy - dostępu do właściwości i metody logicznej. Myślę nawet, że opakowanie nie było potrzebne. Prosty [myArray filterArrayUsingPredicate: [NSPredicate predicateWithFormat: @ "myMethod = TRUE"]]; wystarczy. Dzięki! (Uwielbiam też alternatywy, ale ta jest fajna).
Motti Shneor
3

NSPredicatejest sposób konstruowania nextstep za warunek filtru zbiór ( NSArray, NSSet, NSDictionary).

Na przykład rozważ dwie tablice arri filteredarr:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",@"c"];

filteredarr = [NSMutableArray arrayWithArray:[arr filteredArrayUsingPredicate:predicate]];

filterarr z pewnością będzie zawierał elementy zawierające sam znak c.

aby ułatwić zapamiętanie tych, którzy mają małe tło sql

*--select * from tbl where column1 like '%a%'--*

1) wybierz * z tbl -> kolekcja

2) kolumna1, np. „% A%” ->NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",@"c"];

3) wybierz * z tabeli, gdzie kolumna1, np. „% A%” ->

[NSMutableArray arrayWithArray:[arr filteredArrayUsingPredicate:predicate]];

mam nadzieję, że to pomoże

Durai Amuthan.H
źródło
2

NSArray + Xh

@interface NSArray (X)
/**
 *  @return new NSArray with objects, that passing test block
 */
- (NSArray *)filteredArrayPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate;
@end

NSArray + Xm

@implementation NSArray (X)

- (NSArray *)filteredArrayPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
{
    return [self objectsAtIndexes:[self indexesOfObjectsPassingTest:predicate]];
}

@end

Możesz pobrać tę kategorię tutaj

skywinder
źródło
0

Sprawdź tę bibliotekę

https://github.com/BadChoice/Collection

Zawiera wiele łatwych funkcji tablicowych, aby nigdy więcej nie napisać pętli

Możesz więc po prostu:

NSArray* youngHeroes = [self.heroes filter:^BOOL(Hero *object) {
    return object.age.intValue < 20;
}];

lub

NSArray* oldHeroes = [self.heroes reject:^BOOL(Hero *object) {
    return object.age.intValue < 20;
}];
Jordi Puigdellívol
źródło
0

Najlepszym i najłatwiejszym sposobem jest utworzenie tej metody i przekazanie tablicy i wartości:

- (NSArray *) filter:(NSArray *)array where:(NSString *)key is:(id)value{
    NSMutableArray *temArr=[[NSMutableArray alloc] init];
    for(NSDictionary *dic in self)
        if([dic[key] isEqual:value])
            [temArr addObject:dic];
    return temArr;
}
jalmatari
źródło