I have a long NSMutableArray of a piece of text separated per word.
I am looking for a few keywords key words to perform actions, but the array may contain multiple of the keywords and I want it to only respond to the last keyword in the array and ignore all the ones it finds before that. What would be an efficient way of achieving this?
Search the array backwards.
NSArray* words;
[words enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString* word = (NSString*)obj;
if ([word isEqualToString:#"testString"])
stop = YES;
}];
Try using your array's reverseObjectEnumerator:
NSInteger index = [myArray length];
for (NSString *str in [myArray reverseObjectEnumerator]) {
index -= 1;
if ([str isEqualToString:#"theStringToTestFor"])
break;
}
index should now contain the index of the last matching string in the array.
Related
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.
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.
I am trying to search for a string within an array, but I only want to search the last five objects in the array for the string.
I have been fiddling with every parameter I can find on NSRange to no avail.
I would post some example code, but I can't even get out the line I need, whether its through introspection, enumeration, or just some NSRange call that I missed.
If your array elements are strings that you searched for, you can directly check the array as follows:
if ([yourArray containsObject:yourString])
{
int index = [yourArray indexOfObject:yourString];
if (index>= yourArray.count-5)
{
// Your string matched
}
}
I like indexesOfObjectsWithOptions:passingTest: for this. Example:
NSArray *array = #[#24, #32, #126, #1, #98, #16, #67, #42, #44];
// run test block on each element of the array, starting at the end of the array
NSIndexSet *hits = [array indexesOfObjectsWithOptions:NSEnumerationReverse passingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
// if we're past the elements we're interested in
// we can set the `stop` pointer to YES to break out of
// the enumeration
if (idx < [array count] - 5) {
*stop = YES;
return NO;
}
// do our test -- if the element matches, return YES
if (40 > [obj intValue]) {
return YES;
}
return NO;
}];
// indexes of matching elements are in `hits`
NSLog(#"%#", hits);
Try this :-
//Take only last 5 objects
NSRange range = NSMakeRange([mutableArray1 count] - 5, 5);
NSMutableArray *mutableArray2 = [NSMutableArray arrayWithArray:
[mutableArray1 subarrayWithRange:range]];
//Now apply search logic on your mutableArray2
for (int i=0;i<[mutableArray2 count];i++)
{
if ([[mutableArray2 objectAtIndex:i] isEqualToString:matchString])
{
//String matched
}
}
Hope this helps you!
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"
}]];
I need to search a mutable array for the maximum value and return its position as well as value. I'd only like to iterate through the array once and I'm not sure if that's possible
an example of what I'm trying to accomplish can be demonstrated below
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i<20; i++)
[array addObject:[NSNumber numberWithInteger:(arc4random()%200)]];
NSObject *max = [array valueForKeyPath:#"#max.self"];
the max object only seems to contain the value (and not the position). this can be demonstrated through the debugger with print-object max
Any advice out there?
Using valueForKeyPath:#"#max.self" is great, but only if you want the maximum value.
To know both index and value in one iteration, I'd use enumerateWithBlock:
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i<20; i++)
[array addObject:[NSNumber numberWithInteger:(arc4random()%200)]];
__block NSUInteger maxIndex;
__block NSNumber* maxValue = [NSNumber numberWithFloat:0];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSNumber* newValue = obj;
if ([newValue isGreaterThan:maxValue]) {
maxValue = newValue;
maxIndex = idx;
}
}];
Quite more code, but you're iterating only once in the array.
If you're not worried about the possibility of there being more than one identical maximum value in the array, you can use -indexOfObject: to get the index. It will return the first occurrence of the object in the array.
NSUInteger index = [array indexOfObject:max];
I hope, following solution will help you to identify maximum value from array.
int max = [[numberArray valueForKeyPath:#"#max.intValue"] intValue];
NSLog(#"Highest number: %i",max);
Please let me know if any issue.
Here you can find correct way to deal with collections and KVC
Its nice Post by Matt
http://nshipster.com/kvc-collection-operators/