📜 ⬆️ ⬇️

Tasks for the developer Yandex.Music for iOS

When completing the application form for the Yandex.Musicle developer position for iOS, they are asked to complete test tasks. Tasks are laid out in open form, there is no request not to disclose tasks and not to publish a decision.

Let's get started

Question 1. The authorization system has the following restriction on the login format: it must begin with a Latin letter, it may consist of Latin letters, numbers, periods and minuses and must end with a Latin letter or numbers. The minimum login length is 1 character. Maximum - 20 characters.

Write code that checks if the input string matches this rule.
')
We use a regular expression. The NSRegularExpression class appeared in 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); } 

Here is the finished project using this code. You can test.
I think this question is aimed at weeding out those who are afraid of regular expressions.

Question 2. Write a method that returns the N most common words in the input line.

Much more interesting.

 -(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   -     } //     :            . 

I would use this approach if I needed to solve this problem in a real iOS application, provided that I understand where the input data will come from for searching and assume that the size of the input line will not be more than a few megabytes. It is a reasonable assumption for iOS applications, in my opinion. Otherwise, there would not be a line at the entrance, but a file. With really large input data, you will have to sweat over a regular expression to iterate over words to get rid of one intermediate array. Such a regular expression is very dependent on the language - what works for the Russian does not prokanaet for Chinese. But what to do with the words further - except for a straightforward algorithm, nothing comes to mind. If you had to choose one most common word - this is Fast Majority Voting. But the beauty of this algorithm is that it works to select a single value. Modifications of the algorithm for selecting N values ​​are not known to me. I did not manage to modify it myself.

Question 3. Using NSURLConnection, write a method for asynchronously loading a text document over HTTP. Give an example of its use.

 -(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] ); } }]; } 


It's simple. Probably, like the first question, this question I know / do not know.

Question 4. List all the problems you see in the code below. Suggest how to fix them.
 NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 1000; ++i) { if ([operation isCancelled]) return; process(data[i]); } }]; [queue addOperation:operation]; 


Personally, I see a problem in that the variable operation “captured” by the block when creating the block has not yet been initialized to the end. What real value of this variable will be at the moment of “capture” depends on whether this code is used in a class method or in a simple function. It is possible that the generated code will be fully functional and so, but this code is not clear to me. How to get out of the situation? So:

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

Perhaps it would be enough just to add the __block modifier to the declaration of the operation variable. But as in the code above - for sure. Some even create a __weak copy of the operation variable and use it inside the block. Having thought well, I decided that in this particular case, when the lifetime of the operation and block variable is known, this is unnecessary. Well, I would think whether to use NSBlockOperation for such complex structures. Reasonable arguments can not bring - a matter of personal preference.

What else is wrong with this code? I do not like different magic constants in the code and instead of 1000 I would use define, const, sizeof or something like that.

In long cycles in objective-c, you need to remember about autoreleased variables and, if such variables are used in a function or method process, and this method or function does not care about it, you need to wrap this call in @autoreleasepool {}. Creating a new pool at each iteration of the cycle may be overhead or redundant. If ARC is not used, you can create a new NSAutoreleasePool every, say, 10 loop iterations. If ARC is used, this is not possible. By the way, this is probably the only reason not to use ARC.

It is not clear by the code whether the data is changing during processing, if someone else is accessing this data during processing from other streams, what data structures are used, whether the process itself cares about the exclusive access to data when it is needed. You may need to take care of locks.

Question 5. There are tables:

 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) ) 

Write a query that returns the names of tracks downloaded more than 1000 times.

Here such a request copes with the task remarkably:
 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) 

Tested in sqlite. I suppose that it is possible to reduce one select, but I do not know how.
The presence of this question in the task is quite explicable - approximately this application will deal with.

Waiting for critics.

Source: https://habr.com/ru/post/209076/


All Articles