mr_ivan777
mr_ivan777

Reputation: 401

Swift + CoreData. Get N random objects from DB. N << size(DB)

I am trying to make app, that need to get N random objects from CoreData.

The problem is that the database size is much larger than the number of objects that I want to get. Therefore, it would be desirable to have a method that does not affect all the records in the database.

Is it possible to implement this without adding extra fields to the database (eg, id)?

I want to receive different responses to requests, so option "pre-sorted using some random function" is not working.

Upvotes: 1

Views: 819

Answers (3)

Dale Clifford
Dale Clifford

Reputation: 1511

Swift 5 version

// First count the records
let countRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Dictionary");
        
do {
    let count = try managedObjectContext.count(for: countRequest)
                

    // Then run the query again to choose a random row

    let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Dictionary");
    request.fetchLimit = 5
    request.fetchOffset = Int(arc4random_uniform(UInt32(count)))
                    
    do {
        // Here's your results
        let results = try managedObjectContext.fetch(request) as! [Dictionary]
    }
    catch let error as NSError {
        print("Could not fetch \(error), \(error.userInfo)")
    }

}
catch let error as NSError {
    print("Could not fetch \(error), \(error.userInfo)")
}

Upvotes: 1

Mundi
Mundi

Reputation: 80271

The problem with fetching n objects one by one could be performance. Depending on how large n is, this might not scale.

Instead, you could take advantage of the fact that Core Data is pretty efficient when handling large number of objects. You could fetch tens of thousands of objects without too large a memory footprint due to a mechanism called "faulting".

Thus, i would suggest that you fetch all objects and simply pick some out from the result.

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Response"];
NSArray *all = [managedObjectContext executeFetchRequest:request error:nil];

NSMutableArray *pickedAnswers = [NSMutableArray array];
int remaining = 10;

if (all.count < remaining) { /* abort */ }
while (remaining > 0) {
   Response *response = all[arc4random_uniform(all.count)];
   if (![pickedAnswers containsObject:response]) {
        [pickedAnswers addObject:response];
        remaining--;
   }
}

Note that another elegant answer would be to first shuffle the array (as shown here and then pick the first n elements). That would eliminate unnecessary runs through the while loop.

Upvotes: 3

bteapot
bteapot

Reputation: 2027

Count all the records first:

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"<your entity name>"];
NSUInteger count = [context countForFetchRequest:request error:NULL];

Use fetchOffset:

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"<your entity name>"];
request.fetchLimit = 1;
request.fetchOffset = arc4random_uniform(count);
NSArray *result = [context executeFetchRequest:request error:NULL];

Repeat N times to get desired number of objects.

Upvotes: 5

Related Questions