Як зробити та використовувати чергу в Objective-C?


107

Я хочу використовувати структуру даних черги у своїй програмі Objective-C. У C ++ я б використовував чергу STL. Яка еквівалентна структура даних у Objective-C? Як натиснути / попсувати елементи?

Відповіді:


153

Версія Бена - це стек замість черги, тож я трохи підправив його:

NSMutableArray + QueueAdditions.h

@interface NSMutableArray (QueueAdditions)
- (id) dequeue;
- (void) enqueue:(id)obj;
@end

NSMutableArray + QueueAdditions.m

@implementation NSMutableArray (QueueAdditions)
// Queues are first-in-first-out, so we remove objects from the head
- (id) dequeue {
    // if ([self count] == 0) return nil; // to avoid raising exception (Quinn)
    id headObject = [self objectAtIndex:0];
    if (headObject != nil) {
        [[headObject retain] autorelease]; // so it isn't dealloc'ed on remove
        [self removeObjectAtIndex:0];
    }
    return headObject;
}

// Add to the tail of the queue (no one likes it when people cut in line!)
- (void) enqueue:(id)anObject {
    [self addObject:anObject];
    //this method automatically adds to the end of the array
}
@end

Просто імпортуйте .h-файл, де ви хочете використовувати ваші нові методи, і називайте їх, як і будь-які інші методи NSMutableArray.

Удачі та продовжуйте кодування!


1
Я додав коментований рядок на початку dequeue для тих, хто хоче повернути нуль, а не підняти виняток при спробі вийти з порожньої черги. IMO, слідуючи поведінці NSMutableArray щодо винятку, більше відповідає какао. Зрештою, ви можете -countзаздалегідь зателефонувати, щоб перевірити, чи є якісь об’єкти для видалення. Це питання справді.
Квінн Тейлор

2
Я додав цей код до репуту github. Не соромтеся розпрощатися або повідомте мене, чи я щось не так зрозумів : github.com/esromneb/ios-queue-object Спасибі !!!
portforwardpodcast

2
Чи щось мені не вистачає, чи ця реалізація має складність O (n) у декеї? Це жахливо. Вам буде набагато краще з реалізацією кругового масиву. ця реалізація може спрацювати, але ідея де (O) декеювання болісна.
ThatGuy

11
@Wolfcow, коли ви видаляєте об'єкт з індексу 0, кожен об'єкт у масиві зміщується на один. Тому, щоб видалити один елемент, це O (n). Можливо, добре для невеликих черг, що, ймовірно, 99% часу в мобільних додатках, але це було б жахливим рішенням для великих наборів даних у критичних для часу ситуаціях. Знову ж таки, не те, що ви знайдете це в більшості об'єктивних ситуацій на С
ThatGuy

2
@ThatGuy Трохи запізнюється, але NSArray реалізується за допомогою кругового буфера, тому час виконання не буде тетою (N).
hhanesand

33

Я б не сказав, що використання NSMutableArray - це обов'язково найкраще рішення, особливо якщо ви додаєте методи з категоріями, через крихкість, яку вони можуть спричинити, якщо назви методів стикаються. Для швидкої n-брудної черги я б використав методи додавання та видалення в кінці змінного масиву. Однак, якщо ви плануєте повторно використовувати чергу, або якщо ви хочете, щоб ваш код був більш читабельним і зрозумілим для себе, виділений клас черги - це, мабуть, те, що ви хочете.

У какао немає вбудованого, але є й інші варіанти, і вам також не потрібно писати його з нуля. Для справжньої черги, яка лише додає та видаляє з кінців, круговий буферний масив є надзвичайно швидкою реалізацією. Ознайомтеся з CHDataStructures.framework , бібліотекою / рамкою в Objective-C, над якою я працював. У ньому є різноманітна реалізація черг, а також стеки, деки, відсортовані набори тощо. Для ваших цілей CHCircularBufferQueue значно швидший (тобто доступний з орієнтирами) і більш читабельний (правда, суб'єктивний), ніж використання NSMutableArray.

Однією з головних переваг використання нативного класу Objective-C замість класу ST + C ++ є те, що він безперебійно інтегрується з кодом Какао і працює набагато краще з кодування / декодування (серіалізація). Він також чудово працює зі збиранням сміття та швидким перерахуванням (обидва присутні в 10.5+, але лише останні на iPhone), і вам не потрібно турбуватися про те, що є об'єктом Objective-C, а що - об'єктом C ++.

Нарешті, хоча NSMutableArray кращий, ніж стандартний масив C при додаванні та видаленні з будь-якого кінця, це також не найшвидше рішення для черги. Для більшості застосунків це задовільно, але якщо вам потрібна швидкість, круговий буфер (або в деяких випадках зв'язаний список, оптимізований для збереження гарячих ліній кеш-пам'яті), може легко пошкодити NSMutableArray.


2
Радий, що хтось насправді відповів справжнім рішенням черги
Casebash

Усі посилання розірвані - де я можу взяти цю рамку? Я прочитав багато хороших речей про це, але не можу знайти фактичний код!
амок

Рамка звучить багатообіцяюче, але посилання на SVN все ще порушені. Будь-який шанс десь дістати код? EDIT: Отримав це з mac.softpedia.com/progDownload/…, але я не можу побачити, чи це поточна версія
Kay

Клоп репо- ревіта Дейва Делонга у цей час, як видається, є репо-репо.
Regexident

29

Наскільки мені відомо, Objective-C не забезпечує структуру даних черги. Ваша найкраща ставка - створити NSMutableArray, а потім використовувати [array lastObject], [array removeLastObject]щоб отримати предмет, і [array insertObject:o atIndex:0]...

Якщо ви цим займаєтесь багато, можливо, ви захочете створити категорію Objective-C для розширення функціональності NSMutableArrayкласу. Категорії дозволяють динамічно додавати функції до існуючих класів (навіть до тих, для яких у вас немає джерела) - ви можете створити таку чергу:

(ПРИМІТКА. Цей код насправді є стеком, а не чергою. Див. Коментарі нижче)

@interface NSMutableArray (QueueAdditions)

- (id)pop;
- (void)push:(id)obj;

@end

@implementation NSMutableArray (QueueAdditions)

- (id)pop
{
    // nil if [self count] == 0
    id lastObject = [[[self lastObject] retain] autorelease];
    if (lastObject)
        [self removeLastObject];
    return lastObject;
}

- (void)push:(id)obj
{
     [self addObject: obj];
}

@end

7
Чи знаєте ви, що ви реалізували тут стек, а не чергу?
Джим Пульс

Ах - вибачте! - див. модифікації Wolfcow нижче.
Бен Готов

Я погоджуюся, якщо ви заміните "кращу ставку" на "найпростіший варіант". :-) Пуристики структури даних та одержимості продуктивності віддають перевагу справжній черзі, але NSMutableArray може легко встати в чергу.
Квінн Тейлор

3
+1 до бен, тому що я хотів вирішити стек, навіть якщо просили черги :)
whitneyland

Все, про що я можу подумати - це біль. Ви вставляєте об'єкт на початку масиву, після цього потрібно копіювати кожен елемент на 1 пробіл щоразу, коли ви вставляєте. У цьому випадку пов'язаний список буде набагато кращим.
TheM00s3

8

Немає реального класу колекцій черг, але NSMutableArray можна використовувати для ефективного того ж самого. Ви можете визначити категорію, щоб додати поп / push методи як зручність, якщо хочете.


Правда, NSMutableArray робить досить пристойну чергу, хоча видалення з фронту - це не те, що структура масиву перевершує. Тим не менш, для невеликих черг, продуктивність все одно не є головною проблемою. Мій приятель деякий час блогував
Квінн Тейлор

7

Так, використовуйте NSMutableArray. NSMutableArray реально реалізований як 2-3 дерева; вам, як правило, не потрібно хвилюватись характеристиками продуктивності додавання або видалення об'єктів з NSMutableArray за довільними показниками.


1
NSArray (і NSMutableArray за допомогою розширення) - кластерний кластер, тобто він має кілька приватних реалізацій, які можуть використовуватися взаємозамінно поза лаштунками. Той, який ви отримуєте, зазвичай залежить від кількості елементів. Крім того, Apple може вільно змінити деталі будь-якої реалізації у будь-який час. Однак ви правильні, що він зазвичай набагато гнучкіший, ніж стандартний масив.
Квінн Тейлор

5

re: Wolfcow - Ось виправлена ​​реалізація методу відмови Вольфкова

- (id)dequeue {
    if ([self count] == 0) {
        return nil;
    }
    id queueObject = [[[self objectAtIndex:0] retain] autorelease];
    [self removeObjectAtIndex:0];
    return queueObject;
}

4

Рішення, в яких використовується категорія NSMutableArray, не є справжніми чергами, тому що NSMutableArrayвикриває операції, які є набором черг. Наприклад, вам не слід дозволяти видаляти елемент із середини черги (оскільки ці рішення категорій все ще дозволяють вам робити). Найкраще інкапсулювати функціональність, головний принцип об'єктно-орієнтованого дизайну.

StdQueue.h

#import <Foundation/Foundation.h>

@interface StdQueue : NSObject

@property(nonatomic, readonly) BOOL empty;
@property(nonatomic, readonly) NSUInteger size;
@property(nonatomic, readonly) id front;
@property(nonatomic, readonly) id back;

- (void)enqueue:(id)object;
- (id)dequeue;

@end

StdQueue.m

#import "StdQueue.h"

@interface StdQueue ()

@property(nonatomic, strong) NSMutableArray* storage;

@end

@implementation StdQueue

#pragma mark NSObject

- (id)init
{
    if (self = [super init]) {
        _storage = [NSMutableArray array];
    }
    return self;
}

#pragma mark StdQueue

- (BOOL)empty
{
    return self.storage.count == 0;
}

- (NSUInteger)size
{
    return self.storage.count;
}

- (id)front
{
    return self.storage.firstObject;
}

- (id)back
{
    return self.storage.lastObject;
}

- (void)enqueue:(id)object
{
    [self.storage addObject:object];
}

- (id)dequeue
{
    id firstObject = nil;
    if (!self.empty) {
        firstObject  = self.storage.firstObject;
        [self.storage removeObjectAtIndex:0];
    }
    return firstObject;
}

@end

можна стверджувати, що за допомогою певних методів (тобто KVC) до внутрішнього масиву зберігання можна отримати доступ та маніпулювати безпосередньо, але набагато краще, ніж використовувати категорію.
vikingosegundo

3

це моя реалізація, сподіваюся, що це допомагає.

Це вид мінімалізму, тому ви повинні тримати слід, зберігаючи нову голову на попсі та відкидаючи стару голову

@interface Queue : NSObject {
    id _data;
    Queue *tail;
}

-(id) initWithData:(id) data;
-(id) getData;

-(Queue*) pop;
-(void) push:(id) data;

@end

#import "Queue.h"

@implementation Queue

-(id) initWithData:(id) data {
    if (self=[super init]) {
        _data = data;
        [_data retain];
    }
    return self;
}
-(id) getData {
    return _data;
}

-(Queue*) pop {
    return tail;
}
-(void) push:(id) data{
    if (tail) {
        [tail push:data];
    } else {
        tail = [[Queue alloc]initWithData:data];
    }
}

-(void) dealloc {
    if (_data) {
        [_data release];
    }
    [super release];
}

@end

2

Чи є якась конкретна причина, що ви не можете просто використовувати чергу STL? Ціль C ++ - це супернабір C ++ (просто використовуйте .mm як розширення замість .m для використання Objective C ++ замість Objective C). Тоді ви можете використовувати STL або будь-який інший код C ++.

Одне з питань використання черги / вектору / списку STL тощо з об’єктами Objective C полягає в тому, що вони, як правило, не підтримують управління пам'яттю збереження / випуску / автовивільнення. Це легко обійти з контейнером класу контейнерів Smart + Point C ++, який зберігає об'єкт Objective C при його створенні та випускає при знищенні. Залежно від того, що ви ставите в чергу STL, це часто не є необхідним.


1
Це насправді не здається гарною ідеєю ... тільки тому, що ти можеш зробити щось, не означає, що ти повинен. Затягування всієї екосистеми STL та C ++ лише для чергового класу, безумовно, є надмірним.
екстроп-двигун

3
Власне, відтоді, як це було розміщено, це стало набагато кращою ідеєю. Об'єктивний C ++ / ARC означає, що ви можете використовувати контейнери STL з об'єктовими вказівниками Objective C, і все це просто працює. ARC дбає про управління пам'яттю автоматично для вас в структурах C ++. Я б також загалом заперечував, що C ++, будучи набагато кращим C, робить Objective-C ++ кращим вибором взагалі, ніж звичайний Objective C (даючи такі речі, як клас enum). І я дуже сумніваюся, що додавання STL / C ++ чинить помітний вплив на розмір будь-якого додатка в реальному світі.
Peter N Lewis

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.