lọc NSArray thành một NSArray mới trong Objective-C


121

Tôi có một NSArrayvà tôi muốn tạo mới NSArrayvới các đối tượng từ mảng ban đầu đáp ứng các tiêu chí nhất định. Tiêu chí được quyết định bởi một hàm trả về a BOOL.

Tôi có thể tạo NSMutableArray, lặp qua mảng nguồn và sao chép các đối tượng mà hàm bộ lọc chấp nhận và sau đó tạo một phiên bản bất biến của nó.

Có cách nào tốt hơn?

Câu trả lời:


140

NSArrayNSMutableArraycung cấp các phương thức để lọc nội dung mảng. NSArraycung cấp FilterArrayUsingPredicate: trả về một mảng mới chứa các đối tượng trong bộ thu khớp với vị từ được chỉ định. NSMutableArraythêm filterUsingPredicate: đánh giá nội dung của người nhận so với vị từ được chỉ định và chỉ để lại các đối tượng phù hợp. Các phương pháp này được minh họa trong ví dụ sau.

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

84
Tôi đã nghe podcast của Papa Smurf và Papa Smurf cho biết các câu trả lời sẽ xuất hiện trong StackOverflow để cộng đồng có thể xếp hạng và cải thiện chúng.
willc2

10
@mmalc - Có thể phù hợp hơn, nhưng chắc chắn thuận tiện hơn để xem nó ngay tại đây.
Bryan

5
NSPredicate đã chết, các khối tồn tại lâu dài! cf. câu trả lời của tôi dưới đây.
Cầu đất sét

Hàm chứa [c] có nghĩa là gì? Tôi luôn nhìn thấy [c] nhưng tôi không hiểu nó làm gì?
user1007522

3
@ user1007522, [c] làm cho đối sánh không phân biệt chữ hoa chữ thường
jonbauer 22/10/14

104

Có vô số cách để làm điều này, nhưng cho đến nay cách gọn gàng nhất chắc chắn là sử dụng [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.
}]];

Tôi nghĩ điều đó ngắn gọn nhất có thể.


Nhanh:

Đối với những người làm việc với NSArrays trong Swift, bạn có thể thích phiên bản ngắn gọn hơn này :

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

filterchỉ là một phương thức trên Array( NSArrayđược bắc cầu ngầm với Swift Array). Nó nhận một đối số: một bao đóng nhận một đối tượng trong mảng và trả về a Bool. Trong phần đóng của bạn, chỉ cần trả về truebất kỳ đối tượng nào bạn muốn trong mảng đã lọc.


1
NSDictionary * đóng vai trò gì ở đây?
Kaitain

Các liên kết @Kaitain được yêu cầu bởi NSPredicate predicateWithBlock:API.
ThomasW

@Kaitain bindingsTừ điển có thể chứa các ràng buộc khác nhau cho các mẫu.
Amin Negm-Awad

Nhiều hơn nữa hữu ích hơn câu trả lời được chấp nhận khi giao dịch với các đối tượng phức tạp :)
Bến Leggiero

47

Dựa trên câu trả lời của Clay Bridges, đây là một ví dụ về lọc bằng cách sử dụng các khối (thay đổi yourArraythành tên biến mảng của bạn và testFuncthành tên của hàm thử nghiệm của bạn):

yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self testFunc:obj];
}]];

Cuối cùng, một câu trả lời không chỉ đề cập đến việc lọc với các khối mà còn đưa ra một ví dụ điển hình về cách thực hiện. Cảm ơn.
Krystian

Tôi thích câu trả lời này; mặc dù filteredArrayUsingPredicategọn gàng hơn, thực tế là bạn không sử dụng bất kỳ vị từ nào sẽ che khuất mục đích.
James Perih

Đây là cách hiện đại nhất để làm điều này. Cá nhân tôi mong mỏi các viết tắt cũ "objectsPassingTest" đã biến mất khỏi API tại một số điểm nhất định. Vẫn chạy nhanh và tốt. Tôi làm như NSPredicate - nhưng đối với những thứ khác, nơi mà các búa nặng hơn là cần thiết
Motti Shneor

Bạn sẽ gặp lỗi ngay bây giờ Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC... nó mong đợi NSIndexSets
anoop4real

@ anoop4real, tôi nghĩ nguyên nhân cho sự cảnh báo mà bạn đề cập là bạn sử dụng lẫn lộn indexOfObjectPassingTestthay vì indexesOfObjectsPassingTest. Dễ dàng bỏ lỡ, nhưng sự khác biệt lớn :)
pckill

46

Nếu bạn là OS X 10.6 / iOS 4.0 trở lên, có lẽ bạn nên sử dụng các khối tốt hơn NSPredicate. Xem -[NSArray indexesOfObjectsPassingTest:]hoặc viết danh mục của riêng bạn để thêm một tiện ích -select:hoặc -filter:phương pháp ( ví dụ ).

Muốn người khác viết thể loại đó, thử nghiệm nó, v.v.? Kiểm tra BlocksKit ( tài liệu mảng ). Và có rất nhiều ví dụ khác được tìm thấy bằng cách tìm kiếm ví dụ như "chọn thể loại khối nsarray" .


5
Bạn có phiền mở rộng câu trả lời của mình bằng một ví dụ không? Các trang web blog có xu hướng chết khi bạn cần chúng nhất.
Dan Abramov

8
Biện pháp bảo vệ chống lại sự thối rữa liên kết là trích xuất mã có liên quan và những gì không có từ các bài viết được liên kết, không thêm liên kết khác. Giữ các liên kết, nhưng thêm một số mã mẫu.
toolbear

3
@ClayBridges Tôi thấy câu hỏi này đang tìm cách lọc trong cacao. Vì câu trả lời của bạn không có bất kỳ mẫu mã nào trong đó, tôi đã mất khoảng 5 phút để tìm hiểu các liên kết của bạn để tìm ra rằng các khối sẽ không đạt được những gì tôi cần. Nếu mã có trong câu trả lời, có thể mất 30 giây. Câu trả lời này FAR ít hữu ích hơn nếu không có mã thực tế.
N_A

1
@mydogisbox et alia: chỉ có 4 câu trả lời ở đây và do đó rất nhiều chỗ cho một câu trả lời lấy mẫu mã sáng bóng và cao cấp của riêng bạn. Tôi hạnh phúc với của tôi, vì vậy hãy để nó yên.
Cầu đất sét

2
mydogisbox đúng về điểm này; nếu tất cả những gì bạn đang làm là cung cấp một liên kết, nó không thực sự là một câu trả lời, ngay cả khi bạn thêm nhiều liên kết hơn. Xem meta.stackexchange.com/questions/8231 . Bài kiểm tra quỳ: Câu trả lời của bạn có thể tự đứng được không, hay nó yêu cầu nhấp vào các liên kết để có bất kỳ giá trị nào? Đặc biệt, bạn nói rằng "bạn có thể tốt hơn với các khối hơn NSPredicate," nhưng bạn không thực sự giải thích tại sao.
Robert Harvey

16

Giả sử rằng tất cả các đối tượng của bạn đều thuộc loại tương tự, bạn có thể thêm một phương thức làm thể loại của lớp cơ sở của chúng để gọi hàm bạn đang sử dụng cho tiêu chí của mình. Sau đó, tạo một đối tượng NSPredicate tham chiếu đến phương thức đó.

Trong một số danh mục, xác định phương pháp của bạn sử dụng chức năng của bạn

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

Sau đó, bất cứ nơi nào bạn sẽ lọc:

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

Tất nhiên, nếu hàm của bạn chỉ so sánh với các thuộc tính có thể truy cập từ bên trong lớp của bạn, thì việc chuyển đổi các điều kiện của hàm thành một chuỗi vị từ có thể dễ dàng hơn.


Tôi thích câu trả lời này, bởi vì nó rất duyên dáng và ngắn gọn, và rất cần thiết phải học lại cách chính thức hóa một NSPredicate cho điều cơ bản nhất - truy cập thuộc tính và phương thức boolean. Tôi thậm chí nghĩ rằng trình bao bọc là không cần thiết. Một [myArray filterArrayUsingPredicate: [NSPredicate predicateWithFormat: @ "myMethod = TRUE"]]; sẽ đủ. Cảm ơn! (Tôi cũng thích các lựa chọn thay thế, nhưng cái này rất hay).
Motti Shneor

3

NSPredicatelà cách NeXTSTEP của xây dựng điều kiện để lọc một bộ sưu tập ( NSArray, NSSet, NSDictionary).

Ví dụ, hãy xem xét hai mảng arrfilteredarr:

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

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

Filterarr chắc chắn sẽ có các mục chỉ chứa ký tự c.

để dễ nhớ những người có nền tảng sql nhỏ đó là

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

1) chọn * từ tbl -> bộ sưu tập

2) column1 như '% a%' ->NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",@"c"];

3) chọn * từ tbl trong đó cột 1 như '% a%' ->

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

Tôi hi vọng cái này giúp được


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

Bạn có thể tải về danh mục này tại đây


0

Kiểm tra thư viện này

https://github.com/BadChoice/Collection

Nó đi kèm với nhiều hàm mảng dễ dàng để không bao giờ viết lại một vòng lặp

Vì vậy, bạn chỉ có thể làm:

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

hoặc là

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

0

Cách tốt nhất và dễ dàng là tạo phương pháp này và truyền mảng và giá trị:

- (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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.