supermarin / ObjectiveSugar

ObjectiveC additions for humans. Ruby style.
MIT License
2.17k stars 190 forks source link

added reduce methods to NSArray and NSSet #75

Closed robinsenior closed 10 years ago

robinsenior commented 10 years ago

Return a single value from an array or set by iterating through the elements and transforming a running total. Similar to Ruby's reduce method, also known as fold or inject.

shepting commented 10 years ago

Any way to distribute this across multiple cores? It was my understanding that parallelism was one of the reasons why the map/reduce pattern was so valuable.

supermarin commented 10 years ago

@shepting there is, where -reduce would be a void method returning stuff in a block. Otherwise, it might be an interesting problem to solve if the method stays synchronous but internals are async

supermarin commented 10 years ago

@robinsenior thank you for the contribution! Does it make sense to add a counterpart with no initial value as well?

-reduce:^{block} and reduce:0 withBlock:^{block}

robinsenior commented 10 years ago

@shepting only if the operations are associative, I believe. I think that operations like foldl can be parallelized because they assume that order of operations does not matter. Operations like division are order dependant and cannot be executed out of order. (see http://stackoverflow.com/questions/329423/parallelizing-the-reduce-in-mapreduce)

@supermarin we could do that, where if you do not explicitly specify an initial value, then the first element of the array/set is used as the initial value.

supermarin commented 10 years ago

@robinsenior right, that make sense. let's merge this and i'll add the counterpart without explicit accumulator.

Thanks once again! :beer:

ghost commented 10 years ago

@robinsenior & @supermarin Counterpart without initial can be failed

  1. Initial as nil will be failed if block executes accumulator methods (Won't be executed because of nil)
  2. Initial as first element can be failed if types of element and accumulator are different
supermarin commented 10 years ago

@zrcoder could you elaborate a bit more by providing an example?

If the initial value is nil, then the first element is just copied. For example, the implementation might look like:


for(id object in self)
    accumulator = accumulator ? block(accumulator, object) : object;
robinsenior commented 10 years ago

@zrcoder @supermarin should we also add checking for nil blocks?

I notice this library doesn't do much nil checking or range checking, should it be added throughout?

ghost commented 10 years ago
block = ^NSNumber*(NSNumber* accumulator, id object){
    //Assume `add` is categorized and element has value as property
    return [accumulator add object.value]; 
}

Thus, after the first round, accumulator as the first element will be incorrect

supermarin commented 10 years ago

@zrcoder sorry - i still don't understand the problem. if you could reproduce it with a whole gist that'd be appreciated.

Since methods in objc are dynamic, the fact that one is categorized should't make any problems as long as the object responds to it.

ghost commented 10 years ago

@supermarin

@interface Item
@property(nonatomic, strong)NSNumber* value;
+ (instancetype)itemWithValue:(NSNumber*)value;
//Other properties
@end

@interface NSNumber(NumberOnly)
- (NSNumber*)methodAvailableOnNumberButItem:(NSNumber*)number;
@end

NSArray* items = @[[Item itemWithValue:@1],[Item itemWithValue:@2]];

NSNumber* total = [items reduce:^NSNumber*(NSNumber* accumulator, id object){
                        return [accumulator methodAvailableOnNumberButItem: [object value]]; 
                  }

In the block, accumulator is alway expected as a NSNumber , however it will be Item if the initial is nil, therefore it (accumulator) won't respond method methodAvailableOnNumberButItem

supermarin commented 10 years ago

@zrcoder oh, I see what you're saying.

That shouldn't be a problem, since it's the expected behavior from some other languages as well. In that case you want to use the counterpart with initial value provided.

[items reduce:@0 withBlock:^(NSNumber *accumulator, Item *item){
    return [accumulator methodAvailableOnNumberButItem: item.value];
}];
ghost commented 10 years ago

@supermarin My point is the type of accumulator is not homogeneous in all steps of the loop if initial is not provided.