2

I just got this from a company for a interview test and I completed it with ease but they said that my functions were in o(n). Heres the questions

Write a class IntegerTracker with these methods:

track(int) - Receives an integer for tracking. 
get_max() - Returns the max (int) of all integers seen so far. 
get_min() - Returns the min (int) of all integers seen so far. 
get_mean() - Returns the mean (float) of all integers seen so far.
get_mode() - Returns the mode (int) of all integers seen so far.

Ensure each method, including track, runs in constant time (O(1) time complexity).

This is how I completed it

- (instancetype)init{
    if(self == [super init]){
        self.numbers = [[NSMutableArray alloc]init];
    }
    return self;
}

- (void)trackInt:(int)number{
    [self.numbers addObject:[NSNumber numberWithInt:number]];
}

- (int)getMax{

    NSNumber *max = [self.numbers valueForKeyPath:@"@max.self"];
    return [max intValue];
}

- (int)getMin{

    NSNumber *min = [self.numbers valueForKeyPath:@"@min.self"];
    return [min intValue];
}

- (float)getMean{
    NSNumber *average = [self.numbers valueForKeyPath:@"@avg.self"];
    return [average floatValue];
}
- (int)getMode{

    int maxCount = 0;
    int value = 0;
    NSMutableDictionary *mode = [[NSMutableDictionary alloc]init];
    for(NSNumber *n in self.numbers){
        int currentCount = [[mode objectForKey:n.stringValue]intValue];
        currentCount++;
        [mode setObject:@(currentCount) forKey:n.stringValue];
        if(maxCount < currentCount){
            maxCount = currentCount;
            value = [n intValue];
        }
    }

    return value;
}

Can someone show me how I am supposed to complete this in O(1). I already got passed up cause of this so don't think your giving me an answer for the interview. I'm not going to work there. I just want to see how i'm supposed to figure this out without iterating through the array.

luk2302
  • 55,258
  • 23
  • 97
  • 137
Esko918
  • 1,397
  • 1
  • 12
  • 25
  • 4
    you probably have to re-calculate the max (and the others) whenever `track` is called, it the new number is bigger than the current max, then change max, otherwise do not change max, etc. – luk2302 Jan 05 '18 at 15:55
  • No you don't understand what they are asking. They want me to complete all the functions in O(1). IN constant time. I don't know how to do it that way. All the functions need to be completed in O(1). I did them in O(N). SO how can you complete them in O(1) – Esko918 Jan 05 '18 at 15:59
  • Not related but I don't know why you use `stringValue` while the `n` (which is a `NSNumber`) should suffice for the `key` of the dictionary. – Larme Jan 05 '18 at 15:59
  • I had to complete this in under an hour and it was saying I couldn't use the nsnumber there for the key. I know I used an NSNUjmber for the key before but for some reason it gave me an error in OBJective C – Esko918 Jan 05 '18 at 16:01
  • Ahh I know why. Cause I was using setValueForKey instead of using setObjectForKEy and when I converted the code over I just replaced what I already had – Esko918 Jan 05 '18 at 16:03
  • 7
    This is a company hiring for Objective-C handing out a spec with method names like `get_max()`? Looks like you may have dodged a nightmare there… – Rob Napier Jan 05 '18 at 16:16

2 Answers2

6

I would assume you have to write trackInt: in a different way:

- (void)trackInt:(int)number{
    if (number > self.max) {
        self.max = number;
    }
    // different logic for the other desired outcomes
}

That way whenever a new number is added you a simple calculation for determining the max (and the other values) in constant time.

The actual max method then looks like:

- (int) getMax { return self.max; }

The logic for incremental calculation for mode, avg, etc. simply looks a bit different, you will probably never have to use the numbers array though, but more likely have a count and a sum to keep track of.

For mode you can keep a dictionary that maps number onto a counter that keeps track of how often that number has occurred yet. Additionally you store the current count of number that has occurred the most times, maxNumberCount. If the newly incremented counter is bigger than that stored counter you have a new mode value, the current number to store / return and change maxNumberCount accordingly.

luk2302
  • 55,258
  • 23
  • 97
  • 137
  • Well I don't think thats possible cause how would I then complete all the other methods if I didnt save all the numbers in the array – Esko918 Jan 05 '18 at 16:04
  • 2
    @Esko918 where are you struggling? For `min` you do basically the very same, for `avg` you store them sum and the count, for `mode` you *could* store a dictionary that has the `number` as key and the number of times that number occured as value. – luk2302 Jan 05 '18 at 16:06
  • Im wondering how to complete this in o(1). That was the parameters of the question – Esko918 Jan 05 '18 at 16:08
  • @Esko918 I added a paragraph for that situation. – luk2302 Jan 05 '18 at 16:11
  • I did keep a dictionary. Look how I did the mode in my answer. Since I have to iterate over the dictionary its incorrect. Thatsnot O(1) thats O(N). OH wait I see now, If I just put the dictionary as a property every time I included a new number check if that number was in the dictionary if so increment the count if not then add number – Esko918 Jan 05 '18 at 16:13
  • 1
    @Esko918 I will not argue further, the paragraph I wrote is O(1), there is no iteration happening, just regular access by key which is in constant time. – luk2302 Jan 05 '18 at 16:14
  • 2
    For the avg you just keep a running average and a count. The first track sends in a number. So the avg is that number and the count is one. For any others you do ((avg * count) + newInt)/(count + 1) that is O(1) – Fogmeister Jan 05 '18 at 16:16
  • @Fogmeister that is what I was referring to with `count` and `sum`, is it not? Or do you mean something else? – luk2302 Jan 05 '18 at 16:17
  • @luk2302 sorry, missed that bit. Yeah, same thing :-) – Fogmeister Jan 05 '18 at 16:18
  • 2
    @Fogmeister I personally dislike the idea of (avg * count) because that risks losing precision - depending on the requirement (if sum > intMaxValue) that might be required though. – luk2302 Jan 05 '18 at 16:20
  • 1
    @chux i am not sure here but generally dictionary access O(1) iirc, unless you run into worst case where it will be O(n). Anyway, there is no requirement stating that it cannot have a table with 4 billion entries (or however many you wish). I do not think there is a way that will ALWAYS be O(1), even in worst case. Note that the requirement does not even state if the time complexity is for best, worst or average case. – luk2302 Jan 05 '18 at 16:28
  • Luk is not wrong but I gave the answer to suthan since he actually wrote the code – Esko918 Jan 05 '18 at 19:27
  • That is the risk I take by trying go get you to think and code instead of simply giving you the solution. – luk2302 Jan 05 '18 at 20:02
4

To make the functions work in O(1) means that there cannot be any iteration inside them. There is actually no reason to actually store the numbers. You need only to store the statistics:

@property (nonatomic) NSInteger min;
@property (nonatomic) NSInteger max;
@property (nonatomic) NSInteger sum;
@property (nonatomic) NSInteger count;

@property (nonatomic) NSCountedSet *numberCounts; // must be initialized in `init`
@property (nonatomic) NSInteger mostFrequentNumber;

- (void)track:(NSInteger)number {
   if (self.count == 0) { 
      self.min = number;
      self.max = number;
      self.sum = number;
      self.count = 1;
      [self.numberCounts addObject:@(number)];
      self.mostFrequentNumber = number;
   } else {
      self.min = MIN(number, self.min);
      self.max = MAX(number, self.max);
      self.sum += number;
      self.count += 1;
      [self.numberCounts addObject:@(number)];
      if ([self.numberCounts countForObject:@(number)] > [self.numberCounts countForObject:@(self.mostFrequentNumber)] {
         self.mostFrequentNumber = number;
      }
   }
}

- (float)getMean {
   if (self.count == 0) { // protection against dividing by zero!
     return 0;
   }

   return ((float) self.sum) / self.count;
}

- (NSInteger)getMode {
   return self.mostFrequentNumber;
}

Added a demonstration of mode calculation using NSCountedSet. NSCountedSet can be simulated using a dictionary/map in other languages (we have to use a structure with O(1) operations in an average case). The only trick is to perform the necessary operations when adding.

Also note that currently the functions don't follow Obj-C naming conventions and that should be important in an interview, too.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • Then how would you get the mode – Esko918 Jan 05 '18 at 16:09
  • But I was able to do this in any language. SO what about a language that doesn't have NSCountedSet. I chose Objective-C because that was the position I was applying for – Esko918 Jan 05 '18 at 16:12
  • @Esko918 I was wrong about `NSCountedSet`. It wouldn't be helpful, I guess. – Sulthan Jan 05 '18 at 16:12
  • Also whats the count for? Say if I give you the numbers 1,4,3,1,1,5,4 how do you figure it out? – Esko918 Jan 05 '18 at 16:13
  • @Esko918 The `count` tracks how many numbers did you track so far. – Sulthan Jan 05 '18 at 16:14
  • "To make the functions work in O(1) means that there cannot be any iteration inside them." is certainly true. Yet does O(1) also require that any functions called like `addObject:@(number)` also perform in O(1)? – chux - Reinstate Monica Jan 05 '18 at 16:27
  • @chux Yes. Therefore you have to use a structure that does operations in `O(1)` for an average case, e.g. a structure that is using hashing. – Sulthan Jan 05 '18 at 16:28
  • [Fair enough](https://stackoverflow.com/questions/48116997/how-to-complete-this-in-o1/48117233?noredirect=1#comment83211422_48117233). That "use a structure that does operations in O(1)" is an important point of this good answer. – chux - Reinstate Monica Jan 05 '18 at 16:30
  • This would be the correct way to do it. Instead of having the functions compute the mean/max/min/mode like I did with loops. You should have properties in the class that will all be updated when the user inputs a number into the track method. Once this happens you can just return those properties when the function is called. This conforms to the O(1) part of the question – Esko918 Jan 05 '18 at 19:26