Задания для разработчика Яндекс.Музыки для iOS

При заполнении анкеты на должность разработчика Яндекс.Музыки для iOS просят выполнить тестовые задания. Задания выложены в открытом виде, никакой просьбы не разглашать задания и не публиковать решения нет.

Приступим.

Вопрос 1. В системе авторизации есть следующее ограничение на формат логина: он должен начинаться с латинской буквы, может состоять из латинских букв, цифр, точки и минуса и должен заканчиваться латинской буквой или цифрой. Минимальная длина логина — 1 символ. Максимальная — 20 символов.

Напишите код, проверяющий, соответствует ли входная строка этому правилу.

Используем регулярное выражение. Класс NSRegularExpression появился в iOS 4.0.
BOOL loginTester(NSString* login) {
    NSError *error = NULL;
    NSRegularExpression *regex = [NSRegularExpression 
        regularExpressionWithPattern:@"\\A[a-zA-Z](([a-zA-Z0-9\\.\\-]{0,18}[a-zA-Z0-9])|[a-zA-Z0-9]){0,1}\\z"
        options:NSRegularExpressionCaseInsensitive error:&error];
    // Здесь надо бы проверить ошибки, но если регулярное выражение оттестированное и 
    // не из пользовательского ввода - можно пренебречь.
    NSRange rangeOfFirstMatch = [regex rangeOfFirstMatchInString:login options:0 range:NSMakeRange(0, [login length])];
    return (BOOL)(rangeOfFirstMatch.location!=NSNotFound);
}

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

Вопрос 2. Напишите метод, возвращающий N наиболее часто встречающихся слов во входной строке.

Гораздо интересней.

-(NSArray*)mostFrequentWordsInString:(NSString*)string count:(NSUInteger)count {
    // получаем массив слов.
    // такой подход для человеческих языков будет работать хорошо.
    // для языков, вроде китайского, или когда язык заранее не известен, 
    // лучше использовать enumerateSubstringsInRange с опцией NSStringEnumerationByWords
    NSMutableCharacterSet *separators = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
    [separators formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
    NSArray *words = [string componentsSeparatedByCharactersInSet:separators];
    
    NSCountedSet *set = [NSCountedSet setWithArray:words];
    
    // тут бы пригодился enumerateByCount, но его нет.
    // будем строить вручную
    NSMutableArray *selectedWords = [NSMutableArray arrayWithCapacity:count];
    NSMutableArray *countsOfSelectedWords = [NSMutableArray arrayWithCapacity:count];
    
    for (NSString *word in set) {
        NSUInteger wordCount = [set countForObject:word];
        NSNumber *countOfFirstSelectedWord = [countsOfSelectedWords count] ? 
            [countsOfSelectedWords objectAtIndex:0] : nil; // в iOS 7 появился firstObject
        if ([selectedWords count] < count || wordCount >= [countOfFirstSelectedWord unsignedLongValue]) {
            NSNumber *wordCountNSNumber = [NSNumber numberWithUnsignedLong:wordCount];
            NSRange range = NSMakeRange(0, [countsOfSelectedWords count]);
            NSUInteger indexToInsert = [countsOfSelectedWords indexOfObject:wordCountNSNumber inSortedRange:range 
                options:NSBinarySearchingInsertionIndex 
                usingComparator:^(NSNumber *n1, NSNumber *n2) 
            {
                NSUInteger _n1 = [n1 unsignedLongValue];
                NSUInteger _n2 = [n2 unsignedLongValue];
                if (_n1 == _n2)
                    return NSOrderedSame;
                else if (_n1 < _n2)
                    return NSOrderedAscending;
                else
                    return NSOrderedDescending;
            }];
            [selectedWords insertObject:word atIndex:indexToInsert];
            [countsOfSelectedWords insertObject:wordCountNSNumber atIndex:indexToInsert];
            // если слов уже есть больше чем нужно, удаляем то что с наименьшим повторением
            if ([selectedWords count] > count) {
                [selectedWords removeObjectAtIndex:0];
                [countsOfSelectedWords removeObjectAtIndex:0];
            }
        }
    }
    return [selectedWords copy]; 
    // можно было бы сразу вернуть selectedWords, 
    // но возвращать вместо immutable класса его mutable сабклас нехорошо - может привести к багам
}
// очень интересный метод для юнитестов: правильный результат может быть разным и зависит от порядка слов в строке.

Я бы именно такой подход и использовал, если бы мне нужно было решить эту задачу в реальном iOS приложении, при условии, что я понимаю, откуда будут браться входные данные для поиска и предполагаю, что размеры входной строки не будут больше нескольких мегабайт. Вполне разумное допущение для iOS приложения, на мой взгляд. Иначе на входе не было бы строки, а был бы файл. При реально больших входных данных прийдется попотеть над регулярным выражением для перебора слов, чтоб избавиться от одного промежуточного массива. Такое регулярное выражение очень зависит от языка — то что сработает для русского не проканает для китайского. А вот что делать со словами дальше — кроме прямолинейного алгоритма в голову ничего не приходит. Если бы нужно было выбрать одно наиболее часто встречающееся слово — это Fast Majority Voting. Но вся красота этого алгоритма в том, что он работает для выбора одного значения. Модификаций алгоритма для выбора N значений мне не известны. Самому модифицировать не получилось.

Вопрос 3. Используя NSURLConnection, напишите метод для асинхронной загрузки текстового документа по HTTP. Приведите пример его использования.

-(void)pullTextFromURLString:(NSString*)urlString completion:(void(^)(NSString*text))callBack {
    NSURLRequest *request = [NSURLRequest requestWithURL: [NSURL URLWithString:urlString] 
        cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
    [NSURLConnection sendAsynchronousRequest:request 
        queue:[NSOperationQueue mainQueue] 
        completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) 
    {
        if (error) {
            NSLog(@"Error %@", error.localizedDescription);
        } else {
            // вообще, не мешало бы определить кодировку, чтоб не было неприятностей
            callBack( [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding] );
        }
    }];
}


Тут все просто. Наверное, как и первый вопрос, этот вопрос на знаю / не знаю.

Вопрос 4. Перечислите все проблемы, которые вы видите в приведенном ниже коде. Предложите, как их исправить.
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 1000; ++i) {
        if ([operation isCancelled]) return;
        process(data[i]);
    }
}];
[queue addOperation:operation];


Лично я вижу проблему в том, что переменная operation, «захваченная» блоком при создании блока, еще не проинициализирована до конца. Какое реально значение этой переменной будет в момент «захвата», зависит от того, используется ли этот код в методе класса или в простой функции. Вполне возможно, что сгенерированный код будет вполне работоспособен и так, но мне этот код не ясен. Как выйти из ситуации? Так:

NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
    for (int i = 0; i < 1000; ++i) {
        if ([operation isCancelled]) return;
        process(data[i]);
    }
}];
[queue addOperation:operation];

Возможно, достаточно было бы просто добавить модификатор __block в объявление переменной operation. Но так, как в коде выше — наверняка. Некоторые даже создают __weak копию переменной operation и внутри блока используют ее. Хорошо подумав я решил, что в данном конкретном случае, когда известно время жизни переменной operation и блока — это излишне. Ну и я бы подумал, стоит ли использовать NSBlockOperation для таких сложных конструкций. Разумных доводов привести не могу — вопрос личных предпочтений.

Что еще с этим кодом не так? Я не люблю разных магических констант в коде и вместо 1000 использовал бы define, const, sizeof или что-то в этом роде.

В длинных циклах в objective-c нужно помнить об autoreleased переменных и, если такие переменные используются в функции или методе process, а сам этот метод или функция об этом не заботится, нужно завернуть этот вызов в @autoreleasepool {}. Создание нового пула при каждой итерации цикла может оказаться накладным или излишним. Если не используется ARC, можно создавать новый NSAutoreleasePool каждые, допустим, 10 итераций цикла. Если используется ARC, такой возможности нет. Кстати, это, наверное, единственный довод не использовать ARC.

По коду не ясно, меняются ли данные в процессе обработки, обращается ли кто-то еще к этим данным во время обработки из других потоков, какие используются структуры данных, заботится ли сам process о монопольном доступе к данным тогда когда это нужно. Может понадобиться позаботиться о блокировках.

Вопрос 5. Есть таблицы:

CREATE TABLE tracks (
  id INT NOT NULL AUTO_INCREMENT,
  name VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
)

CREATE TABLE track_downloads (
  download_id BIGINT(20) NOT NULL AUTO_INCREMENT,
  track_id INT NOT NULL,
  download_time TIMESTAMP NOT NULL DEFAULT 0,
  ip INT NOT NULL,
  PRIMARY KEY (download_id)
)

Напишите запрос, возвращающий названия треков, скачанных более 1000 раз.

Вот такой запрос справляется с задачей замечательно:
select name from tracks where id in 
    (select track_id from 
        (select track_id, count(*) as track_download_count from track_downloads 
            group by track_id order by track_download_count desc) 
where track_download_count > 1000)

Проверено в sqlite. Предполагаю, что можно сократить на один select, но не знаю как.
Наличие этого вопроса в задании вполне объяснимо — примерно этим приложение и будет заниматься.

Жду критики.
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 80

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

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

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

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

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

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

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

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

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

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

          UPD: Изменил 4 пункт, я немного не правильно прочитал код
            0
            По поводу «заменить цикл на while с условием выхода по [operation isCancelled]». Вы не в теме. Это не условие выхода из цикла. NSOperation — это кусок какой-то работы, который нужно выполнить. Обычно его добавляют в очередь, откуда эти операции выполняются по одному или параллельно, в зависимости от очередей и приоритетов. Операция может быть отменена. Длительно выполняющаяся операция должна периодически проверять не отменили ли ее и в случае, если отменили, как можно быстрее прекратить работу. Но, возможно, я вас не правильно понял. Вообще, любой for можно заменить на while — это вопрос вкуса.
              0
              Я прохлопал тот момент, что по индексу обращаются к элементам массива, потому и отредактировал свой ответ.
            0
            Я бы во втором задании просто отсортировал бы массив 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} на "?" заменить бы.
              0
              задача 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))];
              
                0
                А если после некоторых слов будут запятые или другие символы?
                  0
                  Ну можно punctuationCharacterSet еще приделать.
                  Вообще никак не могу найти хорошую статью про токенизацию строк, казалось что на NSHipster было, но как будто бы нету. Там рассказывалось как по-уму делить на слова (и не только) в разных локалях.
                    0
                    Типа этого, но не это и как будто бы без Core Foundation.
                      0
                      Это было тут.

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

                      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))];
                      
                        0
                        не хватает еще [substring lowercaseString]; или даже lowercaseStringWithLocale:
                          0
                          и MIN вместо MAX
                      0
                      в первом задании вроде как можно объединить a-zA-Z в a-Z, внутри квадратных скобок точку и дефис можно не экранировать. Не предусмотрено, что длина может быть 1 символ
                        0
                        Предусмотрено — там {0,1} в самом конце.
                          0
                          Да, извиняюсь, как-то проглядел. Но всё равно можно оптимизировать, не только длину, но и читаемость — избавиться от «или» (|). Сейчас если соберусь с силами, то попробую переписать. Как минимум, можно {0,1} заменить на?
                            0
                            раз caseInsensitive, то можно совсем убрать эти A-Z, использовать \d вместо 0-9.
                            тестировал в консоли девелопера в хроме
                            /^[a-z]([a-z\d.-]{0,18}[a-z\d])?$/i
                            получилось так, но чувствую подвох
                            0
                            Предусмотрено, что длина может быть в 1 символ. В «статье» есть ссылка — можете загрузить проект приложения и проверить.
                            0
                            Задача 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
                            


                            Призываю знатоков, интересно увидеть хорошее решение
                              +1
                              Скорее уж так, хотя в деталях мог ошибиться
                              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
                              
                                0
                                Я обратил внимание, что надо селектнуть только имена, может это важно. Спасибо что напомнили про having.
                                А какой здесь профит от inner join?
                                  0
                                  в случае LEFT JOIN у вас могут быть записи не вошли во внутреннее соединение (NULL)
                                    0
                                    Это я понимаю, думал вдруг это влияет на производительность в лучшую сторону. Представил, что таблицы гарантированно полные и поэтому написал left.
                                      0
                                      Можно посмотреть и оценить как происходит запрос поставив перед SELECT в вашем и моем примере EXPLAIN
                                        0
                                        Explain я бы конечно воспользовался, но время позднее, лень создавать таблицу и тестовые данные. Может valeriyvan еще не удалил и попробует?
                                          0
                                          Не удалил. Удивлен, что sqlite поддерживает expain. Завтра попробую.
                                    0
                                    Я обратил внимание, что надо селектнуть только имена, может это важно.

                                    Если HAVING используется то в SELECT нужно указать то что в GROUP BY или агрегированное значение, поэтому в SELECT есть COUNT(d.track_id)
                                  +1
                                  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
                                  
                                    0
                                    Гениально! Чувствуется специалист.
                                  0
                                  В четвертом вопросе мне кажется важным, что operationQueue вызовет операцию на неглавном треде, где совсем нет autorelease pool, поэтому мы должны его сделать, чтобы process(data) всё освободило. Но queue может быть и mainQueue так-то.
                                  • UFO just landed and posted this here
                                      0
                                      В каждом потоке есть 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.
                                        0
                                        Какая-то довольно мутная формулировка если честно.
                                        Ну и приходит в голову мысль, что NSOperationQueue придерживает пул тредов, которые не terminate, а просто засыпают.
                                        Возможно, я тут что-то путаю, но от этой части мануала у меня больше вопросов, чем ответов.
                                          0
                                          Цитирую документацию: 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.
                                        • UFO just landed and posted this here
                                      • UFO just landed and posted this here
                                          0
                                          Отвечу по пунктам.
                                            0
                                            Отвечу по пунктам.

                                            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];
                                            • UFO just landed and posted this here
                                                +2
                                                2. И почему в блоке не просто [n1 compare: n2]? Наверное, потому что я — тормоз. Другого ответа у меня нет.

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

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

                                                  Лучше просто [pool drain];
                                                    0
                                                    Да, вы правы, конечно же.
                                                +1
                                                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;
                                                
                                                  +2
                                                  последнее условие хромает
                                                    0
                                                    Да, время позднее, и copy-paste подвёл. :) Но суть, я думаю, ясна.
                                                  +2
                                                  Дальше пробежал по-диагонали, но бросилось в глаза про блоки в п. 4.

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

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

                                                          Пересмотрю сессию WWDC. Перечитаю документацию. Эта тема с блоками и операциями и как оно, все таки, правильно, вполне может быть темой отдельной статьи.
                                                            +2
                                                            Что-то я не понял, как медитация и гугление привели Вас к неправильному решению. :) На том же 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]).

                                                            Ой. Кажется получилось довольно длинно. Извиняюсь. :)
                                                              0
                                                              Там у меня в середине текста накладка произошла с заменой «i, _data[i]» на «0, _data[0]», но по количеству строк в логе думаю всё равно всё ясно.
                                                          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];
                                                          }
                                                          
                                                            0
                                                            Еще возможный вариант для второго задания. Работает медленнее, но читаемость выше, как мне кажется.

                                                            - (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])))];
                                                            }
                                                            
                                                              0
                                                              Теперь в Яндекс отправится куча резюме с шаблонными решениями на эту позицию.
                                                              Даже не знаю, хорошо это или плохо.
                                                              С учетом того, что субъективно, это худшее приложение от Яндекса в apple store, с точки зрения качества работы.
                                                                +1
                                                                А может, наоборот, к вакансии будет привлечено больше внимания?
                                                                  0
                                                                  Как пользователь, искренне на это надеюсь.
                                                                  0
                                                                  А мне лично HH позвонил по этой вакансии. Думаю, что не мне одному. Так что от Яндекса не убудет.
                                                                • UFO just landed and posted this here
                                                                  0
                                                                  Удивило, что слово «музыка» используется только в заголовке.
                                                                    –2
                                                                    Регулярка чудная.
                                                                    * Конечно же не нужно a-zA-Z если case insensivitive
                                                                    * 0-9 можно заменить на \d, однако из-за обилия экранов это иногда только ухудшает читаемость. Кстати, не знаком с обжСи, нельзя ли их поменьше сделать? И почему там вообще делимитеры совпадают с экранами? Делимитеры лучше выбирать такими, чтобы не только не путались с экранами, но и с тем, что будем мэтчить.
                                                                    * Если в группе должен быть дефис, то можно написать его в конце, не экранируя [a-z-]
                                                                    * Лимит на 20 символов проще проверить вне регулярки, чем городить конструкции вида {0,18}, которые зачастую весьма ненадежны в граничных случаях
                                                                    * не указаны начало-конец строки, может сматчить середину логина, удовлетворяющую условиям

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

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

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

                                                                      Only users with full accounts can post comments. Log in, please.