กรอง NSArray เป็น NSArray ใหม่ใน Objective-C


121

ฉันมีNSArrayและฉันต้องการสร้างNSArrayวัตถุใหม่จากอาร์เรย์เดิมที่ตรงตามเกณฑ์ที่กำหนด เกณฑ์จะถูกกำหนดโดยฟังก์ชันที่ส่งคืน a BOOL.

ฉันสามารถสร้างNSMutableArrayทำซ้ำผ่านอาร์เรย์ต้นทางและคัดลอกวัตถุที่ฟังก์ชันตัวกรองยอมรับแล้วสร้างเวอร์ชันที่ไม่เปลี่ยนรูปได้

มีวิธีที่ดีกว่า?

คำตอบ:


140

NSArrayและNSMutableArrayจัดเตรียมวิธีการกรองเนื้อหาอาร์เรย์ NSArrayให้filteredArrayUsingPredicate:ซึ่งส่งคืนอาร์เรย์ใหม่ที่มีอ็อบเจ็กต์ในตัวรับที่ตรงกับเพรดิเคตที่ระบุ NSMutableArrayเพิ่มfilterUsingPredicate:ซึ่งประเมินเนื้อหาของผู้รับเทียบกับเพรดิเคตที่ระบุและออกจากอ็อบเจ็กต์ที่ตรงกันเท่านั้น วิธีการเหล่านี้แสดงอยู่ในตัวอย่างต่อไปนี้

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
ฉันฟังพอดคาสต์ของ Papa Smurf และ Papa Smurf กล่าวว่าคำตอบควรอยู่ใน StackOverflow เพื่อให้ชุมชนสามารถให้คะแนนและปรับปรุงได้
willc2

10
@mmalc - อาจจะน่าสนใจกว่า แต่ก็สะดวกกว่าที่จะดูที่นี่
Bryan

5
NSPredicate ตายแล้วบล็อคยาว! cf เลย คำตอบของฉันด้านล่าง
Clay Bridges

มี [c] หมายความว่าอะไร? ฉันมักจะเห็น [c] แต่ฉันไม่เข้าใจว่ามันทำอะไร?
user1007522

3
@ user1007522 [c] ทำให้การจับคู่ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่
jonbauer

104

มีหลายวิธีในการทำสิ่งนี้ แต่โดยทั่วไปแล้วสิ่งที่บริสุทธิ์ที่สุดก็คือการใช้[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.
}]];

ฉันคิดว่ามันเป็นเรื่องที่กระชับที่สุดเท่าที่จะทำได้


สวิฟท์:

สำหรับการทำงานที่มีNSArrayอยู่ในสวิฟท์, คุณอาจชอบนี้มากยิ่งขึ้นรุ่นรัดกุม:

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

filterเป็นเพียงวิธีการบนArray( NSArrayเชื่อมโยงกับ Swift โดยปริยายArray) ใช้อาร์กิวเมนต์เดียว: การปิดที่รับวัตถุหนึ่งในอาร์เรย์และส่งกลับค่าBool. ในการปิดของคุณเพียงส่งคืนtrueวัตถุใด ๆ ที่คุณต้องการในอาร์เรย์ที่กรองแล้ว


1
การผูก NSDictionary * มีบทบาทอะไรที่นี่?
Kaitain

ผูก @Kaitain เป็นสิ่งจำเป็นโดยNSPredicate predicateWithBlock:API
ThomasW

@Kaitain bindingsพจนานุกรมสามารถมีการผูกตัวแปรสำหรับเทมเพลต
Amin Negm-Awad

มีประโยชน์มากกว่าคำตอบที่ยอมรับเมื่อจัดการกับวัตถุที่ซับซ้อน :)
Ben Leggiero

47

จากคำตอบของ Clay Bridges นี่คือตัวอย่างของการกรองโดยใช้บล็อก (เปลี่ยนyourArrayเป็นชื่อตัวแปรอาร์เรย์และtestFuncชื่อฟังก์ชันการทดสอบของคุณ):

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

ในที่สุดคำตอบไม่เพียงกล่าวถึงการกรองด้วยบล็อก แต่ยังให้ตัวอย่างที่ดีเกี่ยวกับวิธีการทำ ขอบคุณ
Krystian

ฉันชอบคำตอบนี้ แม้ว่าจะfilteredArrayUsingPredicateมีขนาดเล็กกว่าแต่ความจริงที่ว่าคุณไม่ได้ใช้เพรดิเคตใด ๆ ที่จะปิดบังวัตถุประสงค์
James Perih

นี่เป็นวิธีที่ทันสมัยที่สุดในการทำเช่นนี้ โดยส่วนตัวแล้วฉันโหยหา "objectsPassingTest" ชวเลขแบบเก่าที่หายไปจาก API ณ จุดหนึ่ง ยังคงทำงานได้เร็วและดี ฉันชอบ NSPredicate - แต่สำหรับอย่างอื่นที่ต้องการค้อนที่หนักกว่า
Motti Shneor

คุณจะได้รับข้อผิดพลาดตอนนี้Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC... คาดว่า NSIndexSets
anoop4real

@ anoop4real ผมคิดว่าสาเหตุของการเตือนที่คุณกล่าวถึงคือการที่คุณหลงผิดใช้แทนindexOfObjectPassingTest indexesOfObjectsPassingTestพลาดง่าย แต่แตกต่างมาก :)
pckill

46

หากคุณเป็น OS X 10.6 / iOS 4.0 หรือใหม่กว่าคุณอาจจะบล็อกได้ดีกว่า NSPredicate ดู-[NSArray indexesOfObjectsPassingTest:]หรือเขียนหมวดหมู่ของคุณเองเพื่อเพิ่ม handy -select:หรือ-filter:method ( ตัวอย่าง )

ต้องการให้ใครเขียนหมวดหมู่นั้นทดสอบ ฯลฯ หรือไม่? ตรวจสอบBlocksKit ( เอกสารอาร์เรย์ ) และมีหลายตัวอย่างที่มากขึ้นที่จะพบได้โดยการพูด, การค้นหาเช่น"หมวดหมู่บล็อก NSArray เลือก"


5
คุณช่วยขยายคำตอบด้วยตัวอย่างได้ไหม เว็บไซต์บล็อกมีแนวโน้มที่จะตายเมื่อคุณต้องการมากที่สุด
Dan Abramov

8
การป้องกันลิงก์เน่าคือการตัดทอนโค้ดที่เกี่ยวข้องและสิ่งที่ไม่เกี่ยวข้องออกจากบทความที่เชื่อมโยงไม่ใช่เพิ่มลิงก์เพิ่มเติม เก็บลิงก์ไว้ แต่เพิ่มโค้ดตัวอย่าง
toolbear

3
@ClayBridges ฉันพบคำถามนี้เพื่อหาวิธีกรองโกโก้ เนื่องจากคำตอบของคุณไม่มีตัวอย่างโค้ดเลยฉันจึงใช้เวลาประมาณ 5 นาทีในการขุดดูลิงก์ของคุณเพื่อที่จะพบว่าการบล็อกไม่บรรลุตามที่ฉันต้องการ หากรหัสอยู่ในคำตอบอาจต้องใช้เวลา 30 วินาที คำตอบนี้มีประโยชน์น้อยกว่า FAR หากไม่มีรหัสจริง
N_A

1
@mydogisbox et alia: มีเพียง 4 คำตอบที่นี่และมีที่ว่างมากมายสำหรับคำตอบตัวอย่างโค้ดที่เป็นประกายและเหนือกว่าของคุณเอง ฉันมีความสุขกับของฉันดังนั้นโปรดปล่อยไว้คนเดียว
ดินสะพาน

2
mydogisbox อยู่ตรงจุดนี้ หากสิ่งที่คุณทำคือการให้ลิงค์มันก็ไม่ใช่คำตอบแม้ว่าคุณจะเพิ่มลิงก์เข้าไปอีกก็ตาม ดูmeta.stackexchange.com/questions/8231 การทดสอบกระดาษลิตมัส: คำตอบของคุณสามารถยืนได้ด้วยตัวเองหรือต้องคลิกลิงก์เพื่อให้มีค่าใด ๆ โดยเฉพาะอย่างยิ่งคุณระบุว่า "คุณอาจจะดีกว่ากับการบล็อกมากกว่า NSPredicate" แต่คุณไม่ได้อธิบายว่าทำไม
Robert Harvey

16

สมมติว่าวัตถุของคุณเป็นประเภทเดียวกันทั้งหมดคุณสามารถเพิ่มวิธีการเป็นหมวดหมู่ของคลาสฐานที่เรียกใช้ฟังก์ชันที่คุณใช้สำหรับเกณฑ์ของคุณ จากนั้นสร้างวัตถุ NSPredicate ที่อ้างถึงเมธอดนั้น

ในบางประเภทกำหนดวิธีการที่ใช้ฟังก์ชันของคุณ

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

จากนั้นทุกที่ที่คุณจะกรอง:

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

แน่นอนว่าถ้าฟังก์ชันของคุณเปรียบเทียบกับคุณสมบัติที่เข้าถึงได้จากในคลาสของคุณเท่านั้นการแปลงเงื่อนไขของฟังก์ชันเป็นสตริงเพรดิเคตจะง่ายกว่า


ฉันชอบคำตอบนี้เพราะมันสง่างามและสั้นและลดความจำเป็นที่จะต้องไปเรียนรู้อีกครั้งว่าจะทำให้เป็นทางการ NSPredicate สำหรับสิ่งพื้นฐานที่สุด - การเข้าถึงคุณสมบัติและวิธีบูลีน ฉันคิดว่าไม่จำเป็นต้องใช้กระดาษห่อหุ้ม ง่ายๆ [myArray filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: @ "myMethod = TRUE"]]; ก็พอเพียง ขอบคุณ! (ฉันชอบทางเลือกอื่นเช่นกัน แต่อันนี้ดี)
Motti Shneor

3

NSPredicateเป็นวิธี NeXTStep ของการสร้างเงื่อนไขในการกรองคอลเลกชัน ( NSArray, NSSet, NSDictionary)

ตัวอย่างเช่นพิจารณาสองอาร์เรย์arrและfilteredarr:

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

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

ตัวกรองจะมีรายการที่มีอักขระ c เพียงอย่างเดียว

เพื่อให้ง่ายต่อการจดจำผู้ที่มีพื้นหลัง sql เล็กน้อย

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

1) เลือก * จาก tbl -> คอลเลกชัน

2) column1 เช่น '% a%' ->NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",@"c"];

3) เลือก * จาก tbl โดยที่ column1 เช่น '% a%' ->

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

ฉันหวังว่านี่จะช่วยได้


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

คุณสามารถดาวน์โหลดหมวดหมู่นี้ได้ที่นี่


0

ชำระเงินห้องสมุดนี้

https://github.com/BadChoice/Collection

มันมาพร้อมกับฟังก์ชั่นอาร์เรย์ง่าย ๆ มากมายที่จะไม่ต้องเขียนวนซ้ำอีก

ดังนั้นคุณสามารถทำได้:

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

หรือ

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

0

วิธีที่ดีที่สุดและง่ายที่สุดคือการสร้างวิธีนี้และส่งผ่านอาร์เรย์และค่า:

- (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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.