Gold Challenge: Nested Containers


#1

In the text for the Gold Challenge, at the end of the paragraph, It states “A properly-written BNRContainer class can contain instances of BNRContainer…” Am I correct in taking this to mean that the BNRContainer can contain either BNRItems, or BNRContainers? I wrote my solution with that in mind.

BNRContainer.h

[code]#import “BNRItem.h”

@interface BNRContainer : BNRItem

// Designated Initializer

  • (instancetype)initWithContainerName:(NSString *)name;

  • (void)addBNRObject:(id)newItem isContainer:(BOOL)type;

@property NSMutableArray *BNRItems;
@property NSString *containerName;
@property float containerValue;

@end
[/code]
BNRContainer.m

@implementation BNRContainer

- (instancetype)initWithContainerName:(NSString *)name
{
    // Call the superclass' designated initializer
    self = [super init];
    
    // Did the superclass' designated initializer succeed?
    if (self) {
        // Initialize the mutable array that will hold BNR Items
        _BNRItems = [[NSMutableArray alloc] init];
        _containerName = name;
    }
    
    return self;
}

- (instancetype)init
{
    return [self initWithContainerName:@"Undefined"];
}

// Using the id type instead of a BNRItem or BNRContainer pointer because we don't know
// whether the item being added to the array will be another container, or a single BNRItem
- (void)addBNRObject:(id)newItem isContainer:(BOOL)isContainer
{
    [_BNRItems addObject:newItem];
    
    if (isContainer) {
        
        BNRContainer *containerToAdd = newItem;     //temp pointer to get to the class/instance methods
        
        // Add the value of the new container to the value of its holder
        _containerValue += containerToAdd.containerValue;
        
        containerToAdd = nil;
    }
    else {
        BNRItem *itemToAdd = newItem;               //temp pointer to get to the class/instance methods
        _containerValue += itemToAdd.valueInDollars;
        
        itemToAdd = nil;
    }
}

- (NSString *)description
{
    NSString *descriptionString =
    [[NSString alloc] initWithFormat:@"%@: Worth $%f",
     self.containerName,
     self.containerValue];

    for (id items in [self BNRItems]) {
        NSLog(@"%@", [items description]);
    }

    return descriptionString;
}

@end

main.m

[code]int main(int argc, const char * argv[])
{

@autoreleasepool {
    
    // Create a mutable array, object, store its address in items variable
    NSMutableArray *items = [[NSMutableArray alloc] init];
    
    for (int i = 0; i < 10; i++) {
        BNRItem *item = [BNRItem randomItem];
        [items addObject:item];
    }
    
    
    BNRContainer *container = [[BNRContainer alloc] initWithContainerName:@"John's Container"];     // using designated initializer
    BNRContainer *anotherContainer = [[BNRContainer alloc] init];       // Testing init method with default name
    
    [container addBNRObject:items[0] isContainer:NO];
    [container addBNRObject:items[4] isContainer:NO];
    
    [anotherContainer addBNRObject:container isContainer:YES];
    [anotherContainer addBNRObject:items[7] isContainer:NO];
    
    NSLog(@"%@", container);
    NSLog(@"\n");
    NSLog(@"This container has a nested container, and another single BNRItem");
    NSLog(@"%@", anotherContainer);
    
    // Destroy the mutable array object
    items = nil;
    container = nil;
    anotherContainer = nil;
}
return 0;

}
[/code]

Not sure if this solution is way off base. Any input is appreciated!


#2

Hi,

Since you already declared properties in your header you should also access them in your designated initialiser with dot syntax.

For example, this message send would be
[self.BNRItems addObject:newItem];

instead of
[_BNRItems addObject:newItem];

There are two other cases of this in this method, for containerValue in the If-Else statement.

But you probably already know that …

Thanks! :wink:


#3

Thanks for the tip! I thought about that, and there were times when I tried it, and XCode yelled at me, which I thought was curious. I went back and checked the text because I thought that the authors suggested accessing the instance variables directly, instead of using dot notation, but I see now that they only advocated that in the initializers.


#4

Hey,

I rewrote

  • (void)addBNRObject:(id)newItem isContainer:(BOOL)type;

with dot syntax and with doxygen-style documentation support instead of your comments. Just for practice, not a criticism or anything.

I also placed the addObject: message outside of the If-Else statement.

[code]/*! Adds a @b newItem to the @b BNRItems array.

  1. The @b newItem in the first argument is cast as an @b id instead of a @b BNRItem or @b BNRContainer because we don’t know whether the item being added to the array will be a @b BNRContainer or a @b BNRItem.

  2. If @b isContainer returns YES, we create a local variable from BNRContainer, @b containerToAdd, and assign it to @b newItem.

  3. We then add @b containerToAdd 's @b containerValue to self’s @b containerValue by adding the expression on the right side of the += operator to the expression on the left side of the operator and store the result back into the variable on the left side of the operator.

  4. The @b containerToAdd object is then set to nil.

  5. Otherwise, we create a local variable from @b BNRItem — @b itemToAdd — and assign it to @b newItem.

  6. We repeat steps 3 & 4 for this local variable.

  7. @b newItem is added to the @b BNRItems array which is a property of @b BNRContainer and which has been created in @b BNRContainer’s designated initializer.
    */

  • (void)addBNRObject:(id)newItem isContainer:(BOOL)isContainer
    {
    if (isContainer) {
    BNRContainer *containerToAdd = newItem;

      self.containerValue += containerToAdd.containerValue;
      
      containerToAdd = nil;
    

    } else {

      BNRItem *itemToAdd = newItem;
    
      self.containerValue += itemToAdd.valueInDollars;
      
      itemToAdd = nil;
    

    }
    [self.BNRItems addObject:newItem];
    }[/code]

Hope it’s helpful. I really like doxygen documentation support in Xcode 5, even though it takes longer: I really prefer to just see the code and have its documentation be separate. :slight_smile:


#5

This challenge became a lot easier when I realized the BNRContainer is an item itself. The only thing that sets itself apart from other items is the fact that it can hold things inside of it. With that in mind, you don’t need to add any additional initializers (unless you want to be able to initialize with an array of items). Since a container is an item, it has a name, a serial number, and it’s own value so all the original items should work already and you don’t need this extra “containerName” property.

BNRContainer.h only needs a read-only subitems property, and the ability to add items to the container

[code]#import “BNRItem.h”

@interface BNRContainer : BNRItem
{
NSMutableArray *_subitems;
}

#pragma mark - Property getters and setters

  • (NSArray *)subitems;
  • (void)addItem:(BNRItem *)item;

@end[/code]

BNRContainer.m overrides valueInDollars so it returns the total value of the item and all it’s subitems, and description now appends a listing of all the inner items

[code]#import “BNRContainer.h”

@implementation BNRContainer

#pragma mark - Property getters and setters

  • (NSArray *)subitems {

    if (!_subitems) {
    _subitems = [[NSMutableArray alloc] init];
    }
    return [_subitems copy];
    }

  • (void)addItem:(BNRItem *)item {

    if (!_subitems) {
    _subitems = [[NSMutableArray alloc] init];
    }
    [_subitems addObject:item];
    }

#pragma mark - Method overrides

  • (int)valueInDollars {
    int value = _valueInDollars;

    for (BNRItem *item in self.subitems) {
    value += item.valueInDollars;
    }
    return value;
    }

  • (NSString *)description {

    return [[super description] stringByAppendingString:
    [NSString stringWithFormat:@", Contains: %@", self.subitems]];
    }

@end[/code]

Updated main.m so it puts items inside two containers, and then stuffs one container into another container

[code]#import <Foundation/Foundation.h>
#import “BNRContainer.h”

int main(int argc, const char * argv[]) {

@autoreleasepool {
    
    // Create some containers to hold items
    BNRContainer *container    = [BNRContainer randomItem];
    BNRContainer *subContainer = [BNRContainer randomItem];
    
    // Add several random items to the first container
    for (int i = 0; i < 10; i++) {
        [container addItem:[BNRItem randomItem]];
    }
    
    // Add several random items to the sub-container
    for (int i = 0; i < 3; i++) {
        [subContainer addItem:[BNRItem randomItem]];
    }
    
    // Add the sub-container to the original first container
    [container addItem:subContainer];
    
    NSLog(@"%@", container);
}
return 0;

}[/code]


#6

My solution was almost identical to this. I did, however, override the designated initializer to create the array to hold items. I figured if an object is instantiated as a BNRContainer object, it should have an array when instantiated. This avoided having to check in multiple places for the existence of the array and then having to alloc it at that point.

[code]- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber
{
self = [super initWithItemName:name valueInDollars:value serialNumber:sNumber];

if (self) {
    _items = [[NSMutableArray alloc] init];
}

return self;

}[/code]


#7

bunglenutter, why do you need to know what kind of class it is? If you override the accessor for valueInDollars, then whenever you traverse the array and ask for the object’s value, you get the right thing. If the array object is a BNRitem, it returns the right thing. If it’s a BNRContainer, it returns the total value.

I saw other solutions that consciously tried to keep track of what type of object it is (“Is Container” attribute, etc). This seems to be counter to the inheritance/subclass paradigm. The objective is to create specialized objects with extended behavior. You should let it do it’s thing (let the underlying frameworks keep track of the object types and send the appropriate messages to the appropriate object/class types).

Am I missing something as to why the application must keep track of the object type? (This is a sincere question, not a rhetorical judgement. Always looking to gain from others’ insight)


#8

[quote=“cgravs”]This challenge became a lot easier when I realized the BNRContainer is an item itself. The only thing that sets itself apart from other items is the fact that it can hold things inside of it. With that in mind, you don’t need to add any additional initializers (unless you want to be able to initialize with an array of items). Since a container is an item, it has a name, a serial number, and it’s own value so all the original items should work already and you don’t need this extra “containerName” property.

BNRContainer.h only needs a read-only subitems property, and the ability to add items to the container

[code]#import “BNRItem.h”

@interface BNRContainer : BNRItem
{
NSMutableArray *_subitems;
}

#pragma mark - Property getters and setters

  • (NSArray *)subitems;
  • (void)addItem:(BNRItem *)item;

@end[/code]

BNRContainer.m overrides valueInDollars so it returns the total value of the item and all it’s subitems, and description now appends a listing of all the inner items

[code]#import “BNRContainer.h”

@implementation BNRContainer

#pragma mark - Property getters and setters

  • (NSArray *)subitems {

    if (!_subitems) {
    _subitems = [[NSMutableArray alloc] init];
    }
    return [_subitems copy];
    }

  • (void)addItem:(BNRItem *)item {

    if (!_subitems) {
    _subitems = [[NSMutableArray alloc] init];
    }
    [_subitems addObject:item];
    }

#pragma mark - Method overrides

  • (int)valueInDollars {
    int value = _valueInDollars;

    for (BNRItem *item in self.subitems) {
    value += item.valueInDollars;
    }
    return value;
    }

  • (NSString *)description {

    return [[super description] stringByAppendingString:
    [NSString stringWithFormat:@", Contains: %@", self.subitems]];
    }

@end[/code]

Updated main.m so it puts items inside two containers, and then stuffs one container into another container

[code]#import <Foundation/Foundation.h>
#import “BNRContainer.h”

int main(int argc, const char * argv[]) {

@autoreleasepool {
    
    // Create some containers to hold items
    BNRContainer *container    = [BNRContainer randomItem];
    BNRContainer *subContainer = [BNRContainer randomItem];
    
    // Add several random items to the first container
    for (int i = 0; i < 10; i++) {
        [container addItem:[BNRItem randomItem]];
    }
    
    // Add several random items to the sub-container
    for (int i = 0; i < 3; i++) {
        [subContainer addItem:[BNRItem randomItem]];
    }
    
    // Add the sub-container to the original first container
    [container addItem:subContainer];
    
    NSLog(@"%@", container);
}
return 0;

}[/code][/quote]

Excellent, I didn’t do it this way, I tried the other way people seemed to be doing it, but IMO this is the way the challenge should be completed. I’m going to fix mine up.


#9

Yep, likewise, I had competed the challenge the other way people were doing it, but I much prefer this way.
One thing to note from cgravs way of doing this, is that the description of the container doesn’t return the correct information.

[quote]“Printing the description of a BNRContainer object should show you the name of the container, its value in dollars (a sum of all items in the container plus the value of the container itself), and a list of every instance of BNRItem it contains. A properly-written BNRContainer class can contain instances of BNRContainer. It can also report back its full value and every contained item properly.”
Excerpt From: Joe Conway. “iOS Programming.” iBooks. itunes.apple.com/gb/book/ios-pr … 2947?mt=11[/quote]

When using the super description stringByAppendingString method, it just lists the item with its value. It doesnt return the name of the container, or the total value, so you’d need something along the lines of:

- (NSString *)description
{
    NSString *descriptionString = [[NSString alloc]initWithFormat:@"Container: %@, Total value: $%d, contains items and containers: %@",
                                   self.containerName,
                                   self.valueInDollars,
                                   self.subitems];
    return descriptionString;
}

This is what I have, and I believe this works correctly. It’s printing the total value of Main Container, which includes the values of all the items within it, including the sub container, and the items contained within that.
However when NSLog’ing the description in main.m, I get the output of the sub-container, all on one line with the new line \n’s being printed as though they were normal characters. Any ideas on this? I saw another thread where the guy was having the same issue, but no one responded on there…
Here’s the output I get:

2014-09-16 11:39:24.053 RandomItems[54809:303] Container: Main Container, Total value: $763, contains items and containers: ( "Fluffy Bear (2G4E6): Worth $46, recorded on 2014-09-16 10:39:24 +0000", "Rusty Bear (5E8A5): Worth $60, recorded on 2014-09-16 10:39:24 +0000", "Shiny Mac (8U4A0): Worth $96, recorded on 2014-09-16 10:39:24 +0000", "Fluffy Bear (6P3A6): Worth $19, recorded on 2014-09-16 10:39:24 +0000", "Shiny Spork (2O5Q1): Worth $51, recorded on 2014-09-16 10:39:24 +0000", "Container: Container1, Total value: $491, contains items and containers: (\n \"Fluffy Mac (1U8L1): Worth $77, recorded on 2014-09-16 10:39:24 +0000\",\n \"Rusty Bear (4K9I3): Worth $29, recorded on 2014-09-16 10:39:24 +0000\",\n \"Shiny Bear (5B4D3): Worth $54, recorded on 2014-09-16 10:39:24 +0000\",\n \"Fluffy Spork (1H8W3): Worth $15, recorded on 2014-09-16 10:39:24 +0000\",\n \"Shiny Mac (1Q6L5): Worth $10, recorded on 2014-09-16 10:39:24 +0000\",\n \"Rusty Bear (2P6O7): Worth $12, recorded on 2014-09-16 10:39:24 +0000\",\n \"Rusty Mac (9L2R1): Worth $45, recorded on 2014-09-16 10:39:24 +0000\",\n \"Rusty Bear (1E7K7): Worth $78, recorded on 2014-09-16 10:39:24 +0000\",\n \"Rusty Bear (4H5B8): Worth $41, recorded on 2014-09-16 10:39:24 +0000\",\n \"Rusty Mac (7B8Z7): Worth $85, recorded on 2014-09-16 10:39:24 +0000\"\n)" ) Program ended with exit code: 0

In case you are interested, heres all my code:
BNRContainer.m:

[code]#import “BNRContainer.h”

@interface BNRContainer ()
{
NSMutableArray *_listOfItems;
}

@end

@implementation BNRContainer

  • (void)setListOfItems:(NSArray *)itemList;
    {
    _listOfItems = [itemList mutableCopy];
    }

  • (NSArray *)listOfItems
    {
    return [_listOfItems copy];
    }

  • (void)addItem:(BNRItem *)addItem
    {
    // Is listOfBNRItems nil?
    if (!_listOfItems) {
    // Create the array
    _listOfItems = [[NSMutableArray alloc]init];
    }
    [_listOfItems addObject:addItem];
    }

  • (int)valueInDollars {
    int value = _valueInDollars;

    for (BNRItem *item in self.listOfItems) {
    value += item.valueInDollars;
    }
    return value;
    }

  • (NSString *)description
    {
    NSString *descriptionString = [[NSString alloc]initWithFormat:@“Container: %@, Total value: $%d, contains items and containers: %@”,
    self.containerName,
    self.valueInDollars,
    self.listOfItems];
    return descriptionString;
    }

@end
[/code]

main.m

[code]#import <Foundation/Foundation.h>
#import “BNRItem.h”
#import “BNRContainer.h”

int main(int argc, const char * argv[])
{

@autoreleasepool {
    
    NSMutableArray *items = [[NSMutableArray alloc]init];

    BNRContainer *mainContainer = [[BNRContainer alloc]init];
    mainContainer.containerName = [NSString stringWithFormat:@"Main Container"];
    mainContainer.valueInDollars = 0;

    BNRContainer *containerWithItems = [[BNRContainer alloc]init];
    containerWithItems.containerName = [NSString stringWithFormat:@"Container1"];
    containerWithItems.valueInDollars = 45;
    
    // Create Container1, and add items
    for (int i = 0; i < 10; i++) {
        BNRItem *item = [BNRItem randomItem];
        [items addObject:item];
        [containerWithItems addItem:item];
    }

    // Create Main Container, and add items
    for (int i = 0; i < 5; i++) {
        BNRItem *item = [BNRItem randomItem];
        [items addObject:item];
        [mainContainer addItem:item];
    }

    // Add Container1, to main container
    [mainContainer addItem:containerWithItems];
    
    NSLog(@"%@", mainContainer.description);
    
    items = nil;
    
}
return 0;

}[/code]


#10

Hi,

For printing an array in an array, I had the same problem, but after an hour of research and another hour or two of trial and error I solved it with the following code… It’s probably not perfect but it works really well !!

Open to improvements of course (I did not use “isKindOfClass” in the end, and the string approach instead with the Name as it allowed me to better test certain aspects while writing the coder itself !

to note: I fully override the super class (i.e. BNRItem) way of printing the actual description string if printing the contents of a BNRContainer object.

Code to go into BNRContainer.m
(you may have to adjust your variable names…)


- (NSString *)description
{
    
    NSMutableString *descriptionString =
    [[NSMutableString alloc] initWithFormat:@"\n\nCONTAINER %@ (%@): Total Worth USD %d, recorded on %@, \nActual empty Container value USD %d, with %lu items",
     self.itemName,
     self.serialNumber,
     self.valueInDollars,
     self.dateCreated,
     self.containerEmptyValueInDollars,
     [self.container count]];
    
    int iCount = 0;
    
    for (id subObj in self.container)
    {
        
        NSString *className = [subObj className];
      
        if ([className isEqualToString:@"BNRItem"])
        {
            iCount++;
            BNRItem *holdItem = subObj;
            [descriptionString appendFormat:@"\nItem no: %d = %@", iCount, holdItem];
        }
        else
        {
            iCount++;
            BNRContainer *holdContainer = subObj;
            [descriptionString appendFormat:@"\n\n SUB CONTAINER no: %d = %@\n END OF SUB-CONTAINER\n", iCount, holdContainer];
        }

    } // the iteration loop ends here
    
    [descriptionString appendFormat:@"\nEND\n\n"];
    
    return descriptionString;
}

AND SAMPLE OUTPUT… ( note: I have two levels of nesting in one case !)


2014-11-10 11:46:11.640 RandomItemsChallenges[3776:1908629] Test Gold2 = 

CONTAINER Mike's PURE GOLD container (4K6L8): Total Worth USD 1254, recorded on 2014-11-10 10:46:11 +0000, 
Actual empty Container value USD 101, with 12 items
Item no: 1 = Fluffy Spork (8H4E6): Worth $54, recorded on 2014-11-10 10:46:11 +0000
Item no: 2 = Shiny Mac (7T7I5): Worth $91, recorded on 2014-11-10 10:46:11 +0000
Item no: 3 = Rusty Bear (0Q6Q8): Worth $70, recorded on 2014-11-10 10:46:11 +0000
Item no: 4 = Rusty Spork (5R3N6): Worth $69, recorded on 2014-11-10 10:46:11 +0000
Item no: 5 = Fluffy Bear (9W3G2): Worth $43, recorded on 2014-11-10 10:46:11 +0000
Item no: 6 = Fluffy Mac (3A2K1): Worth $34, recorded on 2014-11-10 10:46:11 +0000
Item no: 7 = Fluffy Spork (9V2W4): Worth $30, recorded on 2014-11-10 10:46:11 +0000
Item no: 8 = Rusty Spork (1D9P8): Worth $39, recorded on 2014-11-10 10:46:11 +0000
Item no: 9 = Rusty Spork (7J7H0): Worth $23, recorded on 2014-11-10 10:46:11 +0000
Item no: 10 = Fluffy Bear (5P7Q9): Worth $64, recorded on 2014-11-10 10:46:11 +0000

 SUB CONTAINER no: 11 = 

CONTAINER Warped Container (0M0J0): Total Worth USD 561, recorded on 2014-11-10 10:46:11 +0000, 
Actual empty Container value USD 12, with 4 items
Item no: 1 = Shiny Spork (2X1N2): Worth $33, recorded on 2014-11-10 10:46:11 +0000
Item no: 2 = Fluffy Spork (1Z5I2): Worth $41, recorded on 2014-11-10 10:46:11 +0000
Item no: 3 = Rusty Spork (4A6U2): Worth $34, recorded on 2014-11-10 10:46:11 +0000

 SUB CONTAINER no: 4 = 

CONTAINER Warped Container (0B0A0): Total Worth USD 441, recorded on 2014-11-10 10:46:11 +0000, 
Actual empty Container value USD 202, with 5 items
Item no: 1 = Shiny Bear (8D0L2): Worth $14, recorded on 2014-11-10 10:46:11 +0000
Item no: 2 = Rusty Mac (6K0B3): Worth $98, recorded on 2014-11-10 10:46:11 +0000
Item no: 3 = Shiny Bear (4R1R8): Worth $37, recorded on 2014-11-10 10:46:11 +0000
Item no: 4 = Fluffy Spork (4F7P4): Worth $17, recorded on 2014-11-10 10:46:11 +0000
Item no: 5 = Rusty Mac (4I1F6): Worth $73, recorded on 2014-11-10 10:46:11 +0000
END


 END OF SUB-CONTAINER

END


 END OF SUB-CONTAINER

Item no: 12 = Cheese Fondue (1M2I3): Worth $75, recorded on 2014-11-10 10:46:11 +0000
END

best,