From 35d4708f364c008ead787e87bb7c8b587c1487af Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Fri, 2 Oct 2015 10:00:20 -0700 Subject: [PATCH] Add Set equivalents for valid Array methods --- NSArray+inject.m | 2 +- NSSet+all.h | 29 +++++++++++++++++++++++++++ NSSet+all.m | 43 +++++++++++++++++++++++++++++++++++++++ NSSet+any.h | 18 +++++++++++++++++ NSSet+any.m | 24 ++++++++++++++++++++++ NSSet+find.h | 17 ++++++++++++++++ NSSet+find.m | 14 +++++++++++++ NSSet+flatMap.h | 30 ++++++++++++++++++++++++++++ NSSet+flatMap.m | 19 ++++++++++++++++++ NSSet+flatten.h | 14 +++++++++++++ NSSet+flatten.m | 18 +++++++++++++++++ NSSet+groupBy.h | 15 ++++++++++++++ NSSet+groupBy.m | 19 ++++++++++++++++++ NSSet+has.h | 24 ++++++++++++++++++++++ NSSet+has.m | 11 ++++++++++ NSSet+inject.h | 21 +++++++++++++++++++ NSSet+inject.m | 18 +++++++++++++++++ NSSet+join.h | 19 ++++++++++++++++++ NSSet+join.m | 12 +++++++++++ NSSet+map.h | 29 +++++++++++++++++++++++++++ NSSet+map.m | 35 ++++++++++++++++++++++++++++++++ NSSet+max.h | 24 ++++++++++++++++++++++ NSSet+max.m | 20 +++++++++++++++++++ NSSet+min.h | 19 ++++++++++++++++++ NSSet+min.m | 20 +++++++++++++++++++ NSSet+none.h | 18 +++++++++++++++++ NSSet+none.m | 25 +++++++++++++++++++++++ NSSet+partition.h | 16 +++++++++++++++ NSSet+partition.m | 15 ++++++++++++++ NSSet+pluck.h | 24 ++++++++++++++++++++++ NSSet+pluck.m | 15 ++++++++++++++ NSSet+select.h | 47 +++++++++++++++++++++++++++++++++++++++++++ NSSet+select.m | 36 +++++++++++++++++++++++++++++++++ NSSet+union.h | 14 +++++++++++++ NSSet+union.m | 11 ++++++++++ NSSet+without.h | 17 ++++++++++++++++ NSSet+without.m | 21 +++++++++++++++++++ YOLO.h | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 38 files changed, 823 insertions(+), 1 deletion(-) create mode 100644 NSSet+all.h create mode 100644 NSSet+all.m create mode 100644 NSSet+any.h create mode 100644 NSSet+any.m create mode 100644 NSSet+find.h create mode 100644 NSSet+find.m create mode 100644 NSSet+flatMap.h create mode 100644 NSSet+flatMap.m create mode 100644 NSSet+flatten.h create mode 100644 NSSet+flatten.m create mode 100644 NSSet+groupBy.h create mode 100644 NSSet+groupBy.m create mode 100644 NSSet+has.h create mode 100644 NSSet+has.m create mode 100644 NSSet+inject.h create mode 100644 NSSet+inject.m create mode 100644 NSSet+join.h create mode 100644 NSSet+join.m create mode 100644 NSSet+map.h create mode 100644 NSSet+map.m create mode 100644 NSSet+max.h create mode 100644 NSSet+max.m create mode 100644 NSSet+min.h create mode 100644 NSSet+min.m create mode 100644 NSSet+none.h create mode 100644 NSSet+none.m create mode 100644 NSSet+partition.h create mode 100644 NSSet+partition.m create mode 100644 NSSet+pluck.h create mode 100644 NSSet+pluck.m create mode 100644 NSSet+select.h create mode 100644 NSSet+select.m create mode 100644 NSSet+union.h create mode 100644 NSSet+union.m create mode 100644 NSSet+without.h create mode 100644 NSSet+without.m diff --git a/NSArray+inject.m b/NSArray+inject.m index 816af5c..df4475b 100644 --- a/NSArray+inject.m +++ b/NSArray+inject.m @@ -4,7 +4,7 @@ @implementation NSArray (YOLOInject) - (id(^)(id, id (^)(id, id)))inject { return ^(id initial_memo, id (^block)(id, id)) { - BOOL wasNonMutable = [initial_memo classForCoder] == [NSArray class] || [initial_memo classForCoder] == [NSDictionary class]; + BOOL wasNonMutable = [initial_memo classForCoder] == [NSArray class] || [initial_memo classForCoder] == [NSDictionary class] || [initial_memo classForCoder] == [NSSet class]; if (wasNonMutable) initial_memo = [initial_memo mutableCopy]; diff --git a/NSSet+all.h b/NSSet+all.h new file mode 100644 index 0000000..b95a6f1 --- /dev/null +++ b/NSSet+all.h @@ -0,0 +1,29 @@ +#import + + +@interface NSSet (YOLOAll) + +/** + Invokes the given block for each element in the receiver. Should the + block return `NO`, the method immediately returns `NO`, ceasing + enumeration. If all executions of the block return `YES`, `all` + returns `YES`. + + BOOL rv = @[@1, @2, @3].all(^(id o){ + return [o intValue] > 0; + }); + // rv => YES + + BOOL rv = @[@1, @2, @3].all(^(int d){ + return d < 3; + }); + // rv => NO + + Instead of a block, you can pass a `Class` object. + + BOOL rv = @[@1, @2, @3].all(NSNumber.class); + // rv => YES +*/ +- (BOOL(^)(id blockOrClass))all; + +@end diff --git a/NSSet+all.m b/NSSet+all.m new file mode 100644 index 0000000..7b58922 --- /dev/null +++ b/NSSet+all.m @@ -0,0 +1,43 @@ +#import "YOLO.ph" + + +@implementation NSSet (YOLOAll) + +- (BOOL(^)(id o))all { + return ^(id arg){ + BOOL (^block)(id o) = nil; + + if (arg == NSString.class /*or segfaults!*/ || YOLOIsClass(arg)) { + Class cls = arg; + block = ^(id o){ + return [o isKindOfClass:cls]; + }; + } else { + switch ([YOLOMS(arg) getArgumentTypeAtIndex:1][0]) { + case 'c': { block = ^(id o){ return ((BOOL(^)(char))arg)([o charValue]); }; break; } + case 'i': { block = ^(id o){ return ((BOOL(^)(int))arg)([o intValue]); }; break; } + case 's': { block = ^(id o){ return ((BOOL(^)(short))arg)([o shortValue]); }; break; } + case 'l': { block = ^(id o){ return ((BOOL(^)(long))arg)([o longValue]); }; break; } + case 'q': { block = ^(id o){ return ((BOOL(^)(long long))arg)([o longLongValue]); }; break; } + case 'C': { block = ^(id o){ return ((BOOL(^)(unsigned char))arg)([o unsignedCharValue]); }; break; } + case 'I': { block = ^(id o){ return ((BOOL(^)(unsigned))arg)([o unsignedIntValue]); }; break; } + case 'S': { block = ^(id o){ return ((BOOL(^)(unsigned short))arg)([o unsignedShortValue]); }; break; } + case 'L': { block = ^(id o){ return ((BOOL(^)(unsigned long))arg)([o unsignedLongValue]); }; break; } + case 'Q': { block = ^(id o){ return ((BOOL(^)(unsigned long long))arg)([o unsignedLongLongValue]); }; break; } + case 'f': { block = ^(id o){ return ((BOOL(^)(float))arg)([o floatValue]); }; break; } + case 'd': { block = ^(id o){ return ((BOOL(^)(double))arg)([o doubleValue]); }; break; } + case 'B': { block = ^(id o){ return ((BOOL(^)(BOOL))arg)([o boolValue]); }; break; } + default: + block = arg; + break; + } + } + + for (id o in self) + if (!block(o)) + return NO; + return YES; + }; +} + +@end diff --git a/NSSet+any.h b/NSSet+any.h new file mode 100644 index 0000000..b453a8b --- /dev/null +++ b/NSSet+any.h @@ -0,0 +1,18 @@ +#import + +@interface NSSet (YOLOAny) + +/** + BOOL rv = @[@1, @2, @3].any(^(id o){ + return [o intValue] == 3; + }); + // rv => YES + + Instead of a block, you can pass a `Class` object. + + BOOL rv = @[@1, @2, @3].any(NSNumber.class); + // rv => YES +*/ +- (BOOL(^)(id blockOrClass))any; + +@end diff --git a/NSSet+any.m b/NSSet+any.m new file mode 100644 index 0000000..ac80c4b --- /dev/null +++ b/NSSet+any.m @@ -0,0 +1,24 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOAny) + +- (BOOL(^)(id))any { + return ^(id arg){ + BOOL (^block)(id o) = nil; + + if (arg == NSString.class /*or segfaults!*/ || YOLOIsClass(arg)) { + Class cls = arg; + block = ^(id o){ + return [o isKindOfClass:cls]; + }; + } else + block = arg; + + for (id o in self) + if (block(o)) + return YES; + return NO; + }; +} + +@end diff --git a/NSSet+find.h b/NSSet+find.h new file mode 100644 index 0000000..66c1ccc --- /dev/null +++ b/NSSet+find.h @@ -0,0 +1,17 @@ +#import + +@interface NSSet (YOLOFind) + +/** + Passes each entry in the array to the given block, returning the first + element for which block is not `NO`. If no object matches, returns + `nil`. + + id rv = @[@1, @2, @3, @4].find(^(id n){ + return [n isEqual:@3]; + }); + // rv => @3 +*/ +- (id(^)(id))find; + +@end diff --git a/NSSet+find.m b/NSSet+find.m new file mode 100644 index 0000000..d495338 --- /dev/null +++ b/NSSet+find.m @@ -0,0 +1,14 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOFind) + +- (id(^)(id))find { + return ^id(BOOL (^block)(id o)) { + for (id item in self) + if (block(item)) + return item; + return nil; + }; +} + +@end diff --git a/NSSet+flatMap.h b/NSSet+flatMap.h new file mode 100644 index 0000000..622bc8d --- /dev/null +++ b/NSSet+flatMap.h @@ -0,0 +1,30 @@ +#import + +@interface NSSet (YOLOFlatMap) + +/** + Returns a new set with the concatenated results of running block once + for every element in the receiver. + + id rv = @[@1, @2, @3, @4].flatMap(^(id n){ + return @[n, n]; + }); + // rv => @[@1, @1, @2, @2, @3, @3, @4, @4] + + id rv = @[@1, @2, @3, @4].flatMap(^(id n){ + return [NSSet setWithArray:@[n, n]]; + }); + // rv => @[@1, @1, @2, @2, @3, @3, @4, @4] + + id rv = @[@1, @2, @3, @4].flatMap(^(id n){ + return @[n, @[n]]; + }); + // rv => @[@1, @[@1], @2, @[@2], @3, @[@3], @4, @[@4]] + + PROTIP: Useful over vanilla map followed by a flatten because flatten is + recursive, and you may want to preserve array relationships beyond the + first level. Also, `flatMap` is technically more efficient. +*/ +- (id(^)(NSSet *(^)(id o)))flatMap; + +@end diff --git a/NSSet+flatMap.m b/NSSet+flatMap.m new file mode 100644 index 0000000..559e62a --- /dev/null +++ b/NSSet+flatMap.m @@ -0,0 +1,19 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOFlatMap) + +- (NSSet *(^)(NSSet *(^)(id o)))flatMap { + return ^(NSSet *(^block)(id o)){ + NSMutableSet *rv = [NSMutableSet new]; + for (id o in self) { + id m = block(o); + if ([m isKindOfClass:[NSSet class]]) + [rv addObjectsFromArray:[m allObjects]]; + else + [rv addObjectsFromArray:m]; + } + return rv; + }; +} + +@end diff --git a/NSSet+flatten.h b/NSSet+flatten.h new file mode 100644 index 0000000..a3bd332 --- /dev/null +++ b/NSSet+flatten.h @@ -0,0 +1,14 @@ +#import + +@interface NSSet (YOLOFlatten) + +/** + Returns a new, one-dimensional array that is a recursive flattening of + the receiver. + + id rv = @[@[@1, @[@2]], @3, @[@4]].flatten + // rv => @[@1, @2, @3, @4] +*/ +- (NSSet *)flatten; + +@end diff --git a/NSSet+flatten.m b/NSSet+flatten.m new file mode 100644 index 0000000..9515c53 --- /dev/null +++ b/NSSet+flatten.m @@ -0,0 +1,18 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOFlatten) + +- (id)flatten { + NSMutableSet *aa = [NSMutableSet set]; + for (id o in self) { + if ([o isKindOfClass:[NSArray class]]) + [aa addObjectsFromArray:[o flatten]]; + else if ([o isKindOfClass:[NSSet class]]) + [aa addObjectsFromArray:[o allObjects]]; + else + [aa addObject:o]; + } + return aa; +} + +@end diff --git a/NSSet+groupBy.h b/NSSet+groupBy.h new file mode 100644 index 0000000..06e342c --- /dev/null +++ b/NSSet+groupBy.h @@ -0,0 +1,15 @@ +#import + +@interface NSSet (YOLOGroupBy) + +/** + Groups the collection by result of the given block. + + id rv = @[@1, @2, @3, @4].groupBy(^(NSNumber *n) { + return @(n.intValue % 2); + }); + // rv => @{@0: @[@1, @3], @1: @[@2, @4]} +*/ +- (NSDictionary *(^)(id (^)(id o)))groupBy; + +@end diff --git a/NSSet+groupBy.m b/NSSet+groupBy.m new file mode 100644 index 0000000..bb69ed3 --- /dev/null +++ b/NSSet+groupBy.m @@ -0,0 +1,19 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOGroupBy) + +- (NSDictionary *(^)(id (^)(id o)))groupBy { + return ^id(id (^block)(id)) { + NSMutableDictionary *dict = [NSMutableDictionary new]; + for (id o in self) { + id key = block(o); + if (!dict[key]) + dict[key] = [NSMutableArray arrayWithObject:o]; + else + [dict[key] addObject:o]; + } + return dict; + }; +} + +@end diff --git a/NSSet+has.h b/NSSet+has.h new file mode 100644 index 0000000..041c846 --- /dev/null +++ b/NSSet+has.h @@ -0,0 +1,24 @@ +#import + +@interface NSSet (YOLOHas) + +/** + BOOL rv = @[@1, @2, @3].has(@2) + // rv => YES + +Short-hand for containsObject:. Provided because doing a map, then a +select and a few more chained dot-notations are commonly followed with +the need to then determine if a particular value is in the resulting +array, and then having to square bracket the whole chain is ugly. + +We decided not to override any or find with the capability to take an +object rather than a block and instead add this method. Rest assured the +decision was careful. In the end has() seemed the choice that resulted in +the clearest code. + +`has` was chosen over *contains* or *includes* because it is short and +clear. +*/ +- (BOOL (^)(id o))has; + +@end diff --git a/NSSet+has.m b/NSSet+has.m new file mode 100644 index 0000000..ef888ad --- /dev/null +++ b/NSSet+has.m @@ -0,0 +1,11 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOHas) + +- (BOOL (^)(id o))has { + return ^BOOL(id o){ + return [self containsObject:o]; + }; +} + +@end diff --git a/NSSet+inject.h b/NSSet+inject.h new file mode 100644 index 0000000..37781a7 --- /dev/null +++ b/NSSet+inject.h @@ -0,0 +1,21 @@ +#import + +@interface NSSet (YOLOInject) + +/** + Combines all elements of the receiver by applying a binary operation. + + id rv = @[@1, @2, @3, @4].inject(@{}, ^(NSMutableDictionary *memo, NSNumber *n){ + memo[n] = @(n.intValue * n.intValue); + return memo; + }); + // rv => @{@1: @1, @2: @4, @3: @9, @4: @16} + + PROTIP: If you feed `inject` a non-mutable dictionary or array YOLOKit + mutates it for your block, and then finally returns a non-mutable copy. + + @see -reject +*/ +- (id(^)(id initial_memo, id (^)(id memo, id obj)))inject; + +@end diff --git a/NSSet+inject.m b/NSSet+inject.m new file mode 100644 index 0000000..fec8d4f --- /dev/null +++ b/NSSet+inject.m @@ -0,0 +1,18 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOInject) + +- (id(^)(id, id (^)(id, id)))inject { + return ^(id initial_memo, id (^block)(id, id)) { + BOOL wasNonMutable = [initial_memo classForCoder] == [NSArray class] || [initial_memo classForCoder] == [NSDictionary class] || [initial_memo classForCoder] == [NSSet class]; + if (wasNonMutable) + initial_memo = [initial_memo mutableCopy]; + + id memo = initial_memo; + for (id obj in self) + memo = block(memo, obj); + return wasNonMutable ? [memo copy] : memo; + }; +} + +@end diff --git a/NSSet+join.h b/NSSet+join.h new file mode 100644 index 0000000..cbd8686 --- /dev/null +++ b/NSSet+join.h @@ -0,0 +1,19 @@ +#import + +@interface NSSet (YOLOJoin) + +/** + Returns a string of all the receiver’s elements joined with the provided + separator string. + + id rv = @[@1, @2, @3, @4].join(@","); + // rv => @"1,2,3,4" + + id rv = @[@1, @2, @3, @4].join(@""); + // rv => @"1234" + + PROTIP: `-description` is called on all objects before joining them. +*/ +- (NSString *(^)(NSString *))join; + +@end diff --git a/NSSet+join.m b/NSSet+join.m new file mode 100644 index 0000000..8ae792c --- /dev/null +++ b/NSSet+join.m @@ -0,0 +1,12 @@ +#import "NSSet+pluck.h" +#import "YOLO.ph" + +@implementation NSSet (YOLOJoin) + +- (NSString *(^)(NSString *))join { + return ^(NSString *separator) { + return [self.pluck(@"description").allObjects componentsJoinedByString:separator ?: @""]; + }; +} + +@end diff --git a/NSSet+map.h b/NSSet+map.h new file mode 100644 index 0000000..e8590bd --- /dev/null +++ b/NSSet+map.h @@ -0,0 +1,29 @@ +#import + +@interface NSSet (YOLOMap) + +/** + Invokes the given block once for each element of self. Creates a new + array containing the values returned by the block. + + id rv = @[@1, @2, @3, @4].map(^(NSNumber *n){ + return @(n.intValue * n.intValue); + }); + // rv => @[@1, @4, @9, @16] + + If the given block returns nil, that element is skipped in the returned + array. + + The given block can have up to three parameters, the first is an element + in the array, the second that element’s index, and the third the array + itself. + + The second parameter can be a primitive (eg. `int`), or an `NSNumber *`: + + @"YOLO".split(@"").map(^(NSString *letter, int index){ + //… + }); +*/ +- (NSSet *(^)(id block))map; + +@end diff --git a/NSSet+map.m b/NSSet+map.m new file mode 100644 index 0000000..3b603b3 --- /dev/null +++ b/NSSet+map.m @@ -0,0 +1,35 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOMap) + +- (NSSet *(^)(id))map { + return ^id(id frock) { + NSMethodSignature *sig = YOLOMS(frock); + id (^block)(id, NSUInteger) = ^{ + switch (sig.numberOfArguments){ + case 2: return ^(id a, NSUInteger b){ return ((id(^)(id))frock)(a); }; + case 3: + return [sig getArgumentTypeAtIndex:2][0] == '@' + ? ^(id a, NSUInteger b){ return ((id(^)(id, id))frock)(a, @(b)); } + : ^(id a, NSUInteger b){ return ((id(^)(id, NSUInteger))frock)(a, b); }; + case 4: + return [sig getArgumentTypeAtIndex:2][0] == '@' + ? ^(id a, NSUInteger b){ return ((id(^)(id, id, id))frock)(a, @(b), self); } + : ^(id a, NSUInteger b){ return ((id(^)(id, NSUInteger, id))frock)(a, b, self); }; + default: + @throw @"Invalid argument count to map"; + } + }(); + + id mapped[self.count]; + NSUInteger ii = 0, jj = 0; + for (id mappable in self) { + id o = block(mappable, ii++); + if (o) + mapped[jj++] = o; + } + return [NSSet setWithObjects:mapped count:jj]; + }; +} + +@end diff --git a/NSSet+max.h b/NSSet+max.h new file mode 100644 index 0000000..6627948 --- /dev/null +++ b/NSSet+max.h @@ -0,0 +1,24 @@ +#import + +@interface NSSet (YOLOMax) + +/** + Returns the element for which the given block returns the largest + integer. + + id rv = @[@4, @2, @1, @3].max(^(NSNumber *n){ + return n.intValue; + }); + // rv => @4 + + id rv = @[@4, @2, @1, @3].max(^(NSNumber *n){ + return (n.intValue - 3) * (n.intValue - 3); + }); + // rv => @1 + + NOTE: currently you must return an integer, we plan to allow you to + return whatever you like. +*/ +- (id(^)(id))max; + +@end diff --git a/NSSet+max.m b/NSSet+max.m new file mode 100644 index 0000000..75b5c90 --- /dev/null +++ b/NSSet+max.m @@ -0,0 +1,20 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOMax) + +- (id(^)(id))max { + return ^(NSInteger (^block)(id o)) { + NSInteger value = NSIntegerMin; + id keeper = nil; + for (id o in self) { + NSInteger ov = block(o); + if (ov > value) { + value = ov; + keeper = o; + } + } + return keeper; + }; +} + +@end diff --git a/NSSet+min.h b/NSSet+min.h new file mode 100644 index 0000000..4370330 --- /dev/null +++ b/NSSet+min.h @@ -0,0 +1,19 @@ +#import + +@interface NSSet (YOLOMin) + +/** + Returns the element for which the given block returns the smallest + integer. + + id rv = @[@4, @2, @1, @3].min(^(NSNumber *n){ + return n; + }); + // rv => @1 + + NOTE: currently you must return an integer, we plan to allow you to + return whatever you like. +*/ +- (id(^)(id))min; + +@end diff --git a/NSSet+min.m b/NSSet+min.m new file mode 100644 index 0000000..d27d50b --- /dev/null +++ b/NSSet+min.m @@ -0,0 +1,20 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOMin) + +- (id(^)(id))min { + return ^(NSInteger (^block)(id o)) { + NSInteger value = NSIntegerMax; + id keeper = nil; + for (id o in self) { + NSInteger ov = block(o); + if (ov < value) { + value = ov; + keeper = o; + } + } + return keeper; + }; +} + +@end diff --git a/NSSet+none.h b/NSSet+none.h new file mode 100644 index 0000000..b7d1881 --- /dev/null +++ b/NSSet+none.h @@ -0,0 +1,18 @@ +#import + +@interface NSSet (YOLONone) + +/** + BOOL rv = @[@1, @2, @3].none(^(id o){ + return [o intValue] > 4; + }); + // rv => YES + + Instead of a block, you can pass a `Class` object. + + BOOL rv = @[@1, @2, @3].none(NSNumber.class); + // rv => NO +*/ +- (BOOL(^)(id blockOrClass))none; + +@end diff --git a/NSSet+none.m b/NSSet+none.m new file mode 100644 index 0000000..50bd178 --- /dev/null +++ b/NSSet+none.m @@ -0,0 +1,25 @@ +#import "YOLO.ph" +#import + +@implementation NSSet (YOLONone) + +- (BOOL(^)(id o))none { + return ^(id arg){ + BOOL (^block)(id o) = nil; + + if (arg == NSString.class /*or segfaults!*/ || YOLOIsClass(arg)) { + Class cls = arg; + block = ^(id o){ + return [o isKindOfClass:cls]; + }; + } else + block = arg; + + for (id o in self) + if (block(o)) + return NO; + return YES; + }; +} + +@end diff --git a/NSSet+partition.h b/NSSet+partition.h new file mode 100644 index 0000000..aecf861 --- /dev/null +++ b/NSSet+partition.h @@ -0,0 +1,16 @@ +#import + +@interface NSSet (YOLOPartition) + +/** + Partitions the receiver into two sets based on the boolean return + value of the given block. + + id rv = @[@"A", @"B", @"AA"].partition(^(id s){ + return [s hasPrefix:@"A"]; + }); + //rv => @[@[@"A", @"AA"], @[@"B"]] +*/ +- (NSSet *(^)(id blockReturningBool))partition; + +@end diff --git a/NSSet+partition.m b/NSSet+partition.m new file mode 100644 index 0000000..90d8aa2 --- /dev/null +++ b/NSSet+partition.m @@ -0,0 +1,15 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOPartition) + +- (NSSet *(^)(id o))partition { + return ^NSSet *(BOOL(^block)(id o)) { + id aa = [NSMutableSet new]; + id bb = [NSMutableSet new]; + for (id o in self) + [block(o) ? aa : bb addObject:o]; + return [NSSet setWithArray:@[aa, bb]]; + }; +} + +@end diff --git a/NSSet+pluck.h b/NSSet+pluck.h new file mode 100644 index 0000000..a82f191 --- /dev/null +++ b/NSSet+pluck.h @@ -0,0 +1,24 @@ +#import + +@interface NSSet (YOLOPluck) + +/** + Returns a new array that is the result of calling the given method on + each element in the receiver. + + MKShape *rhombas = [MKShape new]; rhombas.title = @"rhombas"; + MKShape *ellipse = [MKShape new]; ellipse.title = @"ellipse"; + MKShape *hexagon = [MKShape new]; hexagon.title = @"hexagon"; + + id rv = @[rhombas, ellipse, hexagon].pluck(@"title") + // rv => @[@"rhombas", @"ellipse", @"hexagon"] + + id rv = @[rhombas, ellipse, hexagon].pluck(@"title.uppercaseString") + // rv => @[@"RHOMBAS", @"ELLIPSE", @"HEXAGON"] + + NOTE: This is a convenience function for the common case of mapping + (`-map`) an array to the result of a method call on each element. +*/ +- (NSSet *(^)(NSString *keypath))pluck; + +@end diff --git a/NSSet+pluck.m b/NSSet+pluck.m new file mode 100644 index 0000000..2059f74 --- /dev/null +++ b/NSSet+pluck.m @@ -0,0 +1,15 @@ +@import Foundation.NSKeyValueCoding; +#import "YOLO.ph" + +@implementation NSSet (YOLOPluck) + +- (NSSet *(^)(NSString *))pluck { + return ^NSSet *(NSString *key) { + if (!key.length) + return self; + + return [self valueForKeyPath:key]; + }; +} + +@end diff --git a/NSSet+select.h b/NSSet+select.h new file mode 100644 index 0000000..b627969 --- /dev/null +++ b/NSSet+select.h @@ -0,0 +1,47 @@ +#import + +@interface NSSet (YOLOSelect) + +/** + Returns a new array containing all elements for which the given block + returns `YES`. + + id rv = @[@1, @2, @3, @4].select(^(NSNumber *n){ + return n.intValue % 2 == 0; + }); + // rv => @[@2, @4] + + Alternatively pass a `Class` object: + + id rv = @[@1, @"1", @{}].select(NSString.class) + // rv => @[@"1"] + + Convenient when searching for specifc subviews. + + self.view.subviews.select(UITextField.class); + + Though you may need an `allSubviews` pod. + + @see -reject +*/ +- (NSSet *(^)(id blockOrClass))select; + +/** + Returns a new array containing all elements for which the given block + returns `NO`. + + id rv = @[@1, @2, @3, @4].reject(^(NSNumber *n){ + return n.intValue % 2 == 0; + }); + // rv => @[@1, @3] + + Alternatively pass a `Class` object: + + id rv = @[@1, @"1", @{}].reject(NSNumber.class) + // rv => @[@"1", @{}] + + @see -select +*/ +- (NSSet *(^)(id blockOrClass))reject; + +@end diff --git a/NSSet+select.m b/NSSet+select.m new file mode 100644 index 0000000..1e07c26 --- /dev/null +++ b/NSSet+select.m @@ -0,0 +1,36 @@ +#import "YOLO.ph" +#import + +@implementation NSSet (YOLOSelect) + +// The blockToUse variable is necessary or: EXC_BAD_ACCESS + +#define YOLOSelectReject(logic) \ + if (!input) return [NSSet set]; \ + BOOL (^blockToUse)(); \ + if (YOLOIsClass(input)) { \ + blockToUse = ^(id o){ \ + return [o isKindOfClass:input]; \ + }; \ + } else \ + blockToUse = input;\ + id selected[self.count]; \ + int ii = 0; \ + for (id o in self) \ + if (logic blockToUse(o)) \ + selected[ii++] = o; \ + return [NSSet setWithObjects:selected count:ii] + +- (NSSet *(^)(id))select { + return ^(id input) { + YOLOSelectReject(!!); + }; +} + +- (NSSet *(^)(id))reject { + return ^(id input) { + YOLOSelectReject(!); + }; +} + +@end diff --git a/NSSet+union.h b/NSSet+union.h new file mode 100644 index 0000000..7575885 --- /dev/null +++ b/NSSet+union.h @@ -0,0 +1,14 @@ +#import + +@interface NSArray (YOLOConcat) + +/** + Returns a new array that is the receiver with the given array + concatenated to the end. + + id rv = @[@1, @2].concat(@[@3, @4]); + // rv => @[@1, @2, @3, @4] +*/ +- (NSArray *(^)(NSArray *other_array))concat; + +@end diff --git a/NSSet+union.m b/NSSet+union.m new file mode 100644 index 0000000..3a9ec11 --- /dev/null +++ b/NSSet+union.m @@ -0,0 +1,11 @@ +#import "YOLO.ph" + +@implementation NSArray (YOLOConcat) + +- (NSArray *(^)(NSArray *))concat { + return ^(id other_array) { + return [self arrayByAddingObjectsFromArray:other_array]; + }; +} + +@end diff --git a/NSSet+without.h b/NSSet+without.h new file mode 100644 index 0000000..5a11d0f --- /dev/null +++ b/NSSet+without.h @@ -0,0 +1,17 @@ +#import + +@interface NSSet (YOLOWithout) + +/** + Returns a new array where objects in the given array are removed from + the receiver. + + id rv = @[@1, @2, @3, @4, @5, @6].without(@2); + // rv => @[@1, @3, @4, @5, @6] + + id rv = @[@1, @2, @3, @4, @5, @6].without(@[@2, @3]); + // rv => @[@1, @4, @5, @6] +*/ +- (NSSet *(^)(id arrayOrSetOrObject))without; + +@end diff --git a/NSSet+without.m b/NSSet+without.m new file mode 100644 index 0000000..56506f8 --- /dev/null +++ b/NSSet+without.m @@ -0,0 +1,21 @@ +#import "YOLO.ph" + +@implementation NSSet (YOLOWithout) + +- (NSSet *(^)(id))without { + return ^NSSet *(id arrayOrSet) { + if (!arrayOrSet) + return self; + + id objs = [arrayOrSet isKindOfClass:[NSSet class]] + ? [arrayOrSet allObjects] + : [arrayOrSet isKindOfClass:[NSArray class]] + ? arrayOrSet + : @[arrayOrSet]; + id rv = self.mutableCopy; + [rv removeObjectsInArray:objs]; + return rv; + }; +} + +@end diff --git a/YOLO.h b/YOLO.h index 19bba16..a315fc0 100644 --- a/YOLO.h +++ b/YOLO.h @@ -142,12 +142,63 @@ #if __has_include() #import #endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif #if __has_include() #import #endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif #if __has_include() #import #endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif +#if __has_include() + #import +#endif #if __has_include() #import #endif