Challenge: removing assets

Here is my solution:

BNREmployee.h

[code]…

  • (void)removeAsset:(unsigned int)i;

    [/code]

BNREmployee.m

[code]…

  • (void)removeAsset:(unsigned int)i
    {
    if (i < _assets.count)
    {
    [_assets removeObjectAtIndex:i];
    }
    else {
    NSLog(@“Index out of range or employee does not own any assets”);
    }
    }…[/code]

Example lines for main.m

[code]…
NSLog(@“Giving up ownership of one asset”);

    [employees[2] removeAsset:0];

…[/code]

Perhaps I don’t understand the purpose of this challenge or failed to see some obvious point, but I thought about this quite differently. Aren’t we removing an asset from one of the employees? After the assets are assigned there is no array of assets. They all belong to one of the employees. My ‘removeAsset’ method, therefore, takes a pointer to the asset that will be removed. Here is my method in BNREmployee.m:

- (void)removeAsset:(BNRAsset *)a { [_assets removeObject:a]; }

The tricky part seems to be finding the asset. Which employee in employees has that particular asset so we can remove it? Here is my code in main.m:

[code] NSLog(@“Employees: %@”, employees); // List all the assets for all the employees

    // Remove asset 6, for example
    NSString *assetLabel = @"Laptop 6";
    for (BNREmployee *anEmployee in employees) {
        for (BNRAsset *anAsset in anEmployee.assets) {
            if ([anAsset.label isEqualToString:assetLabel]) {
                [anEmployee removeAsset:anAsset];
                break;
            }
        }
    }
    
    NSLog(@"Employees: %@", employees);     // Check to make sure we've removed the asset from one of them

[/code]

I’m hesitant to remove something when enumerating through the array. I read that you should make a copy and enumerate the copy but remove from the original. But the example I found used a Mutable Set rather than an array and the method [myCopy allObjects] didn’t compile for the BNREmployees array. I used ‘break’ instead and it seemed to work, but I’m not completely happy with it.

I just got to this challenge and couldn’t decide between the two methods listed above…does anyone know which one is considered correct (or if both are correct, which is preferable)?

Here’s my two pennies worth…

RE: Solution no 1 above (chrisgraf)
If you know the index of the employee and asset then you don’t need any special methods you can just use removeObjectAtIndex.

Re: Solution no 2 above (BabaGnush)
I think the point of this challenge was for readers to see how the memory is being deallocated when removing objects, of course I could be completely wrong but here’s my take on the challenge…

[code]BNREmployee.m

  • (void)removeAsset:(BNRAsset *)r
    {
    // Is asset nil
    if (_assets){
    [_assets removeObject:r];
    }
    }[/code]

[code]main.m

    NSLog(@"Removing all assets");
    
    for (BNREmployee *e in employees){
        for (BNRAsset *a in e.assets){
            [e removeAsset:a];
        }
    }

[/code]

I’ve also modified the description of employees to see what assets have been added to each employee. This will help you see that the loop iterates over all assets in each employee and removes them.

[code]BNREmployee.m

  • (NSString *)description
    {
    return [NSString stringWithFormat:@"<Employee %d: $%d in assets, %@>", self.employeeID, self.valueOfAssets, self.assets];
    }
    [/code]

Anyway, whatever the solution is at least we all wrote some Objective-C code and we can have a debate about it. Good luck to other beginners!

See below code used in my main.m. Updated BNREmployee.h & .m to have removeAsset method. In main approach I took was to randomly select an employees, check if he had any assets, if he did then randomly remove one. I included a lot of NSLog statements so I could check what was happening was indeed correct. See below code snippet from main.m that does the above. Grateful anyones feedback or thoughts. Please note srandomdev() used to get true random number generation, see jscorbin post on this in this forum http://forums.bignerdranch.com/viewtopic.php?f=457&t=7941

[code] // Randomly select an employee and then check if they have any assets and randomly remove one of those assets. Note srandomdev() included earlier to get true random number

    // First check to see if employee has asset
    
    bool hasAsset;
    
    // Uses a for loop with no counter, loop will run until it enconters an employee with an asset and will then break out of the loop once has selected and removed an asset.
    
    for (int i= 0; i <1;) {
        // randomly chooses employee to check
        NSUInteger randomIndexRemove = random() % [employees count];
        NSLog(@"The random Employee selected is Employee %lu", (unsigned long)randomIndexRemove);
        // finds that employee in the employee array
        BNREmployee *employeeWithAssets = [employees objectAtIndex:randomIndexRemove];
        
        // set hasAssets to true, if the employee has assets
        hasAsset = employeeWithAssets.assets;
        
        // If statement check to see if employee has an asset, if they do have assets, will randomly select one of those assets to remove
        if (hasAsset) {
            NSLog(@"Employee:%lu has an asset to be removed", randomIndexRemove);
            
            // Outputs a list of all the assets that the employee has
            
            NSLog(@"Employee:%lu has the following Assets: %@",randomIndexRemove, employeeWithAssets.assets);
            
            NSUInteger selectRandomAssetHeldByEmployee = random() % [employeeWithAssets.assets count];
            
            // Adds 1 to the array count so as not to give 0 based index in following NSLog output
            NSUInteger assetNumber = selectRandomAssetHeldByEmployee +1;
            NSLog(@"The asset seleted to be removed is number %lu in the list", assetNumber);
            
            // Creates a BNRAsset instance of the asset to be removed, so that it can be interrogated to provide its label for the following NSLog statement
            
            BNRAsset *assetToRemove =[employeeWithAssets.assets objectAtIndex:selectRandomAssetHeldByEmployee];
            NSLog(@"The asset to be removed is %@", assetToRemove.label);
            
            //The asset is removed
            [employeeWithAssets removeAsset:selectRandomAssetHeldByEmployee];
            // For loop is broken out of avoiding an infinite loop.
            break;
        // If employee selected does not hav any assets displays message in NSLog statement below and the for loop reruns
        } else {
            
            NSLog(@"Employee: %lu has no asset to be removed, chosing next employee to be checked", randomIndexRemove);
        }
    
            }[/code]

So this is a tough one. You guys have posted some reasonable solutions. This:

for (BNREmployee *e in employees) {
            for (BNRAsset *a in e.assets)
                [e removeAsset:a];
}

works great for removing all assets, but what id really like to do is directly remove ONE asset in a way that is a bit simpler than what Jimbo259 has posted. I just can’t seem to figure out a good way to do it, but surely its possible. Something like:

[[employees objectAtIndex:3] removeAsset:HowDoIAddressThisAsset?];

Do I really have to run a for each loop on the employee to find out which assets he has, in order to access one of them or is there an easier way?

TIA!

:slight_smile:

To remove a specific asset, first you must zoom in on it by searching.

You could do something like this:

BNREmployee  * employee = [employees objectAtIndex:3];
BNRAsset * victim = [employee findAssetByName:@"FooBar"];
if (victim) {
    [employee removeAsset:victim];
}
...
@implementation BNREmployee
...
BNRAsset * findAssetByName:(NSString *)name {
   for (BNRAsset * asset in self.assets) {
          if ([asset.name isEqualToString:name]) {
               return asset;
          }
   }
   return nil;
}
...
@end

Very nice example! Thank you!
Im glad to know that it required implementing an extra method. I was afraid maybe i was just missing something terribly visible. This make a lot of sense though!

First off, I agree that this is so far easily the most confusing chapter and challenge in the book. I think part of this is due to how complicate these concepts and their implementations are in Objective C and how weird some of the syntax is (Obj-C is my first language, but surely others must be more internally consistent), but I think it’s also because the Authors just throw huge amounts of concepts and code at the reader with a fairly small amount of explanation to go with it.

That being said…

My solution is very similar to that by ibex10. The program often gives more than one asset to an employee, so presumably we’re trying to remove a specific one, not everything the employee has.

I did this by iterating through all the employees/assets and looking at each asset’s NSString label, to see if it matches the one we’re trying to delete.

Here’s my function in employee.m (obviously with a matching bit in employee.h)

- (void)removeAssetWithLabel:(NSString *)a { BOOL wasFound = NO; int assetCounter = 0; //employees have their assets in an array so we have to start as 0, then 1, etc. for (BNRAsset *testAsset in _assets) { NSLog(@"testing %@ vs %@", testAsset.label, a); //This is unnecessary, but is a much quicker way to see what's going on than putting a breakpoint and iterating step by step if ([a isEqualToString:testAsset.label]) { //testing the string that the function was called with VS the label of the object currently being checked [_assets removeObjectAtIndex:assetCounter]; wasFound = YES; NSLog(@"The asset <%@> was deleted", a); } assetCounter++; } if( wasFound == NO ){ NSLog(@"This employee doesn't %@. Moving on.", a); } }

Then in main.m I added this:

NSLog(@"Trying to delete laptop 7..."); for (BNREmployee *e in employees) { [e removeAssetWithLabel:@"Laptop 7"]; }

The next bit doesn’t do anything except send out some NSLogs, but it helped me to understand how the program works.

In employee.m…

- (void)listAssets
{
    for(BNRAsset *testAsset in _assets) {
        NSLog(@"Employee %i has %@", self.employeeID, testAsset.label);
    }
}

in main.m…

        for (BNREmployee *e in employees) {
            [e listAssets];
        }

I read the phrase how weird as how beautiful :slight_smile:

Its really interesting to see how different people decided to choose the asset to delete.
I decided to choose a random item to delete.

Also, looking at others examples, it looks like I could have built out the removeAsset method more, and removed some of the code from the main.m file.
Also, I used breakpoints to step through the code and inspect each array member individually and figure out what was going on when it was broken at first. This seems stupid when seeing that other people just did an NSLog and log what is being checked against what, but then on the plus side, I learnt a metric tonne about using the debugging tools and reading their output within Xcode. So even though I struggled with this, I still feel like it was a great learning exercise. And thats the point, right?

One other thing is, I know I used a count of entries in the employees array, to work out a random asset to remove. This is a bodge job. It just happens to work because there are 10 employees, and 10 laptops. If there were 20 laptops, this wouldn’t work properly so I’d need to change this. I might go back and fix this, as I’m assuming I’d need to pull out all the assets into their own array, and perform a count on that array to pick a random number between 0 and the correct number of laptops that are out there.

Anyway, here’s what I came up with… Similar to others, but slightly different :slight_smile:

BNREmployee.m

-(void)removeAsset:(BNRAsset *)r
{
    if (_assets) {
        [_assets removeObject:r];
    }
}

main.m

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

@autoreleasepool {

    // Create an array of BNREmployee objects
    NSMutableArray *employees = [[NSMutableArray alloc]init];

    for (int i = 0; i < 10; i++) {
        // Create an instance of BNREmployee
        BNREmployee *mikey = [[BNREmployee alloc]init];

        // Give the instance variables interesting values
        mikey.weightInKilos = 90 + 1;
        mikey.heightInMeters = 1.8 - i/10.0;
        mikey.employeeID = i;

        // Put the epmloyee in the employees array
        [employees addObject:mikey];
    }

        // Create 10 assets
        for (int i = 0; i < 10; i++) {

            // Create an asset
            BNRAsset *asset = [[BNRAsset alloc]init];

            // Give it an interesting label
            NSString *currentLabel = [NSString stringWithFormat:@"Laptop %d", i];
            asset.label = currentLabel;
            asset.resaleValue = 350 + i * 17;

            // Get a random number between 0 and 9 inclusve
            NSUInteger randomIndex = random() % [employees count];

            // Find that employee
            BNREmployee *randomEmployee = [employees objectAtIndex:randomIndex];

            // Assign the asset to the employee
            [randomEmployee addAsset:asset];
   }

    NSLog(@"Employees: %@", employees);

    // Remove an asset
    NSUInteger randomIndex = random() % [employees count];
    NSString *assetLabel = [NSString stringWithFormat:@"Laptop %ld", randomIndex];

    for (BNREmployee *employeeName in employees) {
        for (BNRAsset *assetName in employeeName.assets) {
            if ([assetLabel isEqualToString:assetName.label]) {
                [employeeName removeAsset:assetName];
            }
        }
    }

    NSLog(@"Employees: %@", employees);

    NSLog(@"Giving up ownership of one employee");

    [employees removeObjectAtIndex:5];

    NSLog(@"Giving up ownership of arrays");

    employees = nil;

}
return 0;

}
[/code]

Fixed!
Made some changes to BNREmployee.m and main.m

BNREmployee.m

.................
-(void)removeAsset:(BNRAsset *)r
{
    if (_assets) {
        [_assets removeObject:r];
        NSLog(@"Asset %@ removed from database.", r);
    }
}
.................

main.m

.................................
        NSLog(@"Employees: %@", employees);

        // Figure out the total number of assets
        NSMutableArray *countOfAssets = [[NSMutableArray alloc]init];

        for (BNREmployee *employeeName in employees) {
            for (BNRAsset *assetName in employeeName.assets) {
                [countOfAssets addObject:assetName];
            }
        }

        // Remove an asset
        NSUInteger randomIndex = random() % [countOfAssets count];
        NSString *assetLabel = [NSString stringWithFormat:@"Laptop %ld", randomIndex];

        for (BNREmployee *employeeName in employees) {
            for (BNRAsset *assetName in employeeName.assets) {
                if ([assetLabel isEqualToString:assetName.label]) {
                    [employeeName removeAsset:assetName];
                }
            }
        }

        NSLog(@"Employees: %@", employees);
..........................

Interesting behavior I noticed when I wrote the removal of the first asset assigned to the 2nd (Index=1) employee.

The logs at the end showing the release of all the assets for all employees showed the 2nd employee details and release last.

Is this coincidence? or does the array object get moved to the end since it was the last modified?

The thing you gotta remember is that none of these challenges have any real life practical application - if you think of them from a practical standpoint you will only grow frustrated. They are designed to teach the various concepts/syntax/methods. I spent close to 3 weeks on Chapter 21/22 alone and refused to move past it until I understood the concepts (e.g. the mutable versus non mutable properties/variables in the same declaration took some time to wrap my head around). The concepts become clearer if you stick with it. The BNRAssets/ BNREmployee challenges also illustrate some interesting ways in which you can introduce randomness. For beginners like myself this is great to see in action. Hard chapter, but worth the time spent.

Check out my approach and see if it works for you (there is no right/wrong way to go about this, the point is to learn and understand the why’s/how’s of what you are doing)

viewtopic.php?f=457&t=9471