Как стать автором
Обновить

Комментарии 80

Я бы во втором вопросе NSMutableDictionary использовал.
Типа:
1. Сплитим строку по разделителям
2. Циклом по токенам и dictionary[token] = dictionary[token] + 1. Предварительно token можно конечно лоуэркейсить (например).
3. Ну и выбрать N самых частых слов (после сортировки)

Что-то подсказывает, что и по размеру код был бы меньше.

Модификаций алгоритма для выбора N значений мне не известны.

Как вариант можно с использованием Heap (кучи) решить.
В предоженном мной варианте нет сортировки. И если N существенно меньше количества различных слов на входе, мой вариант будет намного быстрее. Если бы вы предложили законченный код, можно было бы сравнить быстродействие.
а как же indexToInsert? он наверняка не мгновенный
Не мгновенный. Массив countsOfSelectedWords всегда отсортирован и длины не более N. В задании этого нет, но я предполагал, что N будет существенно меньше количества слов на входе и поэтому старался уйти от сортировки всего массива слов.
я думаю в настоящих текстах N не бывает существенно меньше количества слов
в вашем комментарии только два слова повторяются: «слов» и «N», а текста вон сколько
нет, еще «не», но у меня case-sensitive алгоритм, надо бы исправить
Ваше решение работает за O(m log m + n^2), где m — длина входной строки. Или O(m + n^2), если NSCountedSet построен на хеш-таблице.

Решение же с кучей работает за O(m log m + n log n), что всегда быстрее.
Откуда взялся n*n?
Ой, да, извиняюсь, m*n :)

Сложность следующей строчки O(n) и выполняется она O(m) раз, где m — длина оригинальной строки, т.к. количество уникальных слов, в общем случае, O(m).
 [selectedWords insertObject:word atIndex:indexToInsert]; 
с такой оценкой согласен.
Сразу скажу, что я абсолютно не имею никакого отношения к программированию под iOS, но тем не менее у меня есть некоторые замечания к вашим решениям:

1. Довольно спорный момент. Возможно они наоборот пытаются отсеять любителей регулярок. Задача довольно простая и легко решается без использования регулярных выражений (но честно говоря я бы всё равно сделал регулярку, но значительно короче чем у вас)

4. По моему с магическим числом вы немного не угадали: судя по размеру числа его автор преследовал цель сделать «гарантированно продолжительный цикл». Считаю, что правильным будет заменить цикл на while с условием выхода по "[operation isCancelled]"

4. По моему вместо магического числа должен быть размер массива, а не константа или еще что-то в этом роде.

5. Три запроса на выборку данных из 2 таблиц? Это не серьёзно. Почитайте о JOIN. У вас запрос станет проще и читабельнее

UPD: Изменил 4 пункт, я немного не правильно прочитал код
По поводу «заменить цикл на while с условием выхода по [operation isCancelled]». Вы не в теме. Это не условие выхода из цикла. NSOperation — это кусок какой-то работы, который нужно выполнить. Обычно его добавляют в очередь, откуда эти операции выполняются по одному или параллельно, в зависимости от очередей и приоритетов. Операция может быть отменена. Длительно выполняющаяся операция должна периодически проверять не отменили ли ее и в случае, если отменили, как можно быстрее прекратить работу. Но, возможно, я вас не правильно понял. Вообще, любой for можно заменить на while — это вопрос вкуса.
Я прохлопал тот момент, что по индексу обращаются к элементам массива, потому и отредактировал свой ответ.
Я бы во втором задании просто отсортировал бы массив count'ов:

NSMutableArray *wordsByNumbersArray = [NSMutableArray array];
[set enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
    [wordsByNumbersArray addObject:@{@"word": obj,
                                    @"count": @([set countForObject:obj])}];
}];

[wordsByNumbersArray sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"count"
                                                                                 ascending:NO]]];
return [wordsByNumbersArray subarrayWithRange:NSMakeRange(0, N)]


Первое задание решал бы тоже regexp'ом, единственное что — не очень понятно что за \\A и \\z по бокам стоят. Ну и {0, 1} на "?" заменить бы.
задача 2
NSString *wordsString = @"test test hello world test hello";
NSArray *words = [wordsString componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

NSCountedSet *wordsSet = [NSCountedSet setWithArray:words];
NSArray *uniqueWords = [wordsSet allObjects];
NSArray *sortedWords = [uniqueWords sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    NSUInteger count1 = [wordsSet countForObject:obj1];
    NSUInteger count2 = [wordsSet countForObject:obj2];
    
    return [@(count2) compare:@(count1)];
}];

NSArray *result = [sortedWords subarrayWithRange:NSMakeRange(0, MAX([sortedWords count], 3))];
А если после некоторых слов будут запятые или другие символы?
Ну можно punctuationCharacterSet еще приделать.
Вообще никак не могу найти хорошую статью про токенизацию строк, казалось что на NSHipster было, но как будто бы нету. Там рассказывалось как по-уму делить на слова (и не только) в разных локалях.
Типа этого, но не это и как будто бы без Core Foundation.
Это было тут.

И надо было заюзать enumerateSubstringsInRange:options:usingBlock: c опцией NSStringEnumerationByWords
Улучшенная версия

NSString *wordsString = @"test, test hello! world test... hello";

NSCountedSet *wordsSet = [NSCountedSet set];
[wordsString enumerateSubstringsInRange:NSMakeRange(0, [wordsString length])
                                options:NSStringEnumerationByWords
                             usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
                                 [wordsSet addObject:substring];
                             }];

NSArray *uniqueWords = [wordsSet allObjects];
NSArray *sortedWords = [uniqueWords sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    NSUInteger count1 = [wordsSet countForObject:obj1];
    NSUInteger count2 = [wordsSet countForObject:obj2];
    
    return [@(count2) compare:@(count1)];
}];

NSArray *result = [sortedWords subarrayWithRange:NSMakeRange(0, MAX([sortedWords count], 3))];
и MIN вместо MAX
в первом задании вроде как можно объединить a-zA-Z в a-Z, внутри квадратных скобок точку и дефис можно не экранировать. Не предусмотрено, что длина может быть 1 символ
Предусмотрено — там {0,1} в самом конце.
Да, извиняюсь, как-то проглядел. Но всё равно можно оптимизировать, не только длину, но и читаемость — избавиться от «или» (|). Сейчас если соберусь с силами, то попробую переписать. Как минимум, можно {0,1} заменить на?
раз caseInsensitive, то можно совсем убрать эти A-Z, использовать \d вместо 0-9.
тестировал в консоли девелопера в хроме
/^[a-z]([a-z\d.-]{0,18}[a-z\d])?$/i
получилось так, но чувствую подвох
Предусмотрено, что длина может быть в 1 символ. В «статье» есть ссылка — можете загрузить проект приложения и проверить.
Задача 5
не шибко силен в SQL, но наверное как-то так

SELECT `tracks`.`name` 
FROM `track_downloads`
LEFT JOIN `tracks` ON `tracks`.`id` = `track_downloads`.`track_id`
GROUP BY `track_downloads`.`track_id`
WHERE COUNT(`track_downloads`.`track_id`) > 1000


Призываю знатоков, интересно увидеть хорошее решение
Скорее уж так, хотя в деталях мог ошибиться
SELECT t.name, COUNT(d.track_id) as c
FROM track_downloads d
INNER JOIN tracks t on t.id = d.track_id
GROUP BY d.track_id
HAVING c > 1000
Я обратил внимание, что надо селектнуть только имена, может это важно. Спасибо что напомнили про having.
А какой здесь профит от inner join?
в случае LEFT JOIN у вас могут быть записи не вошли во внутреннее соединение (NULL)
Это я понимаю, думал вдруг это влияет на производительность в лучшую сторону. Представил, что таблицы гарантированно полные и поэтому написал left.
Можно посмотреть и оценить как происходит запрос поставив перед SELECT в вашем и моем примере EXPLAIN
Explain я бы конечно воспользовался, но время позднее, лень создавать таблицу и тестовые данные. Может valeriyvan еще не удалил и попробует?
Не удалил. Удивлен, что sqlite поддерживает expain. Завтра попробую.
Я обратил внимание, что надо селектнуть только имена, может это важно.

Если HAVING используется то в SELECT нужно указать то что в GROUP BY или агрегированное значение, поэтому в SELECT есть COUNT(d.track_id)
SELECT
	tracks.name AS "Track name",
	Count(tracks.id) AS Downloads
FROM
	track_downloads
INNER JOIN tracks ON track_downloads.track_id = tracks.id
GROUP BY
	tracks.id
HAVING
	Downloads > 1000
Гениально! Чувствуется специалист.
В четвертом вопросе мне кажется важным, что operationQueue вызовет операцию на неглавном треде, где совсем нет autorelease pool, поэтому мы должны его сделать, чтобы process(data) всё освободило. Но queue может быть и mainQueue так-то.
НЛО прилетело и опубликовало эту надпись здесь
В каждом потоке есть NSAutoreleasePool.

Цитирую документацию: Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see “Threads”). As new pools are created, they get added to the top of the stack. When pools are deallocated, they are removed from the stack. Autoreleased objects are placed into the top autorelease pool for the current thread. When a thread terminates, it automatically drains all of the autorelease pools associated with itself.
Какая-то довольно мутная формулировка если честно.
Ну и приходит в голову мысль, что NSOperationQueue придерживает пул тредов, которые не terminate, а просто засыпают.
Возможно, я тут что-то путаю, но от этой части мануала у меня больше вопросов, чем ответов.
Цитирую документацию: Note: In iOS 4 and later, operation queues use Grand Central Dispatch to execute operations. Prior to iOS 4, they create separate threads for non-concurrent operations and launch concurrent operations from the current thread. For a discussion of the difference between concurrent and non-concurrent operations and how they are executed, see NSOperation Class Reference.

Для OSX: Operation queues usually provide the threads used to run their operations. In OS X v10.6 and later, operation queues use the libdispatch library (also known as Grand Central Dispatch) to initiate the execution of their operations. As a result, operations are always executed on a separate thread, regardless of whether they are designated as concurrent or non-concurrent operations. In OS X v10.5, however, operations are executed on separate threads only if their isConcurrent method returns NO. If that method returns YES, the operation object is expected to create its own thread (or start some asynchronous operation); the queue does not provide a thread for it.

Смысл такой, что GCD (Grand Central Dispatch) создает потоки только по необходимости, утилизируя те потоки что у него есть. NSOperationQueue в новых системах работает через GCD.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Отвечу по пунктам.
Отвечу по пунктам.

1. Что не понравилось с error не понял. Да, если не нужен код ошибки, можно было передать NULL вместо &error. Результат сравнения имеет тип int, а не BOOL. В данном конкретном случае явное приведение к BOOL избыточно. В реальном коде я использовал не функцию а блок, который передавал дальше. поэтому без приведения была ошибка несоответствия типа блоков.

2. Кода будет меньше, но появится сортировка. У меня нет сортировки. Да, есть многократный поиск, но поиск, во-первых по массиву длины N (в задании этого нет, но я предполагал, что N значительно меньше количества слов на входе), во-вторых, поиск этот выполняется по упорядоченному массиву.

3. Мне показалось, что тот кто писал вопрос или не знал об этом методе или имел в виду что-то другое, чего я не понял.

4. Цитата из документации: Important: If you use Automatic Reference Counting (ARC), you cannot use autorelease pools directly. Instead, you use @autoreleasepool blocks. В ARC коде вы можете или завернуть что вам нужно в @autoreleasepool или не делать этого. В не ARC коде вы можете «ручками» каждую сотую итерацию цикла сделать [pool release]; pool = [[NSAutoreleasePool alloc] init];
НЛО прилетело и опубликовало эту надпись здесь
2. И почему в блоке не просто [n1 compare: n2]? Наверное, потому что я — тормоз. Другого ответа у меня нет.

4. Разумеется. И при каждой итерации NSAutoreleasePool будет уничтожаться и создаваться новый. А теперь создайте пул не каждую итерацию, а каждую сотую. Опять я тормоз. Cделать вложенный цикл, который завернуть в @autoreleasepool {}.

Из половины ответов чему-то да научился. Вот что значит поговорить с умными людьми.
НЛО прилетело и опубликовало эту надпись здесь
Пулы под ARC эффективнее своих до-ARC собратьев, кстати. Если не ошибаюсь, создаются на стеке (де-факто являясь C++-объектами, в этом можно убедитья в исходниках Objective C runtime), в отличие от NSAutoreleasePool, которые являлись полноценными Objective C-объектами со всеми вытекающими (pun not intended).
> [pool release]; pool = [[NSAutoreleasePool alloc] init];

Лучше просто [pool drain];
Да, вы правы, конечно же.
1. Зачем стараться решить одной регуляркой все задачи на свете разом? Если разбить на логические условия, то и вероятность ошибки меньше, и читабельнее. Кроме того, легче будет вносить изменения, когда удастся убедить начальство, что такие требования к паролю неразумны. :)

Что-то вроде:
// Length
if (length < 1 || length > 20) return NO;

// First character
if (!checkRegEx(@"\\A[a-z]")) return NO;

// Last character
if (!checkRegEx(@"[a-z\\d]\\z")) return NO;

// General character set
if (!checkRegEx(@"[a-z\\d-.]\\z")) return NO;
последнее условие хромает
Да, время позднее, и copy-paste подвёл. :) Но суть, я думаю, ясна.
Дальше пробежал по-диагонали, но бросилось в глаза про блоки в п. 4.

[operation addExecutionBlock:^{
    ...
    if ([operation isCancelled]) return;
    ...
}];

Использование __weak здесь не опционально, а строго обязательно! Ибо operation будет захвачена блоком, а блок будет захвачен operation. Классический retain cycle и утечка памяти.
Да, и ещё надо внимательно посмотреть, что есть queue и data[i]. Ибо если это переменные экземпляра, то произойдёт неявная подстановка self->data[i], и одного __weak для operation будет недостаточно, т.к. self проретейнится блоком, и получится более сложный retain цикл self > queue > operation > block > self.
НЛО прилетело и опубликовало эту надпись здесь
Да, на эту тему есть холивары на StackOverflow.
Ниже расписал подробнее.
НЛО прилетело и опубликовало эту надпись здесь
У меня, вообще-то, изначально так и было. Но после долгого медитирования (=гугления), решил что это излишне. На эту тему есть противоположные мнения. См., например, ответы здесь. Я, вообще, предпочитаю, делать так, как учит Apple на WWDC. Один из ответов по ссылке выше ссылаются на сессию WWDC2012, и мне кажется, я даже был на ней. Вполне возможно, что LLVM генерирует правильный код в любом случае.

Пересмотрю сессию WWDC. Перечитаю документацию. Эта тема с блоками и операциями и как оно, все таки, правильно, вполне может быть темой отдельной статьи.
Что-то я не понял, как медитация и гугление привели Вас к неправильному решению. :) На том же SO в правильном ответе используется __weak, и в принятом в итоге тоже (изначально он был неверен, но это указано в UPDATE).

Да и в той же сессии WWDC объясняется, откуда берется retain цикл:

и как его обойти:


Здесь двух мнений быть не может. Использование __weak обязательно!

Но всегда лучше проверить на практике.

Для этого возьмём упрощенный пример без __weak, сразу после добавления операции очистим очередь, а через какое-то время проверим слабую ссылку на операцию на предмет утечки:

Вспомогательный макрос для примера
#define after(DELAY, BLOCK) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, \
                                           DELAY * NSEC_PER_SEC), \
                                           dispatch_get_main_queue(), BLOCK)

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *op = [[NSBlockOperation alloc] init];
    [op addExecutionBlock:^{
        for (int i = 0; i < 5; ++i) {
            if ([op isCancelled]) break; // <- strong
            NSLog(@"i = %d;", i);
        }
    }];
    [queue addOperation:op];
    [queue cancelAllOperations];
    
    __weak NSBlockOperation *weakOperation = op;
    after(0.5, ^{
        NSLog(@"Operation: %p", weakOperation);
    });
// Operation: 0x7527ae0

Как видим, получился retain цикл, и операция осталась в памяти.

Теперь в блоке воспользуемся слабой ссылкой на операцию:
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *op = [[NSBlockOperation alloc] init];
    __weak NSBlockOperation *weakOperation = op;
    [op addExecutionBlock:^{
        for (int i = 0; i < 5; ++i) {
            if ([weakOperation isCancelled]) break; // <- weak
            NSLog(@"i = %d;", i);
        }
    }];
    [queue addOperation:op];
    [queue cancelAllOperations];
    
    after(0.5, ^{
        NSLog(@"Operation: %p", weakOperation);
    });
// Operation: 0x0


Retain цикл разорвался, и память освободилась.

Теперь вернём data из примера, и представим, что это (как и queue) — переменные экземпляра:
@implementation TTTestRetainCycle{
    NSOperationQueue *_queue; // <-
    NSArray *_data; // <-
}

-(void)dealloc{
    [_queue cancelAllOperations];
}

-(void)start{
    _queue = [[NSOperationQueue alloc] init];
    _data = @[@1, @2, @3, @4, @5];
    NSBlockOperation *op = [[NSBlockOperation alloc] init];
    __weak NSBlockOperation *weakOperation = op;
    [op addExecutionBlock:^{
        for (int i = 0; i < 5; ++i) {
            if ([weakOperation isCancelled]) break;
            NSLog(@"data[%d] = %d;", i, [_data[i] integerValue]); // <-
            sleep(1); // <-
        }
    }];
    [_queue addOperation:op];
}

@end


Создадим этот экзмепляр, запустим очередь и операцию с блоком, а через 1.5 секунды его прибьём, ожидая, что очередь остановится:
    __block TTTestRetainCycle *test = [TTTestRetainCycle new];
    [test start];
    after(1.5, ^{
        NSLog(@"Stopping");
        test = nil;
    });
// data[0] = 1;
// data[1] = 2;
// Stopping
// data[2] = 3;
// data[3] = 4;
// data[4] = 5;


Однако как мы видим очередь не остановилась. Это потому, что _data[i] в блоке неявно разворачивается в self->_data[i] и блок захватывает self. В результате, как я уже упоминал в сообщении выше, получается retain cycle: self -> _queue -> operation -> block -> self. Для разрыва цикла нужно использовать слабую ссылку weakSelf->_data[i], однако разыменовывание слабых указателей запрещено. Значит, классически создаём strong указатель из weak:
-(void)start{
    // ...
    __typeof(self) __weak weakSelf = self;
    [op addExecutionBlock:^{
        for (int i = 0; i < 5; ++i) {
            if ([weakOperation isCancelled]) break;
            __typeof(self) strongSelf = weakSelf; // <-
            if (!strongSelf) break;  // <-
            NSLog(@"data[%d] = %d;", i, [strongSelf->_data[i] integerValue]); // <-
            sleep(1);
        }
    }];
    [_queue addOperation:op];
}
// data[0] = 1;
// data[1] = 2;
// Stopping


И получаем нужное поведение. Всё? Не совсем. А что будет, если код обработки у нас будет чуть сложнее, и произведёт пару retain/autorelease для нашего экземпляра? Проверим.

Вспомогательный класс, компилируется с -fno-objc-arc
@implementation TTRetainer
+(void)retain:(id)object{
    [object retain];
}
+(void)autorelease:(id)object{
    [object autorelease];
}
@end

    // ...
    [op addExecutionBlock:^{
        for (int i = 0; i < 5; ++i) {
            if ([weakOperation isCancelled]) break;
            __typeof(self) strongSelf = weakSelf;
            if (!strongSelf) break;
            [TTRetainer retain:strongSelf]; // <-
            [TTRetainer autorelease:strongSelf]; // <-
            NSLog(@"data[%d] = %d;", 0, [strongSelf->_data[0] integerValue]);
            sleep(1);
        }
    }];
    // ...
// data[0] = 1;
// data[0] = 1;
// Stopping
// data[0] = 1;
// data[0] = 1;
// data[0] = 1;

Как мы и предположили, объект продолжил жить дольше, чем нам нужно. Решаем проблему с помощью @autoreleasepool:
    // ...
    [op addExecutionBlock:^{
        for (int i = 0; i < 5; ++i) {
            @autoreleasepool { // <-
                if ([weakOperation isCancelled]) break;
                __typeof(self) strongSelf = weakSelf;
                if (!strongSelf) break;
                [TTRetainer retain:strongSelf];
                [TTRetainer autorelease:strongSelf];
                NSLog(@"data[%d] = %d;", 0, [strongSelf->_data[0] integerValue]);
                sleep(1);
            }
        }
    }];
    // ...
// data[0] = 1;
// data[0] = 1;
// Stopping


Остался только один маленький нюанс. Проверка [weakOperation isCancelled] при продеаллоченной операции ошибочно вернёт NO и цикл продолжит выполнение. Однако мне не удалось воспроизвести ситуацию, что объект NSBlockOperation удалён из памяти, а его цикл всё ещё выполняется. Но поскольку такое поведение незадокументировано и не гарантируется (поправьте, если не так), то лично я всё равно создал бы strong указатель из weakOperation и проверял if (strongOperation && [strongOperation isCancelled]).

Ой. Кажется получилось довольно длинно. Извиняюсь. :)
Там у меня в середине текста накладка произошла с заменой «i, _data[i]» на «0, _data[0]», но по количеству строк в логе думаю всё равно всё ясно.
Первое задание еще можно решить с помощью NSPredicate и регулярки.

- (BOOL)isLoginValid:(NSString *)login {
    NSString *regExp = @"\\A[a-zA-Z](([a-zA-Z0-9\\.\\-]{0,18}[a-zA-Z0-9])|[a-zA-Z0-9]){0,1}\\z";
    NSPredicate *testPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regExp];
    return [testPredicate evaluateWithObject:login];
}
Еще возможный вариант для второго задания. Работает медленнее, но читаемость выше, как мне кажется.

- (NSArray *)mostFriquentWordsInString:(NSString *)inputString count:(int)count {
    NSMutableArray *words = [[inputString componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] mutableCopy];
    NSMutableArray *counts = [NSMutableArray new];
    while ([words count] > 0) {
        NSString *word = [words firstObject];
        NSArray *foundWords = [words filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF == %@", word]];
        [words removeObjectsInArray:foundWords];
        [counts addObject:@{
                            @"word" : word,
                            @"wordCount" : @([foundWords count])
                            }];
    }
    
    [counts sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"wordCount" ascending:NO]]];
    
    return [counts subarrayWithRange:NSMakeRange(0, (MIN(count, [counts count])))];
}
Теперь в Яндекс отправится куча резюме с шаблонными решениями на эту позицию.
Даже не знаю, хорошо это или плохо.
С учетом того, что субъективно, это худшее приложение от Яндекса в apple store, с точки зрения качества работы.
А может, наоборот, к вакансии будет привлечено больше внимания?
Как пользователь, искренне на это надеюсь.
А мне лично HH позвонил по этой вакансии. Думаю, что не мне одному. Так что от Яндекса не убудет.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Удивило, что слово «музыка» используется только в заголовке.
Регулярка чудная.
* Конечно же не нужно a-zA-Z если case insensivitive
* 0-9 можно заменить на \d, однако из-за обилия экранов это иногда только ухудшает читаемость. Кстати, не знаком с обжСи, нельзя ли их поменьше сделать? И почему там вообще делимитеры совпадают с экранами? Делимитеры лучше выбирать такими, чтобы не только не путались с экранами, но и с тем, что будем мэтчить.
* Если в группе должен быть дефис, то можно написать его в конце, не экранируя [a-z-]
* Лимит на 20 символов проще проверить вне регулярки, чем городить конструкции вида {0,18}, которые зачастую весьма ненадежны в граничных случаях
* не указаны начало-конец строки, может сматчить середину логина, удовлетворяющую условиям

Вот мой вариант:
#^[a-z](?:[a-z\d\.-]*[a-z\d]$|$)#siu
Длину проверяем отдельно.
тьфу, доллар за скобку еще можно вынести ^[a-z](?:[a-z\d\.-]*[a-z\d])?$
в методе
-(NSArray*)mostFrequentWordsInString:(NSString*)string count:(NSUInteger)count
полнейшее пренебрежение к утечкам памяти.

во первых:
NSMutableCharacterSet *separators = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
mutableCopy увеличивает счетчик ссылок на 1

во вторых:
return [selectedWords copy];
раз уж на то пошло, то нужно возвращать
return [[selectedWords copy] autorelease];
ARC же везде используется, это очевидно.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации