Выбор ближайших 3 дат из списка

Я работаю над приложением телегида и пытаюсь получить ближайшие 3 даты из NSArray с помощью NSDictionary. Пока все хорошо, но я пытался выяснить, как я могу сделать это наилучшим образом, используя как можно меньше памяти и кода (следовательно, уменьшая вероятность ошибок или сбоев). Массив уже отсортирован.

У меня словарь со всеми каналами показывает за один день. Словарь не содержит NSDate (называется дата).
Допустим, на канале 8 передач, и сейчас время 11:45. шоу №3 началось в 11:00 и закончилось в 12:00, шоу №4 началось в 12:00 и закончилось в 13:00, шоу №5 с 13:00 до 14:00 и т. д.
Как я мог fetch show #3 (которое началось в прошлом!), #4 и #5 самые быстрые (по памяти) и самые легкие из моего массива словарей?

В настоящее время я выполняю цикл for, извлекающий каждый словарь, а затем сравниваю дату словаря с текущей датой. И вот где я застрял. Или, может быть, у меня просто переутомление мозга.

Мой текущий код (через некоторое время тестирования разных вещей):

- (NSArray*)getCommingProgramsFromDict:(NSArray*)programs amountOfShows:(int)shows
{
    int fetched = 0;
    NSMutableArray *resultArray = [[NSMutableArray alloc] init];
    NSDate *latestDate = [NSDate date];

    for (NSDictionary *program in programs)
    {
        NSDate *startDate = [program objectForKey:@"date"];

        NSLog(@"Program: %@", program);
        switch ([latestDate compare:startDate]) {
            case NSOrderedAscending:
                NSLog(@"latestDate is older, meaning the show starts in the future from latestDate");
                // do something
                break;
            case NSOrderedSame:
                NSLog(@"latestDate is the same as startDate");
                // do something
                break;
            case NSOrderedDescending:
                NSLog(@"latestDate is more recent, meaning show starts in the past");
                // do something
                break;
        }

        // Now what?
    }

    return resultArray;
}

Я пишу это для iOS 5, используя ARC.


person Paul Peelen    schedule 01.10.2011    source источник


Ответы (3)


После вашего EDIT и объяснения вот еще один ответ, который, надеюсь, лучше соответствует вашему вопросу.

Идея состоит в том, чтобы найти индекс следующего шоу (дата начала после настоящего момента). Как только вы его получите, будет легко получить шоу с предыдущим индексом (в эфире) и 2 шоу после него.

NSUInteger indexOfNextShow = [arrayOfShows indexOfObjectPassingTest:^BOOL(id program, NSUInteger idx, BOOL *stop) {
    NSDate* startDate = [program objectForKey:@"date"];
    return ([startDate timeIntervalSinceNow] > 0); // startDate after now, so we are after the on-air show
}];

На этом этапе indexOfNextShow содержит индекс шоу в вашем NSArray, которое будет транслироваться после текущего шоу. Таким образом, в соответствии с вашим вопросом вам нужны объекты с индексами indexOfNextShow-1 (показ в прямом эфире), indexOfNextShow (следующий показ) и indexOfNextShow+1 (показ после следующего).

// in practice you should check the range validity before doing this
NSIndexSet* indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(indexOfNextShow-1,3)];
NSArray* onAirShowAnd2Next = [arrayOfShows objectsAtIndexes:indexes];

Очевидно, что на практике вы должны добавить некоторые проверки (например, indexOfNextShow>0 перед попыткой доступа к объекту с индексом indexOfNextShow-1 и indexOfNextShow+1 не превысит общее количество показов в вашем массиве).

Преимущество этого заключается в том, что, поскольку ваш массив шоу уже отсортирован по startDate, indexOfObjectPassingTest: возвращает первый объект, прошедший тест, и прекращает итерацию, как только находит нужный объект. Таким образом, это краткий, легко читаемый код и относительно эффективный.

.

person AliSoftware    schedule 01.10.2011
comment
Кажется, это правильно, я тоже пробовал ... но, похоже, iOS5 (ARC) не нравится. Я получаю сообщение об ошибке incompatible block pointer types sending 'void (^)(__strong id, NSUInteger, BOOL *)' to parameter of type 'BOOL (^)(__strong id, NSUInteger, BOOL *)' [3]". Кроме того, я правильно понял, вы пропустили ]; в конце? - person Paul Peelen; 02.10.2011
comment
Я удалил свой другой комментарий. Это также не работает для меня. Та же ошибка. Google по indexOfObjectPassingTest, но ничего не помогает. Странный. - person Paul Peelen; 02.10.2011
comment
Ах да, извините, не проверил мой код. Вы правы насчет пропавшего ]. Также другая проблема не связана с iOS5 или ARC. Проблема в том, что мой блок не вернул значение (я забыл оператор return YES), поэтому компилятор утверждает, что блок возвращает void, тогда как он ожидает блок, который возвращает BOOL (согласно сигнатуре метода indexOfObjectPassingTest:). Я только что отредактировал свой код, чтобы исправить эти проблемы, одновременно упростив блок (indexOfObjectPassingTest все равно автоматически останавливается при первом совпадении, поэтому я не уверен, что *stop нужен в случае этого метода) - person AliSoftware; 02.10.2011
comment
не должен ли indexOfPassingTest перебирать набор внутри себя? Зачем повторять и тестировать каждый элемент, когда время легко компенсировать? Если вы можете создавать индексы в отсортированном списке, вы можете мгновенно перейти прямо сейчас (или к любому другому маркеру времени). - person bryanmac; 02.10.2011
comment
@AliSoftware У меня возникла такая идея, когда я сравнил ее с результатами Google. Но я сделал неправильный возврат в своем коде, когда тестировал его. Я проверю это снова. - person Paul Peelen; 02.10.2011
comment
Я изменил NSRange на NSMakeRange, и, кажется, это работает. Огромное спасибо! - person Paul Peelen; 02.10.2011

Я не уверен, что понял структуру вашей модели, у вас есть NSArray шоу, каждое шоу представляет собой NSDictionary, содержащее NSDate шоу вместе с другой информацией, верно?

Одна из идей состоит в том, чтобы отсортировать эти NSArray шоу в соответствии с расстоянием между временем начала шоу и текущим моментом.

NSArray* shows = ... // your arraw of NSDictionaries representing each show
NSArray* sortedShows = [shows sortedArrayUsingComparator:^(id show1, id show2) {
    NSTimeInterval ti1 = fabs([[show1 objectForKey:@"startDate"] timeIntervalSinceNow]);
    NSTimeInterval ti2 = fabs([[show2 objectForKey:@"startDate"] timeIntervalSinceNow]);
    return (NSComparisonResult)(ti1-ti2);
}];

Тогда, конечно, в этот момент легко взять только 3 первых шоу массива sortedShows.

Если я неправильно понял структуру вашей модели, отредактируйте свой вопрос, чтобы уточнить ее, но я уверен, что тогда вы сможете адаптировать мой код под свою модель

person AliSoftware    schedule 01.10.2011
comment
Спасибо! Я думаю, вы меня не поняли, поэтому я обновил свой вопрос. Массив уже отсортирован по порядку начала шоу. Что мне нужно, так это шоу, которое сейчас в эфире + два следующих шоу. Итак, если сейчас время 11:45, мне бы понравился результат шоу, которое началось с 11:00 до 12:00 + следующие два шоу. - person Paul Peelen; 02.10.2011
comment
Хорошо, тогда я написал еще один ответ (поскольку это совершенно другое решение, я думаю, что это лучше, чем редактирование моего исходного ответа), который, надеюсь, лучше ответит на ваш вопрос. - person AliSoftware; 02.10.2011
comment
Я считаю, что для этого все равно придется перебирать весь набор данных. Самый быстрый из возможных алгоритмов будет переходить прямо к текущему индексу временных интервалов и выполнять итерацию только 3 раза, чтобы найти следующие три. - person bryanmac; 02.10.2011
comment
Да, я не знал, что список шоу уже отсортирован, именно поэтому, поскольку Пол отредактировал свой вопрос и лучше объяснил это, я предложил лучшее решение, которое еще более краткое, а также эффективное, поскольку оно не будет повторяться через все шоу больше. - person AliSoftware; 02.10.2011

Вопрос задает «самый быстрый (по памяти)». Вы ищете самый быстрый или самый чувствительный к памяти/следу? В алгоритмах часто существует компромисс между пространством и временем, поэтому, чтобы сделать его быстрее, вы обычно делаете это, добавляя индексы и другие структуры данных поиска, которые увеличивают объем памяти.

Для этой проблемы прямой реализацией будет итерация по каждому каналу и каждому элементу, сравнивающая каждый с тремя лучшими, хранящимися в памяти. Но это может быть медленно.

С дополнительным хранилищем у вас может быть дополнительный массив, который индексирует временные интервалы (достаточно один раз в 15 минут?), а затем гирляндная цепочка демонстрирует эти временные интервалы. Учитывая текущее время, вы можете индексировать прямо в слот текущего времени, а затем искать следующий набор шоу. Массив будет иметь указатели на те же объекты, на которые указывают словари. Это дополнительная структура данных для оптимизации одного конкретного шаблона доступа, но за это приходится платить — больше памяти.

Это увеличило бы ваш след, но было бы очень быстро, так как это просто смещение индекса массива.

Наконец, вы можете хранить все свои шоу в базе данных sqlite или CoreData и решить свою проблему с помощью одного запроса. Позвольте движку sql сделать всю тяжелую работу. это также сохранит ваш отпечаток памяти в разумных пределах.

Надеюсь, это натолкнет на некоторые идеи.

ИЗМЕНИТЬ:

Грубый пример, показывающий, как можно построить таблицу просмотра — массив со слотами для каждых 15 минут. Мгновенно перейти к текущему временному интервалу, поскольку это просто смещение массива. Затем вы проходите абсолютное количество прохождений — следующие три, и вы выбываете. Итак, это смещение массива с 3 итерациями.

Большая часть кода построена на дате - таблица поиска, поиск временного интервала и цикл тривиальны.

NSInteger slotFromTime(NSDate *date)
{
    NSLog(@"date: %@", date);

    NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:(NSHourCalendarUnit | NSMinuteCalendarUnit) fromDate:date];
    NSInteger hour = [dateComponents hour];
    NSInteger minute = [dateComponents minute];
    NSInteger slot = (hour * 60 + minute)/15;
    NSLog(@"slot: %d", (int)slot);

    return slot;
}

int main (int argc, const char * argv[])
{
    // An array of arrays - the outer array is an index of 15 min time slots.
    NSArray *slots[96];
    NSDate *currentTime = [NSDate date];
    NSInteger currentSlot = slotFromTime(currentTime);

    // populate with shows into the next few slots for demo purpose
    NSInteger index = currentSlot;
    NSArray *shows1 = [NSArray arrayWithObjects:@"Seinfeld", @"Tonight Show", nil];
    slots[++index] = shows1;
    NSArray *shows2 = [NSArray arrayWithObjects:@"Friends", @"Jurassic Park", nil];
    slots[++index] = shows2; 

    // find next three -jump directly to the current slot and only iterate till we find three.
    // we don't have to iterate over the full data set of shows
    NSMutableArray *nextShow = [[NSMutableArray alloc] init];
    for (NSInteger currIndex = currentSlot; currIndex < 96; currIndex++)
    {
        NSArray *shows = slots[currIndex];
        if (shows)
        {
            for (NSString *show in shows)
            {
                NSLog(@"found show: %@", show);
                [nextShow addObject:show];
                if ([nextShow count] == 3) 
                    break;
            }
        }

        if ([nextShow count] == 3) 
            break;        
    }

    return 0;
}

Это выводит:

2011-10-01 17:48:10.526 Craplet[946:707] date: 2011-10-01 21:48:10 +0000
2011-10-01 17:48:10.527 Craplet[946:707] slot: 71
2011-10-01 17:48:14.335 Craplet[946:707] found show: Seinfeld
2011-10-01 17:48:14.336 Craplet[946:707] found show: Tonight Show
2011-10-01 17:48:21.335 Craplet[946:707] found show: Friends
person bryanmac    schedule 01.10.2011
comment
Спасибо за ответ. Я думаю, что 2-й вариант, который вы написали, мне подойдет (хотя я еще не до конца в нем разбираюсь). Вариант SQL lite не был бы так хорош, поскольку приложения (в настоящее время) используют основные данные. Он извлекает данные из внешней службы и сохраняет их в своей памяти. Первый - это тот, о котором я думал, но не вижу, чтобы он был таким хорошим (следовательно, медленным). Не могли бы вы привести небольшой пример кода того, что именно вы имеете в виду под № 2, чтобы я вас правильно понял? - person Paul Peelen; 02.10.2011
comment
Потрясающий! Теперь я понимаю, что вы имеете в виду. Прохладный. Я попробую! - person Paul Peelen; 02.10.2011
comment
Хорошее решение ;) В любом случае, будьте осторожны, если у вас есть шоу, которые проходят в течение нескольких дней (циклирование слотов), или если ваш массив содержит список шоу в разные дни (перегрузка слотов). - person AliSoftware; 02.10.2011
comment
Хорошие моменты. Одним из возможных решений было бы зарегистрировать его в каждом слоте, который он потребляет. - person bryanmac; 02.10.2011
comment
Еще одна оптимизация заключается в том, что слоты могут быть просто часами со смещением на объекте (начинается с 5 после часа). - person bryanmac; 02.10.2011