Achieve NSArray merging more elegantly with enumerateObjectsUsingBlock:? - objective-c

My challenge this week has been to come to terms with blocks in objective-c. There is something about the syntax that does my head in. Getting there.
I have the following code to achieve a merge of two arrays in a specific way (see comment in code below).
NSArray *keys = #[#"name", #"age"];
NSArray *data = #[
#[#"mark", #"36 years"],
#[#"matt", #"35 years"],
#[#"zoe", #"7 years"]
];
// desired outcome is
// # { #"name" : #[#"mark", #"matt", #"zoe"],
// #"age" : #[#"36 years", #"35 years", #"7 years"]
// }
NSMutableArray *mergedData = [[NSMutableArray alloc] initWithCapacity:keys.count];
for (NSString *key in keys) {
NSLog(#"key: %#", key);
NSInteger keyIndex = [keys indexOfObject:key];
NSMutableArray *dataItemsForKey = [[NSMutableArray alloc] initWithCapacity:data.count];
for (NSArray *row in data) {
// double check the array count for row equals the expected count for keys - otherwise we have a 'match up' issue
if (row.count == keys.count) {
[dataItemsForKey addObject:[row objectAtIndex:keyIndex]];
}
}
[mergedData addObject:[NSDictionary dictionaryWithObject:dataItemsForKey forKey:key]];
}
NSLog (#"mergedData: %#", mergedData);
While this code works fine, in the interest of my challenge and learning, I was wondering if there is a more 'elegant' (aka less code, easier to read) way to do this using enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) ??
I can't quite see a way to make it work, but in the interests of self-education, wonder if those more learned in blocks and arrays may have a more elegant solution.

The first issue that I notice is that you are asking for the index of the current object while enumerating the array. This is a waste of operations, because at every loop iteration you have to look over all array elements (potentially O(N)) to find where the object is.
You could instead do this:
for(NSUInteger i=0; i<keys.count; i++)
{
NSString* key= keys[i];
<Rest of the code>
}
Or just keep track of the index manually incrementing it:
NSUInteger i=0;
for (NSString *key in keys)
{
<Your code>
i++;
}
Or like you wanted, with enumerateObjectsUsingBlock:, which is IMO the most elegant way to do it in this case. Here is an example:
NSMutableDictionary* dict=[NSMutableDictionary new];
[keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
NSMutableArray* fields=[NSMutableArray new];
for(NSArray* array in data)
{
[fields addObject: array[idx]];
}
[dict setObject: fields forKey: obj];
}];
In the case you haven't understood how it works, here is a further explanation:
This way at every execution of the block you can know which is the current object (obj) and it's index (idx). stop is just used to stop enumerating the array, but you don't need it in this case (say that you want to stop the enumeration, you set *stop=YES). In my code I just took every element at the index idx of data, and build an array which is the value that I put into the dictionary, that has obj (what you called key in your code) as key. For any further doubt feel free to ask any clarification through a comment.

The first thing to say is your code does not produce the desired output. You get an array with two dictionaries each with one key.
One way to solve the problem is like this:
NSMutableDictionary* mergedData = [[NSMutableDictionary alloc] init];
[keys enumerateObjectsUsingBlock: ^(id key, NSUInteger keyIndex, BOOL *stop)
{
NSMutableArray* keyValues = [[NSMutableArray alloc] init];
for (NSArray* row in data)
{
[keyValues addObject: [row objectAtIndex: keyIndex]];
}
[mergedData setObject: keyValues forKey: key];
}];
The above will throw an exception if a row doesn't have enough objects in it. You could either check it beforehand or allow the program to crash, it's up to you.

Related

Repeating arbitrary nested for loops

I've been trying for a few hours to work out an algorithm that can achieve the following conditions:
Take an arbitrary number, n arrays (each populated with strings), where each array will be a figurative 'parent' to the subsequent array
For each object (a string) in an array, combine that string with subsequent arrays' strings
Add the combination to an (just one) array
Repeat for all linear combinations of each object
I think this is best explained with an example:
e.g. For three arrays
NSArray *input =
#[[#"cat",#"dog",#"mouse"],[#"apple",#"banana"],[#"green"]]
produce an output that goes something like this:
#[
#"catapplegreen",
#"catbananagreen",
#"dogapplegreen",
#"dogbananagreen",
#"mouseapplegreen",
#"mousebananagreen"
];
I've tried nesting for loops but can't think of a way of allowing there to be an arbitrary amount of loops, as there needs to be one loop or 'level' per array in the input.
If anyone has any advice (even just pointers of what to look into to tackle this problem) I'd be most grateful.
So basically what I think you want to do it a Depth-First Traversal of your data.
Which you could do with a function such as
- (void)DepthFirstOnString:(NSString *)string children:(NSArray *)children {
if (children.count < 1) {
// You're finished
NSLog(#"%#", string);
return;
} else {
// Keep going
NSArray *next = children[0];
NSMutableArray *remaining = [children mutableCopy];
[remaining removeObject:next];
[next enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSMutableString *currentString = [string mutableCopy];
[currentString appendString:obj];
[self DepthFirstOnString:currentString children:remaining];
}];
}
}
being called by this code:
NSArray *input = #[#[#"cat",#"dog",#"mouse"], #[#"apple",#"banana"], #[#"green"]];
NSArray *first = input[0];
NSMutableArray *remaining = [input mutableCopy];
[remaining removeObject:first];
[first enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[self DepthFirstOnString:obj children:remaining];
}];
Not the neatest code but hopefully gives you an idea of where to take it.

Searching NSArray using suffixes

I have a word list stored in an NSArray, I want to find all the words in it with the ending 'ing'.
Could someone please provide me with some sample/pseudo code.
Use NSPredicate to filter NSArrays.
NSArray *array = #[#"test", #"testing", #"check", #"checking"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF ENDSWITH 'ing'"];
NSArray *filteredArray = [array filteredArrayUsingPredicate:predicate];
Let's say you have an array defined:
NSArray *wordList = // you have the contents defined properly
Then you can enumerate the array using a block
// This array will hold the results.
NSMutableArray *resultArray = [NSMutableArray new];
// Enumerate the wordlist with a block
[wordlist enumerateObjectsUsingBlock:(id obj, NSUInteger idx, BOOL *stop) {
if ([obj hasSuffix:#"ing"]) {
// Add the word to the result list
[result addObject:obj];
}
}];
// resultArray now has the words ending in "ing"
(I am using ARC in this code block)
I am giving an example using blocks because its gives you more options should you need them, and it's a more modern approach to enumerating collections. You could also do this with a concurrent enumeration and get some performance benefits as well.
Just loop through it and check the suffixes like that:
for (NSString *myString in myArray) {
if ([myString hasSuffix:#"ing"]){
// do something with myString which ends with "ing"
}
}
NSMutableArray *results = [[NSMutableArray alloc] init];
// assuming your array of words is called array:
for (int i = 0; i < [array count]; i++)
{
NSString *word = [array objectAtIndex: i];
if ([word hasSuffix: #"ing"])
[results addObject: word];
}
// do some processing
[results release]; // if you're not using ARC yet.
Typed from scratch, should work :)

NSMutableArray removeObjectAtIndex usage

I am trying to filter out an array of strings based on their length. I'm completely new to Objective C and OOP in general.
wordList=[[stringFile componentsSeparatedByCharactersInSet:[NSCharacterSetnewlineCharacterSet]] mutableCopy];
for (int x=0; x<[wordList count]; x++) {
if ([[wordList objectAtIndex:x] length] != 6) {
[wordList removeObjectAtIndex:x];
}else {
NSLog([wordList objectAtIndex:x]);
}
}
for (int x=0; x<[wordList count]; x++) {
NSLog([wordList objectAtIndex:x]);
}
The NSLog in the else statement will only output 6 letter words, but the second NSLog outputs the entire array. What am I missing here? Also any general pointers to clean up/improve the code are appreciated.
Depending on what you feel is the easiest to understand you could either filter the array with a predicate or iterate over the array and remove objects. You should chose the approach that you have easiest to understand and maintain.
Filter using a predicate
Predicates are a very concise way of filtering array or sets but depending on your background they may feel strange to use. You could filter your array like this:
NSMutableArray * wordList = // ...
[wordList filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
NSString *word = evaluatedObject;
return ([word length] == 6);
}]];
Enumerating and removing
You cannot modify the array while enumerating it but you can make a note of all the items what you want to remove and remove them all in a batch after having enumerated the entire array, like this:
NSMutableArray * wordList = // ...
NSMutableIndexSet *indicesForObjectsToRemove = [[NSMutableIndexSet alloc] init];
[wordList enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *word = obj;
if ([word length] != 6) [indicesForObjectsToRemove addIndex:idx];
}];
[wordList removeObjectsAtIndexes:indicesForObjectsToRemove];
The problem with your code is that when you remove an item at index x and move to the next index x++, the item that was at x+1 is never examined.
The best way of filtering a mutable array is using the filterUsingPredicate: method. Here is how you use it:
wordList=[[stringFile
componentsSeparatedByCharactersInSet:[NSCharacterSetnewlineCharacterSet]]
mutableCopy];
[wordList filterUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary * bindings) {
return [evaluatedObject length] == 6; // YES means "keep"
}]];

conditional nsarray count

I want a count on an array where a sub array meets a condition.
I thought I could do this, but I can't.
NSLog(#"%d",[[_sections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[[obj objectAtIndex:4] isEqualToString:#"1"];
}] count]);
enumerateObjectsUsingBlock: doesn't return anything. I'd bet that code won't even compile (and, as your comment states, auto-completion doesn't work -- and it shouldn't).
Use NSArray's indexesOfObjectsPassingTest:
and take the count of the resulting NSIndexSet.
Documented here.
bbum is right; you should use indexesOfObjectsPassingTest. It's more straightforward.
But you could use enumerateObjectsUsingBlock to count test-passers, like this:
NSArray *sections = [NSArray arrayWithObjects:#"arb", #"1", #"misc", #"1", #"extra", nil];
NSMutableArray *occurrencesOf1 = [NSMutableArray array];
[sections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([(NSString*)obj isEqualToString:#"1"])
[occurrencesOf1 addObject:obj];
}];
NSLog(#"%d", [occurrencesOf1 count]); // prints 2
It's inefficient because it requires that extra mutable array.
(So you should check bbum's answer as the accepted one -- but I'm new at block functions too, and appreciated the puzzle.)
It's faster to use a for loop (and, IMO, more readable):
NSLog(#"%lu", (unsigned long)[self countSectionsWithValue:#"1" atIndex:4]);
// ...
}
// ...
- (NSUInteger) countSectionsWithValue:(NSString *)value atIndex:(NSInteger)idx
{
NSUInteger count = 0
for (id section in _sections)
{
if ([[section objectAtIndex:idx] isEqualToString:value])
{
count++;
}
}
return count;
}
Also note that I used the proper %lu format and (unsigned long) type in the NSLog. %d isn't descriptive and doesn't act the same in all scenarios.

Creating an NSDictionary from an NSArray (Objective-c 2.0)

I have an NSArray containing n elements at indices 0, 1 ... n-1. I want to populate an NSDictionary with the contents of my array.
Specifically the dictionary should contain key-value pairs where the key is the hash of the ith element in the array and the value is the index into the array.
For example: array = [123, 101, 199] then the dictionary will contain three key-value pairs:
([123 hash], 0)
([199 hash], 2)
([101 hash], 1)
I've done this with a for loop over the array. What's a more concise way to do this? Perhaps something from NSKeyValueCoding?
More info: I'm thinking of something like this:
NSArray *keys = [myArray valueForKey:#"hash"];
NSArray *values = [myArray valueForKey:#"index"]; // #"index" needs to change
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:values
forKeys:keys];
I would probably use something like David's for loop solution myself, but just for kicks, and because I'm still trying to wrap my head completely around them, I came up with a solution using blocks:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:[array count]];
[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
[dict setObject:[NSNumber numberWithUnsignedLong:idx] forKey:hashFor(obj)]; } ];
I'm assuming the existence of a function hashFor that generates the hash. You can replace that part with a message to obj or whatever you do to generate the hash.
A for loop is pretty good, especially if you need the index. Otherwise you might use fast enumeration:
int i = 0;
for (object in array) {
… [NSNumber numberWithInt: i] … // Add to dict
i++;
}
This has nothing to do with KVC.