Gold Challenge: Solution #1


#1

Whew! This one took a while. Good exercise. My solution is, I think, more of a brute force type of solution where I simply loop through the items array several times. My solution involves creating a postArray for each thread consisting of all items with the same title. I then place all of the posts back into the items array. So at the end items contains a set of arrays whereby each of these arrays holds all of the RSSItems. The first time through I pick out the items that do not have an Re: at the front of the title section - I remove the chapter part and author if there is one. I then loop through the remaining items and either add them to a postArray that matches the current title or create another postArray. My process involved stripping off the Re: to evaluate the actual title. I also dealt with the posts that were truncated due to a long title and had a … at the end.

After this post connection processing, the listViewController examined the items array. If the postArray inside of items contained more than one post it set the accessoryView to a disclosure item. If you tap the disclosure item it loads in a second table, the ThreadViewController. It seems rather wasteful to have this since each post will take you to the same webpage. I really did it this way so I could how to implement this drill down in a splitviewcontroller. That was a good learning experience.

So here’s my code. Please, please, please let me know if you have any suggestions for improvement. I’d definitely love to hear them because I know I probably don’t have the most efficient code. Also, I apologize for all of the code checking stuff. I didn’t take all of it out.

RSSChannel.m - changes to trimItemTitles

[code]
-(void)trimItemTitles
{
//Lets first trim the titles so they are just the titles. Maybe pull out the Chapter later
NSRegularExpression titleReg = [[NSRegularExpression alloc] initWithPattern:@"(.) :: (.) :: ."
options:0
error:nil];

for (RSSItem *i in items)
{
    NSLog(@"Title: %@", [i title]);
}

for (RSSItem *i in items)
{
    NSString *itemTitle = [i title];
    NSArray *matches = [titleReg matchesInString:itemTitle options:0 range:NSMakeRange(0,[itemTitle length])];
    if ([matches count] > 0)
    {
        NSTextCheckingResult *result = [matches objectAtIndex:0]; //This is all three groups
        NSString *postTitle = [itemTitle substringWithRange:[result rangeAtIndex:2]];
        [i setTitle:postTitle];
    }
    else
    {
        //If the title is too long the third section is truncated by the server and replaced with ...
        //This fixes that and removes the ...
        //However, the title will not be truly correct, so we'll have to make up for that later
        NSRegularExpression *titleReg2 = [[NSRegularExpression alloc] initWithPattern:@"(.*) :: (.*) ... .*"
                                                                             options:0
                                                                               error:nil];
        NSArray *match2 = [titleReg2 matchesInString:itemTitle options:0 range:NSMakeRange(0, [itemTitle length])];
        if ([match2 count] > 0)
        {
            NSTextCheckingResult *result = [match2 objectAtIndex:0]; //This is all three groups
            NSString *postTitle = [itemTitle substringWithRange:[result rangeAtIndex:2]];
            [i setTitle:postTitle];
        }
        
    }

}

//Now find those posts withput an Re:
NSRegularExpression *reReg = [[NSRegularExpression alloc] initWithPattern:@"Re: "
options:0
error:nil];
//Create the parentArray to hold each postArray
NSMutableArray *parentArray = [[NSMutableArray alloc] init];
//If a post does not have Re: remove from items and add it to a postarray then to parentArray
for (int i = [items count]; i > 0; i–)
{
RSSItem *currentItem = [items objectAtIndex:i-1];
NSString *currentTitle = [currentItem title];
NSArray *matches = [reReg matchesInString:currentTitle options:0 range:NSMakeRange(0, [currentTitle length])];
if ([matches count] == 0) //Re: was not found, thus it is an original post
{
NSMutableArray *postArray = [[NSMutableArray alloc] init];
[postArray addObject:currentItem];
[parentArray insertObject:postArray atIndex:0];
[items removeObjectAtIndex:i-1];
NSLog(@“Item # is %i”, i-1);
}
}

//Let's verify the only items left have Re: at beginning
for (RSSItem *i in items)
{
    NSLog(@"Title: %@", [i title]);
}

//Determine if remaining posts are a part of postArray or should be a new array
//Will temporarily remove the Re: for the comparison
//Get the count before because the count changes
for (int i = 0; i < [items count]; i++)
{
//Create a found booolean
BOOL itemAdded = NO;
//Get the current item title without the Re:. All will have Re:
RSSItem *currentItem = [items objectAtIndex:i];
NSString *currentItemTitle = [currentItem title];
NSString *currentItemTitleForComp = [currentItemTitle stringByReplacingCharactersInRange:NSMakeRange(0, 4) withString:@""];

    for (NSMutableArray *a in parentArray)
    {
        //just for clarity get the item at loc 0
        RSSItem *parentItem = [a objectAtIndex:0];
        //Must check for Re: at beginning of title as it could have been added through this process
        //If Re: is in the title remove it for the comparison.
        NSString * parentItemTitle = [parentItem title];
        NSString *parentItemTitleBeginning = [parentItemTitle substringWithRange:NSMakeRange(0, 4)];
        NSString *parentItemTitleForComp = [[NSString alloc] init];
        if ([parentItemTitleBeginning isEqualToString:@"Re: "])
        {
            parentItemTitleForComp = [parentItemTitle substringWithRange:NSMakeRange(4, [parentItemTitle length] - 4)];
        }
        else
        {
            parentItemTitleForComp = [parentItemTitle copy];
        }
        
        if ([currentItemTitleForComp isEqualToString:parentItemTitleForComp])
        {

// NSLog(@"%@ was equal to %@", currentItemTitleForComp, parentItemTitleForComp);

            [a addObject:currentItem];

// NSLog(@"%@ added to %@", [currentItem title], [[a objectAtIndex:0] title]);
itemAdded = YES;
break;
}
}
//If the loop went all the way through parentArray without finding a match b/w item and post in parentArray
//Add a new postArray to parentArray
//Must be done within loop so other replies in the same thread can be added.
if (!itemAdded)
{
NSMutableArray *postArray = [[NSMutableArray alloc] initWithObjects:currentItem, nil];
[parentArray addObject:postArray];
// NSLog(@"%@ added to it’s own array", [currentItem title]);
}
}

int parentIndex = 0;
for (NSArray *postArray in parentArray)
{
    NSLog(@"Subarray %i", parentIndex);
    for (RSSItem *item in postArray)
    {
        NSString *itemTitle = [item title];
        NSLog(@"Title: %@", itemTitle);
    }
    parentIndex ++;
}
[items removeAllObjects];
[items addObjectsFromArray:parentArray];

}

@end[/code]

ListViewController - Changes to the TableView methods

[code]
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@“UITableViewCell”];

if (cell == nil)
{
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                  reuseIdentifier:@"UITableViewCell"];
}

NSArray *postArray = [[channel items] objectAtIndex:[indexPath row]];
if ([postArray count] == 1)
{
    [cell setAccessoryType:UITableViewCellAccessoryNone];
}
else
{
    [cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
}
[[cell textLabel] setText:[[postArray objectAtIndex:0] title]];

return cell;

}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *postArray = [[channel items] objectAtIndex:[indexPath row]];
if ([postArray count] > 1)
{
threadViewController = [[ThreadViewController alloc] initWithStyle:UITableViewStyleGrouped];
[threadViewController setThread:[[channel items] objectAtIndex:[indexPath row]]];
[threadViewController setChannel:channel];
[threadViewController setWebViewController:webViewController];

    [[self navigationController] pushViewController:threadViewController animated:YES];
}
else
{
    if (![self splitViewController])
        {
           [[self navigationController] pushViewController:webViewController animated:YES];
        }
        else
        {
            UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:webViewController];
            NSArray *vcs = [NSArray arrayWithObjects:[self navigationController], nav, nil];
    
            [[self splitViewController] setViewControllers:vcs];
            [[self splitViewController] setDelegate:webViewController];
        }
    
    NSArray *thread = [[channel items] objectAtIndex:[indexPath row]];
    RSSItem *post = [thread objectAtIndex:0];
    [webViewController listViewController:self handleObject:post];
}

}

-(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
NSArray *thread = [[channel items] objectAtIndex:[indexPath row]];

for (int i = 0; i < [thread count]; i++)
{
    [[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryNone];
    
}
[tableView reloadData];

}[/code]

ThreadViewController.h

#import <Foundation/Foundation.h>
@class WebViewController;
@class RSSChannel;

@interface ThreadViewController : UITableViewController
{

}

@property (nonatomic, strong)NSArray *thread;
@property (nonatomic, strong)WebViewController *webViewController;
@property (nonatomic, strong)RSSChannel *channel;

ThreadViewController.m

#import "ThreadViewController.h"
#import "RSSChannel.h"
#import "RSSItem.h"
#import "WebViewController.h"
#import "ChannelViewController.h"

@implementation ThreadViewController
@synthesize thread, webViewController, channel;

-(id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    
    if (self)
    {
        UIBarButtonItem *bbi = [[UIBarButtonItem alloc] initWithTitle:@"Info"
                                                                style:UIBarButtonItemStyleBordered
                                                               target:self
                                                               action:@selector(showInfo:)];
        [[self navigationItem] setRightBarButtonItem:bbi];

    }
    
    return self;
}

-(void)showInfo:(id)sender
{
    ChannelViewController *channelViewController = [[ChannelViewController alloc] initWithStyle:UITableViewStyleGrouped];
    
    if ([self splitViewController])
    {
        UINavigationController *nvc = [[UINavigationController alloc] initWithRootViewController:channelViewController];
        
        NSArray *vcs = [NSArray arrayWithObjects:[self navigationController], nvc, nil];
        
        [[self splitViewController] setViewControllers:vcs];
        [[self splitViewController] setDelegate:channelViewController];
        
        NSIndexPath *selectedRow = [[self tableView] indexPathForSelectedRow];
        
        if (selectedRow) [[self tableView] deselectRowAtIndexPath:selectedRow animated:YES];
    }
    else
    {
        [[self navigationController] pushViewController:channelViewController animated:YES];
    }
    [channelViewController listViewController:nil handleObject:[self channel]];
}


-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [thread count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
    
    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"];
        
        RSSItem *post = [thread objectAtIndex:[indexPath row]];
        [[cell textLabel] setText:[post title]];
    }
    
    return cell;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (![self splitViewController])
    {
        [[self navigationController] pushViewController:webViewController animated:YES];
    }
    else
    {
        UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:webViewController];
        NSArray *vcs = [[NSArray alloc] initWithObjects:[self navigationController], nav, nil];
        
        [[self splitViewController] setViewControllers:vcs];
        [[self splitViewController] setDelegate:webViewController];
        
        RSSItem *post = [[self thread] objectAtIndex:[indexPath row]];
        [webViewController listViewController:nil handleObject:post];
    }
    
}
@end

#2

I should have a second solution to this problem using a separate RSThread class. I’m also changing the point at which I do the filtering to the parsing inside of RSSItem. Seems to be much cleaner than having to loop through all of the items.