10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | #define kBLACK 0
14 | #define kRED 1
15 |
16 | /**
17 | @file CHRedBlackTree.h
18 | A Red-Black tree implementation of CHSearchTree.
19 | */
20 |
21 | /**
22 | A Red-Black tree, a balanced binary search tree with guaranteed O(log n) access. The algorithms for insertion and removal in this implementation have been adapted from code in the Red-Black trees tutorial, which is in the public domain, courtesy of Julienne Walker. Method names have been changed to match the APIs of existing Cocoa collections provided by Apple.
23 |
24 | A Red-Black tree has a few fundamental rules:
25 |
26 | - Every node is red or black.
27 | - All leaves (null children) are black, even when the parent is black.
28 | - If a node is red, both of its children must be black.
29 | - Every path from a node to its leaves has the same number of black nodes.
30 | - The root of the tree is black. (Optional, but simplifies things.)
31 |
32 |
33 | These constraints, and in particular the black path height and non-consecutive red nodes, guarantee that longest path from the root to a leaf is no more than twice as long as the shortest path from the root to a leaf. (This is true since the shortest possible path has only black nodes, and the longest possible path alternates between red and black nodes.) The result is a fairly balanced tree.
34 |
35 | Figure 1 - A sample Red-Black tree
36 | @image html red-black-tree.png
37 |
38 | The sentinel node (which appears whenever a child link would be null) is always colored black. The algorithms for balancing Red-Black trees can be made to work without explicitly representing the nil leaf children, but they work better and with much less heartache if those links are present. The same sentinel value is used for every leaf link, so it only adds the cost of storing one more node. In addition, tracing a path down the tree doesn't have to check for null children at each step, so insertion, search, and deletion are all a little bit faster.
39 |
40 | Red-Black trees were originally described in the following papers:
41 |
42 |
43 | R. Bayer. "Binary B-Trees for Virtual Memory." ACM-SIGFIDET Workshop on
44 | Data Description, 1971, San Diego, California, Session 5B, p. 219-235.
45 |
46 |
47 |
48 | R. Bayer and E. M. McCreight. "Organization and Maintenance of Large Ordered
49 | Indexes." Acta Informatica 1, 173-189, 1972.
50 |
51 |
52 |
53 | L. J. Guibas and R. Sedgewick. "A dichromatic framework for balanced trees."
54 |
19th Annual Symposium on Foundations of Computer Science, pp.8-21,
55 | 1978. (
DOI link to IEEE)
56 |
57 | */
58 | @interface CHRedBlackTree : CHAbstractBinarySearchTree
59 |
60 | @end
61 |
62 | NS_ASSUME_NONNULL_END
63 |
--------------------------------------------------------------------------------
/source/CHAVLTree.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHAVLTree.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | /**
13 | @file CHAVLTree.h
14 | An AVL tree implementation of CHSearchTree.
15 | */
16 |
17 | /**
18 | An AVL tree, a balanced binary search tree with guaranteed O(log n) access. The algorithms for insertion and removal in this implementation have been adapted from code in the AVL trees tutorial, which is in the public domain, courtesy of Julienne Walker. Method names have been changed to match the APIs of existing Cocoa collections provided by Apple.
19 |
20 | AVL trees are more strictly balanced that most self-balancing binary trees, and consequently have slower insertion and deletion performance but faster lookup, although all operations are still O(log n) in both average and worst cases. AVL trees are shallower than their counterparts; for example, the maximum depth of AVL trees is 1.44 log n, versus 2 log n for Red-Black trees. In practice, AVL trees are quite competitive with other self-balancing trees, and the insertion and removal algorithms are much easier to understand.
21 |
22 | In an AVL tree, the heights of the child subtrees of any node may differ by at most one. If the heights differ by more than one, then one or more rotations around the unbalanced node are required to rebalance the tree. The 4 possible unbalanced cases and how to rebalance them are shown in Figure 1.
23 |
24 | Figure 1 - Rebalancing cases in an AVL tree.
25 | @image html avl-tree-rotations.png
26 |
27 | Although traditional AVL algorithms track the height of each node (one plus the maximum of the height of its chilren) this approach requires updating all the heights along the search path when inserting or removing objects. This penalty can be mitigated by instead storing a balance factor for each node, calculated as the height of the right subtree minus the height of the left subtree. (Any node with a balance factor of -1, 0, or +1 is considered to bebalanced.) If the balance factor of a node is -2 or +2, then rebalancing is required.
28 |
29 | Balance factors are updated when rotating, and at each rotation we must proceed back up the search path. However, on deletion, we can drop out of the loop when a node's balance factor becomes -1 or +1, since the heights of its subtrees has not changed. (If a node's balance factor becomes 0, the parent's balance factor must be updated, and may change to -2 or +2, requiring another rebalance.)
30 |
31 | Figure 2 shows a sample AVL tree, with tree heights in blue and balance factors in red beside each node.
32 |
33 | Figure 2 - Sample AVL tree and balancing data.
34 | @image html avl-tree-sample.png
35 |
36 | AVL trees were originally described in the following paper:
37 |
38 |
39 | G. M. Adelson-Velsky and E. M. Landis. "An algorithm for the organization of information." Proceedings of the USSR Academy of Sciences, 146:263-266, 1962. (English translation in Soviet Mathematics, 3:1259-1263, 1962.)
40 |
41 | */
42 | @interface CHAVLTree : CHAbstractBinarySearchTree
43 |
44 | @end
45 |
46 | NS_ASSUME_NONNULL_END
47 |
--------------------------------------------------------------------------------
/CHDataStructures.xcodeproj/xcshareddata/xcschemes/Benchmarks.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/source/CHMutableSet.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHMutableSet.m
3 | // CHDataStructures
4 | //
5 | // Copyright © 2009-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | const void * CHMutableSetRetain(CFAllocatorRef allocator, const void *value) {
11 | return [(id)value retain];
12 | }
13 |
14 | void CHMutableSetRelease(CFAllocatorRef allocator, const void *value) {
15 | [(id)value release];
16 | }
17 |
18 | CFStringRef CHMutableSetCopyDescription(const void *value) {
19 | return CFRetain([(id)value description]);
20 | }
21 |
22 | Boolean CHMutableSetEqual(const void *value1, const void *value2) {
23 | return [(id)value1 isEqual:(id)value2];
24 | }
25 |
26 | CFHashCode CHMutableSetHash(const void *value) {
27 | return (CFHashCode)[(id)value hash];
28 | }
29 |
30 | static const CFSetCallBacks kCHMutableSetCallbacks = {
31 | 0, // default version
32 | CHMutableSetRetain,
33 | CHMutableSetRelease,
34 | CHMutableSetCopyDescription,
35 | CHMutableSetEqual,
36 | CHMutableSetHash
37 | };
38 |
39 | #pragma mark -
40 |
41 | @implementation CHMutableSet
42 |
43 | - (void)dealloc {
44 | CFRelease(set); // The set will never be null at this point.
45 | [super dealloc];
46 | }
47 |
48 | // Note: Defined here since -init is not implemented in NS(Mutable)Set.
49 | - (instancetype)init {
50 | return [self initWithCapacity:0]; // The 0 means we provide no capacity hint
51 | }
52 |
53 | // Note: This is the designated initializer for NSMutableSet and this class.
54 | // Subclasses may override this as necessary, but must call back here first.
55 | - (instancetype)initWithCapacity:(NSUInteger)numItems {
56 | self = [super init];
57 | if (self) {
58 | set = CFSetCreateMutable(kCFAllocatorDefault, numItems, &kCHMutableSetCallbacks);
59 | }
60 | return self;
61 | }
62 |
63 | - (instancetype)initWithCoder:(NSCoder *)decoder {
64 | return [self initWithArray:[decoder decodeObjectForKey:@"objects"]];
65 | }
66 |
67 | - (void)encodeWithCoder:(NSCoder *)encoder {
68 | [encoder encodeObject:[self allObjects] forKey:@"objects"];
69 | }
70 |
71 | // Overridden from NSMutableSet to encode/decode as the proper class.
72 | - (Class)classForKeyedArchiver {
73 | return [self class];
74 | }
75 |
76 | #pragma mark Querying Contents
77 |
78 | - (id)anyObject {
79 | return [(id)set anyObject];
80 | }
81 |
82 | - (BOOL)containsObject:(id)anObject {
83 | return CFSetContainsValue(set, anObject);
84 | }
85 |
86 | - (NSUInteger)count {
87 | return CFSetGetCount(set);
88 | }
89 |
90 | - (NSString *)debugDescription {
91 | CFStringRef description = CFCopyDescription(set);
92 | CFRelease([(id)description retain]);
93 | return [(id)description autorelease];
94 | }
95 |
96 | - (NSString *)description {
97 | return [[self allObjects] description];
98 | }
99 |
100 | - (id)member:(id)anObject {
101 | return [(id)set member:anObject];
102 | }
103 |
104 | - (NSEnumerator *)objectEnumerator {
105 | return [(id)set objectEnumerator];
106 | }
107 |
108 | #pragma mark Modifying Contents
109 |
110 | - (void)addObject:(id)anObject {
111 | CFSetSetValue(set, anObject);
112 | }
113 |
114 | - (void)removeAllObjects {
115 | CFSetRemoveAllValues(set);
116 | }
117 |
118 | - (void)removeObject:(id)anObject {
119 | CHRaiseInvalidArgumentExceptionIfNil(anObject);
120 | CFSetRemoveValue(set, anObject);
121 | }
122 |
123 | #pragma mark
124 |
125 | - (instancetype)copyWithZone:(NSZone *)zone {
126 | CHMutableSet *copy = [[[self class] allocWithZone:zone] init];
127 | [copy addObjectsFromArray:[self allObjects]];
128 | return copy;
129 | }
130 |
131 | @end
132 |
--------------------------------------------------------------------------------
/source/CHSinglyLinkedList.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHSinglyLinkedList.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | // Copyright © 2002, Phillip Morelock
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | /**
14 | @file CHSinglyLinkedList.h
15 | A standard singly-linked list implementation with pointers to head and tail.
16 | */
17 |
18 | /** A struct for nodes in a CHSinglyLinkedList. */
19 | typedef struct CHSinglyLinkedListNode {
20 | __unsafe_unretained id object; ///< The object associated with this node in the list.
21 | struct CHSinglyLinkedListNode *_Nullable next; ///< The next node in the list.
22 | } CHSinglyLinkedListNode;
23 |
24 | #pragma mark -
25 |
26 | /**
27 | A standard singly-linked list implementation with pointers to head and tail. This is ideally suited for use in LIFO and FIFO structures (stacks and queues). The lack of backwards links precludes backwards enumeration, and removing from the tail of the list is O(n), rather than O(1). However, other operations are slightly faster than for doubly-linked lists. Nodes are represented with C structs, providing much faster performance than Objective-C objects.
28 |
29 | This implementation uses a dummy head node, which simplifies both insertion and deletion by eliminating the need to check whether the first node in the list must change. We opt not to use a dummy tail node since the lack of a previous pointer makes starting at the end of the list rather pointless. The pointer to the tail (either the dummy head node or the last "real" node in the list) is only used for inserting at the end without traversing the entire list first. The figures below demonstrate what a singly-linked list looks like when it contains 0 objects, 1 object, and 2 or more objects.
30 |
31 | @image html singly-linked-0.png Figure 1 - Singly-linked list with 0 objects.
32 |
33 | @image html singly-linked-1.png Figure 2 - Singly-linked list with 1 object.
34 |
35 | @image html singly-linked-N.png Figure 3 - Singly-linked list with 2+ objects.
36 |
37 | To reduce code duplication, all methods that append or prepend objects to the list call \link #insertObject:atIndex:\endlink, and the methods to remove the first or last objects use \link #removeObjectAtIndex:\endlink underneath.
38 |
39 | Singly-linked lists are well-suited as an underlying collection for other data structures, such as stacks and queues (see CHListStack and CHListQueue). The same functionality can be achieved using a circular buffer and an array, and many libraries choose to do so when objects are only added to or removed from the ends, but the dynamic structure of a linked list is much more flexible when inserting and deleting in the middle of a list.
40 |
41 | The primary weakness of singly-linked lists is the absence of a previous link. Since insertion and deletion involve changing the @c next link of the preceding node, and there is no way to step backwards through the list, traversal must always begin at the head, even if searching for an index that is very close to the tail. This does not mean that singly-linked lists are inherently bad, only that they are not well-suited for all possible applications. As usual, all data access attributes should be considered before choosing a data strcuture.
42 | */
43 | @interface CHSinglyLinkedList<__covariant ObjectType> : NSObject
44 | {
45 | CHSinglyLinkedListNode *head; // Dummy node at the front of the list.
46 | CHSinglyLinkedListNode *tail; // Pointer to last node in a list.
47 | CHSinglyLinkedListNode *cachedNode; // Pointer to last accessed node.
48 | NSUInteger cachedIndex; // Index of last accessed node.
49 | NSUInteger count; // The number of objects currently stored in a list.
50 | unsigned long mutations; // Tracks mutations for NSFastEnumeration.
51 | }
52 |
53 | - (instancetype)initWithArray:(NSArray *)array NS_DESIGNATED_INITIALIZER;
54 |
55 | @end
56 |
57 | NS_ASSUME_NONNULL_END
58 |
--------------------------------------------------------------------------------
/source/CHSortedDictionary.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHSortedDictionary.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2009-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | /**
14 | @file CHSortedDictionary.h
15 |
16 | A dictionary which enumerates keys according to their natural sorted order.
17 | */
18 |
19 | /**
20 | A dictionary which enumerates keys according to their natural sorted order. The following additional operations are provided to take advantage of the ordering:
21 | - \link #firstKey\endlink
22 | - \link #lastKey\endlink
23 | - \link #subsetFromKey:toKey:options:\endlink
24 |
25 | Key-value entries are inserted just as in a normal dictionary, including replacement of values for existing keys, as detailed in \link NSMutableDictionary#setObject:forKey: -[NSMutableDictionary setObject:forKey:]\endlink. However, an additional CHSortedSet structure is used in parallel to sort the keys, and keys are enumerated in that order.
26 |
27 | Implementations of sorted dictionaries (aka "maps") in other languages include the following:
28 |
29 | - SortedMap (Java)
30 | - map (C++)
31 |
32 | @note Any method inherited from NSDictionary or NSMutableDictionary is supported, but only overridden methods are listed here.
33 |
34 | @see CHSortedSet
35 | */
36 | @interface CHSortedDictionary<__covariant KeyType, __covariant ObjectType> : CHMutableDictionary
37 | {
38 | id sortedKeys;
39 | }
40 |
41 | #pragma mark Querying Contents
42 | /** @name Querying Contents */
43 | // @{
44 |
45 | /**
46 | Returns the minimum key in the receiver, according to natural sorted order.
47 |
48 | @return The minimum key in the receiver, or @c nil if the receiver is empty.
49 |
50 | @see lastKey
51 | */
52 | - (nullable KeyType)firstKey;
53 |
54 | /**
55 | Returns the maximum key in the receiver, according to natural sorted order.
56 |
57 | @return The maximum key in the receiver, or @c nil if the receiver is empty.
58 |
59 | @see firstKey
60 | */
61 | - (nullable KeyType)lastKey;
62 |
63 | /**
64 | Returns a new dictionary containing the entries for keys delineated by two given objects. The subset is a shallow copy (new memory is allocated for the structure, but the copy points to the same objects) so any changes to the objects in the subset affect the receiver as well. The subset is an instance of the same class as the receiver.
65 |
66 | @param start Low endpoint of the subset to be returned; need not be a key in receiver.
67 | @param end High endpoint of the subset to be returned; need not be a key in receiver.
68 | @param options A combination of @c CHSubsetConstructionOptions values that specifies how to construct the subset. Pass 0 for the default behavior, or one or more options combined with a bitwise OR to specify different behavior.
69 | @return A new sorted dictionary containing the key-value entries delineated by @a start and @a end. The contents of the returned subset depend on the input parameters as follows:
70 | - If both @a start and @a end are @c nil, all keys in the receiver are included. (Equivalent to calling @c -copy.)
71 | - If only @a start is @c nil, keys that match or follow @a start are included.
72 | - If only @a end is @c nil, keys that match or preceed @a start are included.
73 | - If @a start comes before @a end in an ordered set, keys between @a start and @a end (or which match either object) are included.
74 | - Otherwise, all keys @b except those that fall between @a start and @a end are included.
75 | */
76 | - (NSMutableDictionary *)subsetFromKey:(KeyType)start
77 | toKey:(KeyType)end
78 | options:(CHSubsetConstructionOptions)options;
79 |
80 | // @}
81 | @end
82 |
83 | NS_ASSUME_NONNULL_END
84 |
--------------------------------------------------------------------------------
/test/CHUtilTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHUtilTest.m
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 | #import
10 |
11 | @interface CHUtilTest : XCTestCase
12 |
13 | @end
14 |
15 | @implementation CHUtilTest
16 |
17 | #define expectedReason(message) \
18 | ([NSString stringWithFormat:@"%s -- %@", __PRETTY_FUNCTION__, message])
19 |
20 | - (void)testCollectionsAreEqual {
21 | NSArray *array = @[@"A",@"B",@"C"];
22 | NSDictionary *dict = [NSDictionary dictionaryWithObjects:array forKeys:array];
23 | NSSet *set = [NSSet setWithObjects:@"A",@"B",@"C",nil];
24 |
25 | XCTAssertTrue(CHCollectionsAreEqual(nil, nil));
26 |
27 | XCTAssertTrue(CHCollectionsAreEqual(array, array));
28 | XCTAssertTrue(CHCollectionsAreEqual(dict, dict));
29 | XCTAssertTrue(CHCollectionsAreEqual(set, set));
30 |
31 | XCTAssertTrue(CHCollectionsAreEqual(array, [array copy]));
32 | XCTAssertTrue(CHCollectionsAreEqual(dict, [dict copy]));
33 | XCTAssertTrue(CHCollectionsAreEqual(set, [set copy]));
34 |
35 | XCTAssertFalse(CHCollectionsAreEqual(array, nil));
36 | XCTAssertFalse(CHCollectionsAreEqual(dict, nil));
37 | XCTAssertFalse(CHCollectionsAreEqual(set, nil));
38 |
39 | id obj = [NSString string];
40 | XCTAssertThrowsSpecificNamed(CHCollectionsAreEqual(array, obj), NSException, NSInvalidArgumentException);
41 | XCTAssertThrowsSpecificNamed(CHCollectionsAreEqual(dict, obj), NSException, NSInvalidArgumentException);
42 | XCTAssertThrowsSpecificNamed(CHCollectionsAreEqual(set, obj), NSException, NSInvalidArgumentException);
43 | }
44 |
45 | - (void)testIndexOutOfRangeException {
46 | @try {
47 | int idx = 5;
48 | int count = 4;
49 | CHRaiseIndexOutOfRangeExceptionIf(idx, >, count);
50 | XCTFail("Expected an NSRangeException.");
51 | }
52 | @catch (NSException * e) {
53 | XCTAssertEqualObjects([e name], NSRangeException);
54 | XCTAssertEqualObjects([e reason], expectedReason(@"Index out of range: idx (5) > count (4)"));
55 | }
56 | }
57 |
58 | - (void)testInvalidArgumentException {
59 | @try {
60 | CHRaiseInvalidArgumentException(@"Some silly reason.");
61 | XCTFail("Expected an NSInvalidArgumentException.");
62 | }
63 | @catch (NSException * e) {
64 | XCTAssertEqualObjects([e name], NSInvalidArgumentException);
65 | XCTAssertEqualObjects([e reason], expectedReason(@"Some silly reason."));
66 | }
67 | }
68 |
69 | - (void)testNilArgumentException {
70 | id object = nil;
71 | @try {
72 | CHRaiseInvalidArgumentExceptionIfNil(object);
73 | XCTFail("Expected an NSInvalidArgumentException.");
74 | }
75 | @catch (NSException * e) {
76 | XCTAssertEqualObjects([e name], NSInvalidArgumentException);
77 | XCTAssertEqualObjects([e reason], expectedReason(@"Invalid nil value: object"));
78 | }
79 | }
80 |
81 | - (void)testMutatedCollectionException {
82 | @try {
83 | CHRaiseMutatedCollectionException();
84 | XCTFail("Expected an NSGenericException.");
85 | }
86 | @catch (NSException * e) {
87 | XCTAssertEqualObjects([e name], NSGenericException);
88 | XCTAssertEqualObjects([e reason], expectedReason(@"Collection was mutated during enumeration"));
89 | }
90 | }
91 |
92 | - (void)testUnsupportedOperationException {
93 | @try {
94 | CHRaiseUnsupportedOperationException();
95 | XCTFail("Expected an NSInternalInconsistencyException.");
96 | }
97 | @catch (NSException * e) {
98 | XCTAssertEqualObjects([e name], NSInternalInconsistencyException);
99 | XCTAssertEqualObjects([e reason], expectedReason(@"Unsupported operation"));
100 | }
101 | }
102 |
103 | - (void)testCHQuietLog {
104 | // Can't think of a way to verify stdout, so I'll just exercise all the code
105 | CHQuietLog(@"Hello, world!");
106 | CHQuietLog(@"Hello, world! I accept specifiers: %@ instance at 0x%x.",
107 | [self class], self);
108 | CHQuietLog(nil);
109 | }
110 |
111 | @end
112 |
--------------------------------------------------------------------------------
/source/CHAbstractListCollection.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHAbstractListCollection.m
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | @interface CHAbstractListCollection ()
11 |
12 | - (instancetype)initWithCoder:(NSCoder *)decoder NS_DESIGNATED_INITIALIZER;
13 |
14 | @end
15 |
16 | @implementation CHAbstractListCollection
17 |
18 | - (void)dealloc {
19 | [list release];
20 | [super dealloc];
21 | }
22 |
23 | - (instancetype)init {
24 | return [self initWithArray:@[]];
25 | }
26 |
27 | - (instancetype)initWithArray:(NSArray *)anArray {
28 | CHRaiseInvalidArgumentExceptionIfNil(anArray);
29 | self = [super init];
30 | if (self) {
31 | list = [self _newList];
32 | for (id anObject in anArray) {
33 | [list addObject:anObject];
34 | }
35 | }
36 | return self;
37 | }
38 |
39 | // Child classes must override to provide a value for the "list" instance variable.
40 | - (id)_newList {
41 | CHRaiseUnsupportedOperationException();
42 | return nil;
43 | }
44 |
45 | #pragma mark
46 |
47 | - (instancetype)initWithCoder:(NSCoder *)decoder {
48 | self = [super init];
49 | if (self) {
50 | list = [[decoder decodeObjectForKey:@"list"] retain];
51 | }
52 | return self;
53 | }
54 |
55 | - (void)encodeWithCoder:(NSCoder *)encoder {
56 | [encoder encodeObject:list forKey:@"list"];
57 | }
58 |
59 | #pragma mark
60 |
61 | - (instancetype)copyWithZone:(NSZone *)zone {
62 | id copy = [[[self class] allocWithZone:zone] init];
63 | for (id anObject in self) {
64 | [copy addObject:anObject];
65 | }
66 | return copy;
67 | }
68 |
69 | #pragma mark
70 |
71 | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len {
72 | return [list countByEnumeratingWithState:state objects:stackbuf count:len];
73 | }
74 |
75 | #pragma mark -
76 |
77 | - (NSUInteger)count {
78 | return [list count];
79 | }
80 |
81 | - (BOOL)containsObject:(id)anObject {
82 | return [list containsObject:anObject];
83 | }
84 |
85 | - (BOOL)containsObjectIdenticalTo:(id)anObject {
86 | return [list containsObjectIdenticalTo:anObject];
87 | }
88 |
89 | - (void)exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2 {
90 | [list exchangeObjectAtIndex:idx1 withObjectAtIndex:idx2];
91 | }
92 |
93 | - (NSUInteger)hash {
94 | return CHHashOfCountAndObjects([list count], [list firstObject], [list lastObject]);
95 | }
96 |
97 | - (NSUInteger)indexOfObject:(id)anObject {
98 | return [list indexOfObject:anObject];
99 | }
100 |
101 | - (NSUInteger)indexOfObjectIdenticalTo:(id)anObject {
102 | return [list indexOfObjectIdenticalTo:anObject];
103 | }
104 |
105 | - (id)objectAtIndex:(NSUInteger)index {
106 | return [list objectAtIndex:index];
107 | }
108 |
109 | - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes {
110 | return [list objectsAtIndexes:indexes];
111 | }
112 |
113 | - (void)removeObject:(id)anObject {
114 | [list removeObject:anObject];
115 | }
116 |
117 | - (void)removeObjectIdenticalTo:(id)anObject {
118 | [list removeObjectIdenticalTo:anObject];
119 | }
120 |
121 | - (void)removeObjectAtIndex:(NSUInteger)index {
122 | [list removeObjectAtIndex:index];
123 | }
124 |
125 | - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
126 | [list removeObjectsAtIndexes:indexes];
127 | }
128 |
129 | - (void)removeAllObjects {
130 | [list removeAllObjects];
131 | }
132 |
133 | - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
134 | [list replaceObjectAtIndex:index withObject:anObject];
135 | }
136 |
137 | - (NSArray *)allObjects {
138 | return [list allObjects];
139 | }
140 |
141 | - (NSEnumerator *)objectEnumerator {
142 | return [list objectEnumerator];
143 | }
144 |
145 | - (NSString *)description {
146 | return [list description];
147 | }
148 |
149 | @end
150 |
--------------------------------------------------------------------------------
/source/CHAnderssonTree.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHAnderssonTree.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | /**
13 | @file CHAnderssonTree.h
14 | An AA-tree implementation of CHSearchTree.
15 | */
16 |
17 | /**
18 | An AA-tree, a balanced binary search tree with guaranteed O(log n) access. The algorithms for insertion and removal have been adapted from code in the Andersson Tree tutorial, which is in the public domain, courtesy of Julienne Walker. Method names have been changed to match the APIs of existing Cocoa collections provided by Apple.
19 |
20 | The AA-tree (named after its creator, Arne Andersson) is extremely similar to a Red-Black tree. Both are abstractions of B-trees designed to make insertion and removal easier to understand and implement. In a Red-Black tree, a red node represents a horizontal link to a subtree rooted at the same level. In the AA-tree, horizontal links are represented by storing a node's level, not color. (A node whose level is the same as its parent is equivalent to a "red" node.)
21 |
22 | Similar to a Red-Black tree, there are several rules which must be true for an AA-tree to remain valid:
23 |
24 |
25 | - The level of a leaf node is one.
26 | - The level of a left child is less than that of its parent.
27 | - The level of a right child is less than or equal to that of its parent.
28 | - The level of a right grandchild is less than that of its grandparent.
29 | - Every node of level greater than one must have two children.
30 |
31 |
32 | The AA-tree was invented to simplify the algorithms for balancing an abstract B-tree, and does so with a simple restriction: horizontal links ("red nodes") are only allowed as the right child of a node. If we represent both node level and the implicit red colors, an AA-tree looks like the following example:
33 |
34 |
35 | Figure 1 - A sample AA-tree with node levels and implicit coloring.
36 | @image html aa-tree-sample.png
37 |
38 |
39 | Because horizontal links are only allowed on the right, rather than balancing all 7 possible subtrees of 2 and 3 nodes (shown in Figure 2) when removing, an AA-tree need only be concerned with 2: the first and last forms in the figure.
40 |
41 |
42 | Figure 2 - All possible 2- and 3-node subtrees
43 | @image html aa-tree-shapes.png
44 |
45 |
46 | Consequently, only two primitive balancing operations are necessary. The @c skew operation eliminates red nodes as left children, while the @c split operation eliminates consecutive right-child red nodes. (Both of these operations are depicted in the figures below.)
47 |
48 |
49 | Figure 3 - The skew operation.
50 | @image html aa-tree-skew.png
51 |
52 |
53 |
54 | Figure 4 - The split operation.
55 | @image html aa-tree-split.png
56 |
57 |
58 | Performance of an AA-tree is roughly equivalent to that of a Red-Black tree. While an AA-tree makes more rotations than a Red-Black tree, the algorithms are simpler to understand and tend to be somewhat faster, and all of this balances out to result in similar performance. A Red-Black tree is more consistent in its performance than an AA-tree, but an AA-tree tends to be flatter, which results in slightly faster search.
59 |
60 | AA-trees were originally described in the following paper:
61 |
62 |
63 | A. Andersson. "Balanced search trees made simple." Workshop on Algorithms
64 | and Data Structures, pp.60-71. Springer Verlag, 1993.
65 |
66 |
67 | (See PDF original or PostScript original)
68 | */
69 | @interface CHAnderssonTree : CHAbstractBinarySearchTree
70 |
71 | @end
72 |
73 | NS_ASSUME_NONNULL_END
74 |
--------------------------------------------------------------------------------
/tools/strip_comments.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/python
2 |
3 | from optparse import OptionParser
4 | import os.path
5 | import sys
6 |
7 | parser = OptionParser()
8 | parser.add_option("-L", "--line", dest="stripLine",
9 | action="store_true", default=False,
10 | help="strip single-line comments //...\\n")
11 | parser.add_option("-C", "--cstyle", dest="stripCStyle",
12 | action="store_true", default=False,
13 | help="strip C-style comments /*...*/")
14 | parser.add_option("-J", "--javadoc", dest="stripJavadoc",
15 | action="store_true", default=False,
16 | help="strip Javadoc comments /**...*/")
17 | parser.add_option("-H", "--headerdoc", dest="stripHeaderDoc",
18 | action="store_true", default=False,
19 | help="strip HeaderDoc comments /*!...*/")
20 | parser.add_option("--input", dest="inputFile", default="",
21 | help="file from which to read input")
22 | (options, args) = parser.parse_args()
23 |
24 | error = False
25 | if len(args) != 0:
26 | print "ERROR: Invalid non-option arguments:"
27 | for arg in args:
28 | print " "+arg
29 | error = True
30 | if not options.stripLine and not options.stripCStyle and \
31 | not options.stripJavadoc and not options.stripHeaderDoc:
32 | print "ERROR: Please specify at least one comment style to strip."
33 | error = True
34 | if options.inputFile == "":
35 | print "ERROR: Must specify input file to process using '--input'."
36 | error = True
37 | elif os.path.exists(options.inputFile) == False:
38 | print "ERROR: Specified input file does not exist!"
39 | error = True
40 | else:
41 | file = open(options.inputFile, "r")
42 | if error == True:
43 | sys.exit()
44 |
45 | (SOURCE, STRING_LITERAL, CHAR_LITERAL, SLASH, SLASH_STAR, COMMENT_LINE,
46 | COMMENT_CSTYLE, COMMENT_JAVADOC, COMMENT_HEADERDOC) = range(9) #state constants
47 |
48 | state = SOURCE
49 | thisChar = '\n'
50 | while (1):
51 | prevChar = thisChar
52 | thisChar = file.read(1)
53 | if not thisChar:
54 | break
55 |
56 | if state == SOURCE:
57 | if thisChar == '/':
58 | state = SLASH
59 | else:
60 | if thisChar == '"':
61 | state = STRING_LITERAL
62 | elif thisChar == '\'':
63 | state = CHAR_LITERAL
64 | sys.stdout.write(thisChar)
65 |
66 | elif state == STRING_LITERAL:
67 | if thisChar == '"' and prevChar != '\\':
68 | state = SOURCE
69 | sys.stdout.write(thisChar)
70 |
71 | elif state == CHAR_LITERAL:
72 | if thisChar == '\'' and prevChar != '\\':
73 | state = SOURCE
74 | sys.stdout.write(thisChar)
75 |
76 | elif state == SLASH:
77 | if thisChar == '*':
78 | state = SLASH_STAR
79 | elif thisChar == '/':
80 | if not options.stripLine:
81 | sys.stdout.write("//")
82 | state = COMMENT_LINE
83 | else:
84 | sys.stdout.write("/")
85 | sys.stdout.write(thisChar)
86 | state = SOURCE
87 |
88 | elif state == SLASH_STAR:
89 | if thisChar == '*':
90 | if not options.stripJavadoc:
91 | sys.stdout.write("/**")
92 | state = COMMENT_JAVADOC
93 | elif thisChar == '!':
94 | if not options.stripHeaderDoc:
95 | sys.stdout.write("/*!")
96 | state = COMMENT_HEADERDOC
97 | else:
98 | if not options.stripCStyle:
99 | sys.stdout.write("/*")
100 | sys.stdout.write(thisChar)
101 | state = COMMENT_CSTYLE
102 | thisChar = 0
103 | # Prevents counting "/*/" as a C-style block comment
104 |
105 | elif state == COMMENT_LINE:
106 | if thisChar == '\n':
107 | sys.stdout.write("\n")
108 | state = SOURCE
109 | if not options.stripLine:
110 | sys.stdout.write(thisChar)
111 |
112 | elif state == COMMENT_CSTYLE:
113 | if not options.stripCStyle:
114 | sys.stdout.write(thisChar)
115 | if prevChar == '*' and thisChar == '/':
116 | state = SOURCE
117 |
118 | elif state == COMMENT_JAVADOC:
119 | if not options.stripJavadoc:
120 | sys.stdout.write(thisChar)
121 | if prevChar == '*' and thisChar == '/':
122 | state = SOURCE
123 |
124 | elif state == COMMENT_HEADERDOC:
125 | if not options.stripHeaderDoc:
126 | sys.stdout.write(thisChar)
127 | if prevChar == '*' and thisChar == '/':
128 | state = SOURCE
129 |
130 | file.close()
131 |
--------------------------------------------------------------------------------
/source/CHOrderedSet.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHOrderedSet.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2009-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 | #import
10 |
11 | @implementation CHOrderedSet
12 |
13 | - (void)dealloc {
14 | [ordering release];
15 | [super dealloc];
16 | }
17 |
18 | - (instancetype)init {
19 | return [self initWithCapacity:0];
20 | }
21 |
22 | - (instancetype)initWithCapacity:(NSUInteger)numItems {
23 | self = [super initWithCapacity:numItems];
24 | if (self) {
25 | ordering = [[CHCircularBuffer alloc] initWithCapacity:numItems];
26 | }
27 | return self;
28 | }
29 |
30 | #pragma mark Querying Contents
31 |
32 | - (NSArray *)allObjects {
33 | return [ordering allObjects];
34 | }
35 |
36 | - (id)firstObject {
37 | return [ordering firstObject];
38 | }
39 |
40 | - (NSUInteger)hash {
41 | return [ordering hash];
42 | }
43 |
44 | - (NSUInteger)indexOfObject:(id)anObject {
45 | return [ordering indexOfObject:anObject];
46 | }
47 |
48 | - (BOOL)isEqualToOrderedSet:(CHOrderedSet *)otherOrderedSet {
49 | return CHCollectionsAreEqual(self, otherOrderedSet);
50 | }
51 |
52 | - (id)lastObject {
53 | return [ordering lastObject];
54 | }
55 |
56 | - (id)objectAtIndex:(NSUInteger)index {
57 | return [ordering objectAtIndex:index];
58 | }
59 |
60 | - (NSEnumerator *)objectEnumerator {
61 | return [ordering objectEnumerator];
62 | }
63 |
64 | - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes {
65 | CHRaiseInvalidArgumentExceptionIfNil(indexes);
66 | if ([indexes count] == 0) {
67 | return @[];
68 | }
69 | CHRaiseIndexOutOfRangeExceptionIf([indexes lastIndex], >=, [self count]);
70 | NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[self count]];
71 | NSUInteger index = [indexes firstIndex];
72 | while (index != NSNotFound) {
73 | [objects addObject:[self objectAtIndex:index]];
74 | index = [indexes indexGreaterThanIndex:index];
75 | }
76 | return objects;
77 | }
78 |
79 | - (CHOrderedSet *)orderedSetWithObjectsAtIndexes:(NSIndexSet *)indexes {
80 | CHRaiseInvalidArgumentExceptionIfNil(indexes);
81 | if ([indexes count] == 0) {
82 | return [[self class] set];
83 | }
84 | CHOrderedSet *newSet = [[self class] setWithCapacity:[indexes count]];
85 | NSUInteger index = [indexes firstIndex];
86 | while (index != NSNotFound) {
87 | [newSet addObject:[ordering objectAtIndex:index]];
88 | index = [indexes indexGreaterThanIndex:index];
89 | }
90 | return newSet;
91 | }
92 |
93 | #pragma mark Modifying Contents
94 |
95 | - (void)addObject:(id)anObject {
96 | CHRaiseInvalidArgumentExceptionIfNil(anObject);
97 | if (![self containsObject:anObject]) {
98 | [ordering addObject:anObject];
99 | }
100 | [super addObject:anObject];
101 | }
102 |
103 | - (void)exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2 {
104 | [ordering exchangeObjectAtIndex:idx1 withObjectAtIndex:idx2];
105 | }
106 |
107 | - (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
108 | CHRaiseIndexOutOfRangeExceptionIf(index, >, [self count]);
109 | if ([self containsObject:anObject]) {
110 | [ordering removeObject:anObject];
111 | }
112 | [ordering insertObject:anObject atIndex:index];
113 | [super addObject:anObject];
114 | }
115 |
116 | - (void)removeAllObjects {
117 | [super removeAllObjects];
118 | [ordering removeAllObjects];
119 | }
120 |
121 | - (void)removeFirstObject {
122 | [self removeObject:[ordering firstObject]];
123 | }
124 |
125 | - (void)removeLastObject {
126 | [self removeObject:[ordering lastObject]];
127 | }
128 |
129 | - (void)removeObject:(id)anObject {
130 | [super removeObject:anObject];
131 | [ordering removeObject:anObject];
132 | }
133 |
134 | - (void)removeObjectAtIndex:(NSUInteger)index {
135 | [super removeObject:[ordering objectAtIndex:index]];
136 | [ordering removeObjectAtIndex:index];
137 | }
138 |
139 | - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
140 | [(NSMutableSet *)set minusSet:[NSSet setWithArray:[self objectsAtIndexes:indexes]]];
141 | [ordering removeObjectsAtIndexes:indexes];
142 | }
143 |
144 | #pragma mark
145 |
146 | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len {
147 | return [ordering countByEnumeratingWithState:state objects:stackbuf count:len];
148 | }
149 |
150 | @end
151 |
--------------------------------------------------------------------------------
/source/CHDoublyLinkedList.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHDoublyLinkedList.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | // Copyright © 2002, Phillip Morelock
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | /**
14 | @file CHDoublyLinkedList.h
15 | A standard doubly-linked list implementation with pointers to head and tail.
16 | */
17 |
18 | /** A struct for nodes in a CHDoublyLinkedList. */
19 | typedef struct CHDoublyLinkedListNode {
20 | __unsafe_unretained _Nullable id object; ///< The object associated with this node in the list.
21 | struct CHDoublyLinkedListNode *_Nullable next; ///< Next node in the list.
22 | struct CHDoublyLinkedListNode *_Nullable prev; ///< Previous node in the list.
23 | } CHDoublyLinkedListNode;
24 |
25 | #pragma mark -
26 |
27 | /**
28 | A standard doubly-linked list implementation with pointers to head and tail. The extra 'previous' link allows for reverse enumeration and cheaper removal for objects near the tail of the list. The tradeoff is a little extra storage in each list node and a little extra work when inserting and removing. Nodes are represented with C structs, providing much faster performance than Objective-C objects.
29 |
30 | The use of head and tail nodes allows for simplification of the algorithms for insertion and deletion, since the special cases of checking whether a node is the first or last in the list (and handling the next and previous pointers) are done away with. The figures below demonstrate what a doubly-linked list looks like when it contains 0 objects, 1 object, and 2 or more objects.
31 |
32 | @image html doubly-linked-0.png Figure 1 - Doubly-linked list with 0 objects.
33 |
34 | @image html doubly-linked-1.png Figure 2 - Doubly-linked list with 1 object.
35 |
36 | @image html doubly-linked-N.png Figure 3 - Doubly-linked list with 2+ objects.
37 |
38 | Just as with sentinel nodes used in binary search trees, the object pointer in the head and tail nodes can be nil or set to the value being searched for. This means there is no need to check whether the next node is null before moving on; just stop at the node whose object matches, then check after the match is found whether the node containing it was the head/tail or a valid internal node.
39 |
40 | The operations \link #insertObject:atIndex:\endlink and \link #removeObjectAtIndex:\endlink take advantage of the bi-directional links, and search from the closest possible point. To reduce code duplication, all methods that append or prepend objects call \link #insertObject:atIndex:\endlink, and the methods to remove the first or last objects use \link #removeObjectAtIndex:\endlink underneath.
41 |
42 | Doubly-linked lists are well-suited as an underlying collection for other data structures, such as a deque (double-ended queue) like the one declared in CHListDeque. The same functionality can be achieved using a circular buffer and an array, and many libraries choose to do so when objects are only added to or removed from the ends, but the dynamic structure of a linked list is much more flexible when inserting and deleting in the middle of a list.
43 | */
44 | @interface CHDoublyLinkedList<__covariant ObjectType> : NSObject
45 | {
46 | CHDoublyLinkedListNode *head; // Dummy node at the front of the list.
47 | CHDoublyLinkedListNode *tail; // Dummy node at the back of the list.
48 | CHDoublyLinkedListNode *cachedNode; // Pointer to last accessed node.
49 | NSUInteger cachedIndex; // Index of last accessed node.
50 | NSUInteger count; // The number of objects currently in the list.
51 | unsigned long mutations; // Tracks mutations for NSFastEnumeration.
52 | }
53 |
54 | - (instancetype)initWithArray:(NSArray *)array NS_DESIGNATED_INITIALIZER;
55 |
56 | /**
57 | Returns an enumerator that accesses each object in the receiver from back to front.
58 |
59 | @return An enumerator that accesses each object in the receiver from back to front. The enumerator returned is never @c nil; if the receiver is empty, the enumerator will always return @c nil for \link NSEnumerator#nextObject -nextObject\endlink and an empty array for \link NSEnumerator#allObjects -allObjects\endlink.
60 |
61 | @attention The enumerator retains the collection. Once all objects in the enumerator have been consumed, the collection is released.
62 | @warning Modifying a collection while it is being enumerated is unsafe, and may cause a mutation exception to be raised.
63 | */
64 | - (NSEnumerator *)reverseObjectEnumerator;
65 |
66 | @end
67 |
68 | NS_ASSUME_NONNULL_END
69 |
--------------------------------------------------------------------------------
/resources/CHDataStructuresFormatters.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | File Version
6 | 1
7 | CHAbstractBinarySearchTree *
8 |
9 | SummaryString
10 | {(int)[$VAR count]} objects
11 |
12 | CHAbstractListCollection *
13 |
14 | SummaryString
15 | {(int)[$VAR count]} objects
16 |
17 | CHAnderssonTree *
18 |
19 | SummaryString
20 | {(int)[$VAR count]} objects
21 |
22 | CHAVLTree *
23 |
24 | SummaryString
25 | {(int)[$VAR count]} objects
26 |
27 | CHBidirectionalDictionary *
28 |
29 | SummaryString
30 | {(int)[$VAR count]} objects
31 |
32 | CHBinaryTreeNode *
33 |
34 | SummaryString
35 | %object% (left={(*$VAR).left}, right={(*$VAR).right}) [{(long)(*$VAR).balance}]
36 |
37 | CHCircularBuffer *
38 |
39 | SummaryString
40 | {(int)[$VAR count]} objects
41 |
42 | CHCircularBufferDeque *
43 |
44 | SummaryString
45 | {(int)[$VAR count]} objects
46 |
47 | CHCircularBufferQueue *
48 |
49 | SummaryString
50 | {(int)[$VAR count]} objects
51 |
52 | CHCircularBufferStack *
53 |
54 | SummaryString
55 | {(int)[$VAR count]} objects
56 |
57 | CHDoublyLinkedList *
58 |
59 | SummaryString
60 | {(int)[$VAR count]} objects
61 |
62 | CHDoublyLinkedListNode *
63 |
64 | SummaryString
65 | %object% (prev=%prev%, next=%next%)
66 |
67 | CHListDeque *
68 |
69 | SummaryString
70 | {(int)[$VAR count]} objects
71 |
72 | CHListQueue *
73 |
74 | SummaryString
75 | {(int)[$VAR count]} objects
76 |
77 | CHListStack *
78 |
79 | SummaryString
80 | {(int)[$VAR count]} objects
81 |
82 | CHMultiDictionary *
83 |
84 | SummaryString
85 | {(int)[$VAR count]} keys, {(int)[$VAR countForAllKeys]} values
86 |
87 | CHMutableArrayHeap *
88 |
89 | SummaryString
90 | {(int)[$VAR count]} objects
91 |
92 | CHMutableDictionary *
93 |
94 | SummaryString
95 | {(int)[$VAR count]} key/value pairs
96 |
97 | CHMutableSet *
98 |
99 | SummaryString
100 | {(int)[$VAR count]} objects
101 |
102 | CHOrderedDictionary *
103 |
104 | SummaryString
105 | {(int)[$VAR count]} key/value pairs
106 |
107 | CHOrderedSet *
108 |
109 | SummaryString
110 | {(int)[$VAR count]} objects
111 |
112 | CHRedBlackTree *
113 |
114 | SummaryString
115 | {(int)[$VAR count]} objects
116 |
117 | CHSearchTreeHeaderObject *
118 |
119 | SummaryString
120 | (singleton dummy header object)
121 |
122 | CHSinglyLinkedList *
123 |
124 | SummaryString
125 | {(int)[$VAR count]} objects
126 |
127 | CHSinglyLinkedListNode *
128 |
129 | SummaryString
130 | %object% (next=%next%)
131 |
132 | CHSortedDictionary *
133 |
134 | SummaryString
135 | {(int)[$VAR count]} key/value pairs
136 |
137 | CHTreap *
138 |
139 | SummaryString
140 | {(int)[$VAR count]} objects
141 |
142 | CHUnbalancedTree *
143 |
144 | SummaryString
145 | {(int)[$VAR count]} objects
146 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/CHDataStructures.xcodeproj/xcshareddata/xcschemes/CHDataStructures.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
52 |
53 |
54 |
55 |
57 |
63 |
64 |
65 |
66 |
67 |
77 |
78 |
84 |
85 |
86 |
87 |
93 |
94 |
100 |
101 |
102 |
103 |
105 |
106 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/source/CHMutableDictionary.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHMutableDictionary.m
3 | // CHDataStructures
4 | //
5 | // Copyright © 2009-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | #pragma mark CFDictionary callbacks
11 |
12 | const void * CHDictionaryRetain(CFAllocatorRef allocator, const void *value) {
13 | return [(id)value retain];
14 | }
15 |
16 | void CHDictionaryRelease(CFAllocatorRef allocator, const void *value) {
17 | [(id)value release];
18 | }
19 |
20 | CFStringRef CHDictionaryCopyDescription(const void *value) {
21 | return (CFStringRef)[[(id)value description] copy];
22 | }
23 |
24 | Boolean CHDictionaryEqual(const void *value1, const void *value2) {
25 | return [(id)value1 isEqual:(id)value2];
26 | }
27 |
28 | CFHashCode CHDictionaryHash(const void *value) {
29 | return (CFHashCode)[(id)value hash];
30 | }
31 |
32 | static const CFDictionaryKeyCallBacks kCHDictionaryKeyCallBacks = {
33 | 0, // default version
34 | CHDictionaryRetain,
35 | CHDictionaryRelease,
36 | CHDictionaryCopyDescription,
37 | CHDictionaryEqual,
38 | CHDictionaryHash
39 | };
40 |
41 | static const CFDictionaryValueCallBacks kCHDictionaryValueCallBacks = {
42 | 0, // default version
43 | CHDictionaryRetain,
44 | CHDictionaryRelease,
45 | CHDictionaryCopyDescription,
46 | CHDictionaryEqual
47 | };
48 |
49 | HIDDEN CFMutableDictionaryRef CHDictionaryCreateMutable(NSUInteger initialCapacity)
50 | {
51 | // Create a CFMutableDictionaryRef with callback functions as defined above.
52 | return CFDictionaryCreateMutable(kCFAllocatorDefault,
53 | initialCapacity,
54 | &kCHDictionaryKeyCallBacks,
55 | &kCHDictionaryValueCallBacks);
56 | }
57 |
58 | #pragma mark -
59 |
60 | @implementation CHMutableDictionary
61 |
62 | - (void)dealloc {
63 | CFRelease(dictionary); // The dictionary will never be null at this point.
64 | [super dealloc];
65 | }
66 |
67 | // Note: Defined here since -init is not implemented in NS(Mutable)Dictionary.
68 | - (instancetype)init {
69 | return [self initWithCapacity:0]; // The 0 means we provide no capacity hint
70 | }
71 |
72 | // Note: This is the designated initializer for NSMutableDictionary and this class.
73 | // Subclasses may override this as necessary, but must call back here first.
74 | - (instancetype)initWithCapacity:(NSUInteger)capacity {
75 | self = [super init];
76 | if (self) {
77 | dictionary = CHDictionaryCreateMutable(capacity);
78 | }
79 | return self;
80 | }
81 |
82 | #pragma mark
83 |
84 | // Overridden from NSMutableDictionary to encode/decode as the proper class.
85 | - (Class)classForKeyedArchiver {
86 | return [self class];
87 | }
88 |
89 | - (instancetype)initWithCoder:(NSCoder *)decoder {
90 | return [self initWithDictionary:[decoder decodeObjectForKey:@"dictionary"]];
91 | }
92 |
93 | - (void)encodeWithCoder:(NSCoder *)encoder {
94 | [encoder encodeObject:(NSDictionary *)dictionary forKey:@"dictionary"];
95 | }
96 |
97 | #pragma mark
98 |
99 | - (instancetype)copyWithZone:(NSZone *) zone {
100 | // We could use -initWithDictionary: here, but it would just use more memory.
101 | // (It marshals key-value pairs into two id* arrays, then inits from those.)
102 | CHMutableDictionary *copy = [[[self class] allocWithZone:zone] init];
103 | [copy addEntriesFromDictionary:self];
104 | return copy;
105 | }
106 |
107 | #pragma mark
108 |
109 | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len {
110 | return [super countByEnumeratingWithState:state objects:stackbuf count:len];
111 | }
112 |
113 | #pragma mark Querying Contents
114 |
115 | - (NSUInteger)count {
116 | return CFDictionaryGetCount(dictionary);
117 | }
118 |
119 | - (NSString *)debugDescription {
120 | CFStringRef description = CFCopyDescription(dictionary);
121 | CFRelease([(id)description retain]);
122 | return [(id)description autorelease];
123 | }
124 |
125 | - (NSEnumerator *)keyEnumerator {
126 | return [(id)dictionary keyEnumerator];
127 | }
128 |
129 | - (NSEnumerator *)objectEnumerator {
130 | return [(id)dictionary objectEnumerator];
131 | }
132 |
133 | - (id)objectForKey:(id)aKey {
134 | CHRaiseInvalidArgumentExceptionIfNil(aKey);
135 | return (id)CFDictionaryGetValue(dictionary, aKey);
136 | }
137 |
138 | #pragma mark Modifying Contents
139 |
140 | - (void)removeAllObjects {
141 | CFDictionaryRemoveAllValues(dictionary);
142 | }
143 |
144 | - (void)removeObjectForKey:(id)aKey {
145 | CHRaiseInvalidArgumentExceptionIfNil(aKey);
146 | CFDictionaryRemoveValue(dictionary, aKey);
147 | }
148 |
149 | - (void)setObject:(id)anObject forKey:(id)aKey {
150 | CHRaiseInvalidArgumentExceptionIfNil(anObject);
151 | CHRaiseInvalidArgumentExceptionIfNil(aKey);
152 | CFDictionarySetValue(dictionary, [[aKey copy] autorelease], anObject);
153 | }
154 |
155 | @end
156 |
--------------------------------------------------------------------------------
/test/CHStackTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHStackTest.m
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 | #import
10 | #import
11 | #import
12 |
13 | @interface CHStackTest : XCTestCase {
14 | id stack;
15 | NSArray *objects, *stackOrder, *stackClasses;
16 | }
17 | @end
18 |
19 | @implementation CHStackTest
20 |
21 | - (void)setUp {
22 | stackClasses = @[
23 | [CHListStack class],
24 | [CHCircularBufferStack class],
25 | ];
26 | objects = @[@"A", @"B", @"C"];
27 | stackOrder = @[@"C", @"B", @"A"];
28 | }
29 |
30 | /*
31 | NOTE: Several methods in CHStack are tested in the abstract parent classes.
32 | -init
33 | -containsObject:
34 | -containsObjectIdenticalTo:
35 | -removeObject:
36 | -removeAllObjects
37 | -allObjects
38 | -count
39 | -objectEnumerator
40 | */
41 |
42 | - (void)testInitWithArray {
43 | NSMutableArray *moreObjects = [NSMutableArray array];
44 | for (NSUInteger i = 0; i < 32; i++) {
45 | [moreObjects addObject:@(i)];
46 | }
47 | for (Class aClass in stackClasses) {
48 | // Test initializing with nil and empty array parameters
49 | stack = nil;
50 | XCTAssertThrows([[[aClass alloc] initWithArray:nil] autorelease]);
51 | XCTAssertEqual([stack count], 0);
52 | // Test initializing with a valid, non-empty array
53 | stack = [[[aClass alloc] initWithArray:objects] autorelease];
54 | XCTAssertEqual([stack count], [objects count]);
55 | XCTAssertEqualObjects([stack allObjects], stackOrder);
56 | // Test initializing with an array larger than the default capacity
57 | stack = [[[aClass alloc] initWithArray:moreObjects] autorelease];
58 | XCTAssertEqual([stack count], [moreObjects count]);
59 | }
60 | }
61 |
62 | - (void)testIsEqualToStack {
63 | NSMutableArray *emptyStacks = [NSMutableArray array];
64 | NSMutableArray *equalStacks = [NSMutableArray array];
65 | NSMutableArray *reversedStacks = [NSMutableArray array];
66 | NSArray *reversedObjects = [[objects reverseObjectEnumerator] allObjects];
67 | for (Class aClass in stackClasses) {
68 | [emptyStacks addObject:[[aClass alloc] init]];
69 | [equalStacks addObject:[[aClass alloc] initWithArray:objects]];
70 | [reversedStacks addObject:[[aClass alloc] initWithArray:reversedObjects]];
71 | }
72 | // Add a repeat of the first class to avoid wrapping.
73 | [equalStacks addObject:[equalStacks objectAtIndex:0]];
74 |
75 | id stack1, stack2;
76 | for (NSUInteger i = 0; i < [stackClasses count]; i++) {
77 | stack1 = [equalStacks objectAtIndex:i];
78 | XCTAssertThrowsSpecificNamed([stack1 isEqualToStack:(id)[NSString string]],
79 | NSException, NSInvalidArgumentException);
80 | XCTAssertFalse([stack1 isEqual:[NSString string]]);
81 | XCTAssertEqualObjects(stack1, stack1);
82 | stack2 = [equalStacks objectAtIndex:i+1];
83 | XCTAssertEqualObjects(stack1, stack2);
84 | XCTAssertEqual([stack1 hash], [stack2 hash]);
85 | stack2 = [emptyStacks objectAtIndex:i];
86 | XCTAssertFalse([stack1 isEqual:stack2]);
87 | stack2 = [reversedStacks objectAtIndex:i];
88 | XCTAssertFalse([stack1 isEqual:stack2]);
89 | }
90 | }
91 |
92 | - (void)testPushObject {
93 | for (Class aClass in stackClasses) {
94 | stack = [[[aClass alloc] init] autorelease];
95 | XCTAssertThrows([stack pushObject:nil]);
96 |
97 | XCTAssertEqual([stack count], 0);
98 | for (id anObject in objects) {
99 | [stack pushObject:anObject];
100 | }
101 | XCTAssertEqual([stack count], [objects count]);
102 | }
103 | }
104 |
105 | - (void)testTopObjectAndPopObject {
106 | for (Class aClass in stackClasses) {
107 | stack = [[[aClass alloc] init] autorelease];
108 | // Test that the top object starts out as nil
109 | XCTAssertNil([stack topObject]);
110 | // Test that the top object is correct as objects are pushed
111 | for (id anObject in objects) {
112 | [stack pushObject:anObject];
113 | XCTAssertEqualObjects([stack topObject], anObject);
114 | }
115 | // Test that objects are popped in the correct order and count is right.
116 | NSUInteger expected = [objects count];
117 | XCTAssertEqualObjects([stack topObject], @"C");
118 | XCTAssertEqual([stack count], expected);
119 | [stack popObject];
120 | --expected;
121 | XCTAssertEqualObjects([stack topObject], @"B");
122 | XCTAssertEqual([stack count], expected);
123 | [stack popObject];
124 | --expected;
125 | XCTAssertEqualObjects([stack topObject], @"A");
126 | XCTAssertEqual([stack count], expected);
127 | [stack popObject];
128 | --expected;
129 | XCTAssertNil([stack topObject]);
130 | XCTAssertEqual([stack count], expected);
131 | // Test that popping an empty stack has no effect
132 | [stack popObject];
133 | XCTAssertEqual([stack count], expected);
134 | }
135 | }
136 |
137 | @end
138 |
--------------------------------------------------------------------------------
/source/CHOrderedDictionary.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHOrderedDictionary.m
3 | // CHDataStructures
4 | //
5 | // Copyright © 2009-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 | #import
10 |
11 | @implementation CHOrderedDictionary
12 |
13 | - (void)dealloc {
14 | [keyOrdering release];
15 | [super dealloc];
16 | }
17 |
18 | - (instancetype)initWithCapacity:(NSUInteger)numItems {
19 | self = [super initWithCapacity:numItems];
20 | if (self) {
21 | keyOrdering = [[CHCircularBuffer alloc] initWithCapacity:numItems];
22 | }
23 | return self;
24 | }
25 |
26 | - (instancetype)initWithCoder:(NSCoder *)decoder {
27 | self = [super initWithCoder:decoder];
28 | if (self) {
29 | [keyOrdering release];
30 | keyOrdering = [[decoder decodeObjectForKey:@"keyOrdering"] retain];
31 | }
32 | return self;
33 | }
34 |
35 | - (void)encodeWithCoder:(NSCoder *)encoder {
36 | [super encodeWithCoder:encoder];
37 | [encoder encodeObject:keyOrdering forKey:@"keyOrdering"];
38 | }
39 |
40 | #pragma mark
41 |
42 | /** @test Add unit test. */
43 | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len {
44 | return [keyOrdering countByEnumeratingWithState:state objects:stackbuf count:len];
45 | }
46 |
47 | #pragma mark Querying Contents
48 |
49 | - (id)firstKey {
50 | return [keyOrdering firstObject];
51 | }
52 |
53 | - (NSUInteger)hash {
54 | return [keyOrdering hash];
55 | }
56 |
57 | - (id)lastKey {
58 | return [keyOrdering lastObject];
59 | }
60 |
61 | - (NSUInteger)indexOfKey:(id)aKey {
62 | if (CFDictionaryContainsKey(dictionary, aKey)) {
63 | return [keyOrdering indexOfObject:aKey];
64 | } else {
65 | return NSNotFound;
66 | }
67 | }
68 |
69 | - (id)keyAtIndex:(NSUInteger)index {
70 | return [keyOrdering objectAtIndex:index];
71 | }
72 |
73 | - (NSArray *)keysAtIndexes:(NSIndexSet *)indexes {
74 | return [keyOrdering objectsAtIndexes:indexes];
75 | }
76 |
77 | - (NSEnumerator *)keyEnumerator {
78 | return [keyOrdering objectEnumerator];
79 | }
80 |
81 | - (id)objectForKeyAtIndex:(NSUInteger)index {
82 | // Note: -keyAtIndex: will raise an exception if the index is invalid.
83 | return [self objectForKey:[self keyAtIndex:index]];
84 | }
85 |
86 | - (NSArray *)objectsForKeysAtIndexes:(NSIndexSet *)indexes {
87 | return [self objectsForKeys:[self keysAtIndexes:indexes] notFoundMarker:self];
88 | }
89 |
90 | - (CHOrderedDictionary *)orderedDictionaryWithKeysAtIndexes:(NSIndexSet *)indexes {
91 | CHRaiseInvalidArgumentExceptionIfNil(indexes);
92 | if ([indexes count] == 0) {
93 | return [[self class] dictionary];
94 | }
95 | CHOrderedDictionary *newDictionary = [[self class] dictionaryWithCapacity:[indexes count]];
96 | NSUInteger index = [indexes firstIndex];
97 | while (index != NSNotFound) {
98 | id key = [self keyAtIndex:index];
99 | [newDictionary setObject:[self objectForKey:key] forKey:key];
100 | index = [indexes indexGreaterThanIndex:index];
101 | }
102 | return newDictionary;
103 | }
104 |
105 | - (NSEnumerator *)reverseKeyEnumerator {
106 | return [keyOrdering reverseObjectEnumerator];
107 | }
108 |
109 | #pragma mark Modifying Contents
110 |
111 | - (void)exchangeKeyAtIndex:(NSUInteger)idx1 withKeyAtIndex:(NSUInteger)idx2 {
112 | [keyOrdering exchangeObjectAtIndex:idx1 withObjectAtIndex:idx2];
113 | }
114 |
115 | - (void)insertObject:(id)anObject forKey:(id)aKey atIndex:(NSUInteger)index {
116 | CHRaiseInvalidArgumentExceptionIfNil(anObject);
117 | CHRaiseInvalidArgumentExceptionIfNil(aKey);
118 | CHRaiseIndexOutOfRangeExceptionIf(index, >, [self count]);
119 |
120 | id clonedKey = [[aKey copy] autorelease];
121 | if (!CFDictionaryContainsKey(dictionary, clonedKey)) {
122 | [keyOrdering insertObject:clonedKey atIndex:index];
123 | }
124 | CFDictionarySetValue(dictionary, clonedKey, anObject);
125 | }
126 |
127 | - (void)removeAllObjects {
128 | [super removeAllObjects];
129 | [keyOrdering removeAllObjects];
130 | }
131 |
132 | - (void)removeObjectForKey:(id)aKey {
133 | if (CFDictionaryContainsKey(dictionary, aKey)) {
134 | [super removeObjectForKey:aKey];
135 | [keyOrdering removeObject:aKey];
136 | }
137 | }
138 |
139 | - (void)removeObjectForKeyAtIndex:(NSUInteger)index {
140 | // Note: -keyAtIndex: will raise an exception if the index is invalid.
141 | [super removeObjectForKey:[self keyAtIndex:index]];
142 | [keyOrdering removeObjectAtIndex:index];
143 | }
144 |
145 | - (void)removeObjectsForKeysAtIndexes:(NSIndexSet *)indexes {
146 | NSArray *keysToRemove = [keyOrdering objectsAtIndexes:indexes];
147 | [keyOrdering removeObjectsAtIndexes:indexes];
148 | [(NSMutableDictionary *)dictionary removeObjectsForKeys:keysToRemove];
149 | }
150 |
151 | - (void)setObject:(id)anObject forKey:(id)aKey {
152 | [self insertObject:anObject forKey:aKey atIndex:[self count]];
153 | }
154 |
155 | - (void)setObject:(id)anObject forKeyAtIndex:(NSUInteger)index {
156 | [self insertObject:anObject forKey:[self keyAtIndex:index] atIndex:index];
157 | }
158 |
159 | @end
160 |
--------------------------------------------------------------------------------
/test/CHQueueTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHQueueTest.m
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 | #import
10 | #import
11 |
12 | @interface CHQueueTest : XCTestCase {
13 | id queue;
14 | NSArray *objects, *queueClasses;
15 | }
16 | @end
17 |
18 | @implementation CHQueueTest
19 |
20 | - (void)setUp {
21 | queueClasses = @[
22 | [CHListQueue class],
23 | [CHCircularBufferQueue class],
24 | ];
25 | objects = @[@"A",@"B",@"C"];
26 | }
27 |
28 | - (void)testInitWithArray {
29 | NSMutableArray *moreObjects = [NSMutableArray array];
30 | for (NSUInteger i = 0; i < 32; i++) {
31 | [moreObjects addObject:@(i)];
32 | }
33 |
34 | NSEnumerator *classes = [queueClasses objectEnumerator];
35 | Class aClass;
36 | while (aClass = [classes nextObject]) {
37 | // Test initializing with nil and empty array parameters
38 | queue = nil;
39 | XCTAssertThrows(queue = [[[aClass alloc] initWithArray:nil] autorelease], @"%@", aClass);
40 | XCTAssertEqual([queue count], 0);
41 | queue = [[[aClass alloc] initWithArray:@[]] autorelease];
42 | XCTAssertEqual([queue count], 0);
43 | // Test initializing with a valid, non-empty array
44 | queue = [[[aClass alloc] initWithArray:objects] autorelease];
45 | XCTAssertEqual([queue count], [objects count]);
46 | XCTAssertEqualObjects([queue allObjects], objects);
47 | // Test initializing with an array larger than the default capacity
48 | queue = [[[aClass alloc] initWithArray:moreObjects] autorelease];
49 | XCTAssertEqual([queue count], [moreObjects count]);
50 | XCTAssertEqualObjects([queue allObjects], moreObjects);
51 | }
52 | }
53 |
54 | - (void)testIsEqualToQueue {
55 | NSMutableArray *emptyQueues = [NSMutableArray array];
56 | NSMutableArray *equalQueues = [NSMutableArray array];
57 | NSMutableArray *reversedQueues = [NSMutableArray array];
58 | NSArray *reversedObjects = [[objects reverseObjectEnumerator] allObjects];
59 | NSEnumerator *classes = [queueClasses objectEnumerator];
60 | Class aClass;
61 | while (aClass = [classes nextObject]) {
62 | [emptyQueues addObject:[[aClass alloc] init]];
63 | [equalQueues addObject:[[aClass alloc] initWithArray:objects]];
64 | [reversedQueues addObject:[[aClass alloc] initWithArray:reversedObjects]];
65 | }
66 | // Add a repeat of the first class to avoid wrapping.
67 | [equalQueues addObject:[equalQueues objectAtIndex:0]];
68 |
69 | id queue1, queue2;
70 | for (NSUInteger i = 0; i < [queueClasses count]; i++) {
71 | queue1 = [equalQueues objectAtIndex:i];
72 | XCTAssertThrowsSpecificNamed([queue1 isEqualToQueue:(id)[NSString string]], NSException, NSInvalidArgumentException);
73 | XCTAssertFalse([queue1 isEqual:[NSString string]]);
74 | XCTAssertEqualObjects(queue1, queue1);
75 | queue2 = [equalQueues objectAtIndex:i+1];
76 | XCTAssertEqualObjects(queue1, queue2);
77 | XCTAssertEqual([queue1 hash], [queue2 hash]);
78 | queue2 = [emptyQueues objectAtIndex:i];
79 | XCTAssertFalse([queue1 isEqual:queue2]);
80 | queue2 = [reversedQueues objectAtIndex:i];
81 | XCTAssertFalse([queue1 isEqual:queue2]);
82 | }
83 | }
84 |
85 | - (void)testAddObject {
86 | NSEnumerator *classes = [queueClasses objectEnumerator];
87 | Class aClass;
88 | while (aClass = [classes nextObject]) {
89 | queue = [[[aClass alloc] init] autorelease];
90 | // Test that adding a nil parameter raises an exception
91 | XCTAssertThrows([queue addObject:nil]);
92 | XCTAssertEqual([queue count], 0);
93 | // Test adding objects one by one and verify count and ordering
94 | for (id anObject in objects) {
95 | [queue addObject:anObject];
96 | }
97 | XCTAssertEqual([queue count], [objects count]);
98 | XCTAssertEqualObjects([queue allObjects], objects);
99 | }
100 | }
101 |
102 | - (void)testRemoveFirstObject {
103 | NSEnumerator *classes = [queueClasses objectEnumerator];
104 | Class aClass;
105 | while (aClass = [classes nextObject]) {
106 | queue = [[[aClass alloc] init] autorelease];
107 | for (id anObject in objects) {
108 | [queue addObject:anObject];
109 | XCTAssertEqualObjects([queue lastObject], anObject);
110 | }
111 | NSUInteger expected = [objects count];
112 | XCTAssertEqual([queue count], expected);
113 | XCTAssertEqualObjects([queue firstObject], @"A");
114 | XCTAssertEqualObjects([queue lastObject], @"C");
115 | [queue removeFirstObject];
116 | --expected;
117 | XCTAssertEqual([queue count], expected);
118 | XCTAssertEqualObjects([queue firstObject], @"B");
119 | XCTAssertEqualObjects([queue lastObject], @"C");
120 | [queue removeFirstObject];
121 | --expected;
122 | XCTAssertEqual([queue count], expected);
123 | XCTAssertEqualObjects([queue firstObject], @"C");
124 | XCTAssertEqualObjects([queue lastObject], @"C");
125 | [queue removeFirstObject];
126 | --expected;
127 | XCTAssertEqual([queue count], expected);
128 | XCTAssertNil([queue firstObject]);
129 | XCTAssertNil([queue lastObject]);
130 | XCTAssertNoThrow([queue removeFirstObject]);
131 | XCTAssertEqual([queue count], expected);
132 | XCTAssertNil([queue firstObject]);
133 | XCTAssertNil([queue lastObject]);
134 | }
135 | }
136 |
137 | @end
138 |
--------------------------------------------------------------------------------
/source/CHAbstractBinarySearchTree_Internal.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHAbstractBinarySearchTree_Internal.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | /**
13 | @file CHAbstractBinarySearchTree_Internal.h
14 | Contains \#defines for performing various traversals of binary search trees.
15 |
16 | This file is a private header that is only used by internal implementations, and is not included in the the compiled framework. The macros and variables are to be considered private and unsupported.
17 |
18 | Memory for stacks and queues is (re)allocated using NSScannedOption, since (if garbage collection is enabled) the nodes which may be placed in a stack or queue are known to the garbage collector. (If garbage collection is @b not enabled, the macros explicitly free the allocated memory.) We assume that a stack or queue will not outlive the nodes it contains, since they are only used in connection with an active tree (usually during insertion, removal or iteration). An enumerator may contain a stack or queue, but also retains the underlying collection, so correct retain-release calls will not leak.
19 | */
20 |
21 | @interface CHAbstractBinarySearchTree ()
22 |
23 | /**
24 | Convenience method for allocating a new CHBinaryTreeNode. This centralizes the allocation so all subclasses can be sure they're allocating nodes correctly. Explicitly sets the "extra" field used by self-balancing trees to zero. Also sets both @c left and @c right to the value of @c sentinel.
25 |
26 | @param object The value to be stored in the @a object field of the struct; may be @c nil.
27 | @return An struct allocated with @c malloc().
28 | */
29 | - (CHBinaryTreeNode *)_createNodeWithObject:(nullable id)object;
30 |
31 | // NOTE: Subclasses should override the following methods to display any algorithm-specific information (such as the extra field used by self-balancing trees) in debugging output and generated DOT graphs.
32 |
33 | // This method determines the appearance of nodes in the graph produced by -debugDescription, and may be overriden by subclasses. The default implementation returns the -description for the object in the node, surrounded by quote marks.
34 | - (NSString *)debugDescriptionForNode:(CHBinaryTreeNode *)node;
35 |
36 | // This method determines the appearance of nodes in the graph produced by -dotGraphString, and may be overriden by subclasses. The default implementation creates an oval containing the value returned by -description for the object in the node.
37 | - (NSString *)dotGraphStringForNode:(CHBinaryTreeNode *)node;
38 |
39 | @end
40 |
41 | #pragma mark -
42 |
43 | // These are used by subclasses; marked as HIDDEN to reduce external visibility.
44 | HIDDEN FOUNDATION_EXTERN size_t kCHBinaryTreeNodeSize;
45 |
46 | #pragma mark Stack macros
47 |
48 | #define CHBinaryTreeStack_DECLARE() \
49 | __strong CHBinaryTreeNode** stack; \
50 | NSUInteger stackCapacity, stackSize
51 |
52 | #define CHBinaryTreeStack_INIT() { \
53 | stackCapacity = 32; \
54 | stack = malloc(kCHBinaryTreeNodeSize * stackCapacity); \
55 | stackSize = 0; \
56 | }
57 |
58 | #define CHBinaryTreeStack_FREE(stack) { \
59 | free(stack); \
60 | stack = NULL; \
61 | }
62 |
63 | // Since this stack starts at 0 and goes to N-1, resizing is pretty simple.
64 | #define CHBinaryTreeStack_PUSH(node) { \
65 | stack[stackSize++] = node; \
66 | if (stackSize >= stackCapacity) { \
67 | stackCapacity *= 2; \
68 | stack = realloc(stack, kCHPointerSize * stackCapacity); \
69 | } \
70 | }
71 |
72 | #define CHBinaryTreeStack_TOP \
73 | ((stackSize > 0) ? stack[stackSize-1] : NULL)
74 |
75 | #define CHBinaryTreeStack_POP() \
76 | ((stackSize > 0) ? stack[--stackSize] : NULL)
77 |
78 |
79 | #pragma mark Queue macros
80 |
81 | #define CHBinaryTreeQueue_DECLARE() \
82 | __strong CHBinaryTreeNode** queue; \
83 | NSUInteger queueCapacity, queueHead, queueTail
84 |
85 | #define CHBinaryTreeQueue_INIT() { \
86 | queueCapacity = 128; \
87 | queue = malloc(kCHPointerSize * queueCapacity); \
88 | queueHead = queueTail = 0; \
89 | }
90 |
91 | #define CHBinaryTreeQueue_FREE(queue) { \
92 | free(queue); \
93 | queue = NULL; \
94 | }
95 |
96 | // This queue is a circular array, so resizing it takes a little extra care.
97 | #define CHBinaryTreeQueue_ENQUEUE(node) { \
98 | queue[queueTail++] = node; \
99 | queueTail %= queueCapacity; \
100 | if (queueHead == queueTail) { \
101 | queue = realloc(queue, kCHPointerSize*queueCapacity * 2); \
102 | /* Copy wrapped-around portion to end of queue and move tail index */ \
103 | memmove(queue + queueCapacity, queue, kCHPointerSize * queueTail); \
104 | /* Zeroing out shifted memory can simplify debugging queue problems */ \
105 | /*bzero(queue, kCHPointerSize*queueTail);*/ \
106 | queueTail += queueCapacity; \
107 | queueCapacity *= 2; \
108 | } \
109 | }
110 |
111 | // Due to limitations of using macros, you must always call FRONT, then DEQUEUE.
112 | #define CHBinaryTreeQueue_FRONT \
113 | ((queueHead != queueTail) ? queue[queueHead] : NULL)
114 |
115 | #define CHBinaryTreeQueue_DEQUEUE() \
116 | if (queueHead != queueTail) { \
117 | queueHead = (queueHead + 1) % queueCapacity; \
118 | } \
119 |
120 | NS_ASSUME_NONNULL_END
121 |
--------------------------------------------------------------------------------
/source/CHTreap.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHTreap.m
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 | #import "CHAbstractBinarySearchTree_Internal.h"
10 |
11 | @implementation CHTreap
12 |
13 | // Two-way single rotation; 'dir' is the side to which the root should rotate.
14 | #define singleRotation(node,dir,parent) { \
15 | CHBinaryTreeNode *save = node->link[!dir]; \
16 | node->link[!dir] = save->link[dir]; \
17 | save->link[dir] = node; \
18 | parent->link[(parent->right == node)] = save; \
19 | }
20 |
21 | - (void)_subclassSetup {
22 | header->priority = CHTreapNotFound; // This is the highest possible priority
23 | }
24 |
25 | - (void)addObject:(id)anObject {
26 | [self addObject:anObject withPriority:arc4random()];
27 | }
28 |
29 | - (void)addObject:(id)anObject withPriority:(NSUInteger)priority {
30 | CHRaiseInvalidArgumentExceptionIfNil(anObject);
31 | ++mutations;
32 |
33 | CHBinaryTreeNode *parent, *current = header;
34 | CHBinaryTreeStack_DECLARE();
35 | CHBinaryTreeStack_INIT();
36 |
37 | sentinel->object = anObject; // Assure that we find a spot to insert
38 | NSComparisonResult comparison;
39 | while ((comparison = [current->object compare:anObject])) {
40 | CHBinaryTreeStack_PUSH(current);
41 | current = current->link[comparison == NSOrderedAscending]; // R on YES
42 | }
43 | parent = CHBinaryTreeStack_POP();
44 | NSAssert(parent != nil, @"Illegal state, parent should never be nil!");
45 |
46 | [anObject retain]; // Must retain whether replacing value or adding new node
47 | u_int32_t direction;
48 | if (current != sentinel) {
49 | // Replace the existing object with the new object.
50 | [current->object release];
51 | current->object = anObject;
52 | // Assign new priority; bubble down if needed, or just wait to bubble up
53 | current->priority = (u_int32_t) (priority % CHTreapNotFound);
54 | while (current->left != current->right) { // sentinel check
55 | direction = (current->right->priority > current->left->priority);
56 | if (current->priority >= current->link[direction]->priority) {
57 | break;
58 | }
59 | NSAssert(parent != nil, @"Illegal state, parent should never be nil!");
60 | singleRotation(current, !direction, parent);
61 | parent = current;
62 | current = current->link[!direction];
63 | }
64 | } else {
65 | current = [self _createNodeWithObject:anObject];
66 | current->priority = (u_int32_t) (priority % CHTreapNotFound);
67 | ++count;
68 | // Link from parent as the correct child, based on the last comparison
69 | comparison = [parent->object compare:anObject];
70 | parent->link[comparison == NSOrderedAscending] = current; // R if YES
71 | }
72 |
73 | // Trace back up the path, rotating as we go to satisfy the heap property.
74 | // Loop exits once the heap property is satisfied, even after bubble down.
75 | while (parent != header && current->priority > parent->priority) {
76 | // Rotate current node up, push parent down to opposite subtree.
77 | direction = (parent->left == current);
78 | NSAssert(parent != nil, @"Illegal state, parent should never be nil!");
79 | NSAssert(stackSize > 0, @"Illegal state, stack should never be empty!");
80 | singleRotation(parent, direction, CHBinaryTreeStack_TOP);
81 | parent = CHBinaryTreeStack_POP();
82 | }
83 | CHBinaryTreeStack_FREE(stack);
84 | }
85 |
86 | - (void)removeObject:(id)anObject {
87 | CHRaiseInvalidArgumentExceptionIfNil(anObject);
88 | if (count == 0) {
89 | return;
90 | }
91 | ++mutations;
92 |
93 | CHBinaryTreeNode *parent = nil, *current = header;
94 | NSComparisonResult comparison;
95 | u_int32_t direction;
96 |
97 | // First, we must locate the object to be removed, or we exit if not found
98 | sentinel->object = anObject; // Assure that we stop at a sentinel leaf node
99 | while ((comparison = [current->object compare:anObject])) {
100 | parent = current;
101 | current = current->link[comparison == NSOrderedAscending]; // R on YES
102 | }
103 | NSAssert(parent != nil, @"Illegal state, parent should never be nil!");
104 |
105 | if (current != sentinel) {
106 | // Percolate node down the tree, always rotating towards lower priority
107 | BOOL isRightChild;
108 | while (current->left != current->right) { // sentinel check
109 | direction = (current->right->priority > current->left->priority);
110 | isRightChild = (parent->right == current);
111 | singleRotation(current, !direction, parent);
112 | parent = parent->link[isRightChild];
113 | }
114 | // NSAssert(parent != nil, @"Illegal state, parent should never be nil!");
115 | parent->link[parent->right == current] = sentinel;
116 | [current->object release];
117 | free(current);
118 | --count;
119 | }
120 | }
121 |
122 | - (NSUInteger)priorityForObject:(id)anObject {
123 | CHRaiseInvalidArgumentExceptionIfNil(anObject);
124 | sentinel->object = anObject; // Make sure the target value is always "found"
125 | CHBinaryTreeNode *current = header->right;
126 | NSComparisonResult comparison;
127 | while ((comparison = [current->object compare:anObject])) {
128 | current = current->link[comparison == NSOrderedAscending]; // R on YES
129 | }
130 | return (current != sentinel) ? current->priority : CHTreapNotFound;
131 | }
132 |
133 | - (NSString *)debugDescriptionForNode:(CHBinaryTreeNode *)node {
134 | return [NSString stringWithFormat:@"[%11d]\t\"%@\"",
135 | node->priority, node->object];
136 | }
137 |
138 | - (NSString *)dotGraphStringForNode:(CHBinaryTreeNode *)node {
139 | return [NSString stringWithFormat:@" \"%@\" [label=\"%@\\n%d\"];\n",
140 | node->object, node->object, node->priority];
141 | }
142 |
143 | @end
144 |
--------------------------------------------------------------------------------
/source/CHUtil.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHUtil.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | /**
11 | @file CHUtil.h
12 | A group of utility C functions for simplifying common exceptions and logging.
13 | */
14 |
15 | /** Macro for reducing visibility of symbol names not indended to be exported. */
16 | #define HIDDEN __attribute__((visibility("hidden")))
17 |
18 | /** Macro for designating symbols as being unused to suppress compile warnings. */
19 | #define UNUSED __attribute__((unused))
20 |
21 | #pragma mark -
22 |
23 | /** Global variable to store the size of a pointer only once. */
24 | OBJC_EXPORT size_t kCHPointerSize;
25 |
26 | typedef BOOL(*CHObjectEqualityTest)(id,id);
27 |
28 | /**
29 | Simple function for checking object equality, to be used as a function pointer.
30 |
31 | @param o1 The first object to be compared.
32 | @param o2 The second object to be compared.
33 | @return [o1 isEqual:o2]
34 | */
35 | HIDDEN BOOL CHObjectsAreEqual(id o1, id o2);
36 |
37 | /**
38 | Simple function for checking object identity, to be used as a function pointer.
39 |
40 | @param o1 The first object to be compared.
41 | @param o2 The second object to be compared.
42 | @return o1 == o2
43 | */
44 | HIDDEN BOOL CHObjectsAreIdentical(id o1, id o2);
45 |
46 | /**
47 | Determine whether two collections enumerate the equivalent objects in the same order.
48 |
49 | @param collection1 The first collection to be compared.
50 | @param collection2 The second collection to be compared.
51 | @return Whether the collections are equivalent.
52 |
53 | @throw NSInvalidArgumentException if one of both of the arguments do not respond to the @c -count or @c -objectEnumerator selectors.
54 | */
55 | OBJC_EXPORT BOOL CHCollectionsAreEqual(id collection1, id collection2);
56 |
57 | /**
58 | Generate a hash for a collection based on the count and up to two objects. If objects are provided, the result of their -hash method will be used.
59 |
60 | @param count The number of objects in the collection.
61 | @param o1 The first object to include in the hash.
62 | @param o2 The second object to include in the hash.
63 | @return An unsigned integer that can be used as a table address in a hash table structure.
64 | */
65 | HIDDEN NSUInteger CHHashOfCountAndObjects(NSUInteger count, id o1, id o2);
66 |
67 | #pragma mark -
68 |
69 | /**
70 | Convenience macro for raising an exception for an invalid index.
71 | */
72 | #define CHRaiseIndexOutOfRangeExceptionIf(a, comparison, b) \
73 | ({ \
74 | NSUInteger aValue = (a); \
75 | NSUInteger bValue = (b); \
76 | if (aValue comparison bValue) { \
77 | [NSException raise:NSRangeException \
78 | format:@"%s -- Index out of range: %s (%lu) %s %s (%lu)", \
79 | __PRETTY_FUNCTION__, #a, aValue, #comparison, #b, bValue]; \
80 | } \
81 | })
82 |
83 | /**
84 | Convenience macro for raising an exception on an invalid argument.
85 | */
86 | #define CHRaiseInvalidArgumentException(str) \
87 | [NSException raise:NSInvalidArgumentException \
88 | format:@"%s -- %@", \
89 | __PRETTY_FUNCTION__, str]
90 |
91 | /**
92 | Convenience macro for raising an exception on an invalid nil argument.
93 |
94 | */
95 | #define CHRaiseInvalidArgumentExceptionIfNil(argument) \
96 | if (argument == nil) { \
97 | CHRaiseInvalidArgumentException(@"Invalid nil value: " @#argument); \
98 | }
99 |
100 | /**
101 | Convenience macro for raising an exception when a collection is mutated.
102 | */
103 | #define CHRaiseMutatedCollectionException() \
104 | [NSException raise:NSGenericException \
105 | format:@"%s -- Collection was mutated during enumeration", \
106 | __PRETTY_FUNCTION__]
107 |
108 | /**
109 | Convenience macro for raising an exception for unsupported operations.
110 | */
111 | #define CHRaiseUnsupportedOperationException() \
112 | [NSException raise:NSInternalInconsistencyException \
113 | format:@"%s -- Unsupported operation", \
114 | __PRETTY_FUNCTION__]
115 |
116 | /**
117 | Provides a more terse alternative to NSLog() which accepts the same parameters. The output is made shorter by excluding the date stamp and process information which NSLog prints before the actual specified output.
118 |
119 | @param format A format string, which must not be nil.
120 | @param ... A comma-separated list of arguments to substitute into @a format.
121 |
122 | Read Formatting String Objects and String Format Specifiers on this webpage for details about using format strings. Look for examples that use @c NSLog() since the parameters and syntax are idential.
123 | */
124 | OBJC_EXPORT void CHQuietLog(NSString *format, ...);
125 |
126 | /**
127 | A macro for including the source file and line number where a log occurred.
128 |
129 | @param format A format string, which must not be nil.
130 | @param ... A comma-separated list of arguments to substitute into @a format.
131 |
132 | This is defined as a compiler macro so it can automatically fill in the file name and line number where the call was made. After printing these values in brackets, this macro calls #CHQuietLog with @a format and any other arguments supplied afterward.
133 |
134 | @see CHQuietLog
135 | */
136 | #ifndef CHLocationLog
137 | #define CHLocationLog(format,...) \
138 | { \
139 | NSString *file = [[NSString alloc] initWithUTF8String:__FILE__]; \
140 | printf("[%s:%d] ", [[file lastPathComponent] UTF8String], __LINE__); \
141 | [file release]; \
142 | CHQuietLog((format),##__VA_ARGS__); \
143 | }
144 | #endif
145 |
--------------------------------------------------------------------------------
/source/CHAnderssonTree.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHAnderssonTree.m
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 | #import "CHAbstractBinarySearchTree_Internal.h"
10 |
11 | // Remove left horizontal links
12 | #define skew(node) { \
13 | if (node->left->level == node->level && node->level != 0) { \
14 | CHBinaryTreeNode *save = node->left; \
15 | node->left = save->right; \
16 | save->right = node; \
17 | node = save; \
18 | } \
19 | }
20 |
21 | // Remove consecutive horizontal links
22 | #define split(node) { \
23 | if (node->right->right->level == node->level && node->level != 0) { \
24 | CHBinaryTreeNode *save = node->right; \
25 | node->right = save->left; \
26 | save->left = node; \
27 | node = save; \
28 | ++(node->level); \
29 | } \
30 | }
31 |
32 | #pragma mark -
33 |
34 | @implementation CHAnderssonTree
35 |
36 | // NOTE: The header and sentinel nodes are initialized to level 0 by default.
37 |
38 | - (void)addObject:(id)anObject {
39 | CHRaiseInvalidArgumentExceptionIfNil(anObject);
40 | ++mutations;
41 |
42 | CHBinaryTreeNode *parent, *current = header;
43 | CHBinaryTreeStack_DECLARE();
44 | CHBinaryTreeStack_INIT();
45 |
46 | sentinel->object = anObject; // Assure that we find a spot to insert
47 | NSComparisonResult comparison;
48 | while ((comparison = [current->object compare:anObject])) {
49 | CHBinaryTreeStack_PUSH(current);
50 | current = current->link[comparison == NSOrderedAscending]; // R on YES
51 | }
52 |
53 | [anObject retain]; // Must retain whether replacing value or adding new node
54 | if (current != sentinel) {
55 | // Replace the existing object with the new object.
56 | [current->object release];
57 | current->object = anObject;
58 | // No need to rebalance up the path since we didn't modify the structure
59 | goto done;
60 | } else {
61 | current = [self _createNodeWithObject:anObject];
62 | current->level = 1;
63 | ++count;
64 | // Link from parent as the proper child, based on last comparison
65 | parent = CHBinaryTreeStack_POP();
66 | NSAssert(parent != nil, @"Illegal state, parent should never be nil!");
67 | comparison = [parent->object compare:anObject];
68 | parent->link[comparison == NSOrderedAscending] = current; // R if YES
69 | }
70 |
71 | // Trace back up the path, rebalancing as we go
72 | BOOL isRightChild;
73 | while (parent != NULL) {
74 | isRightChild = (parent->right == current);
75 | skew(current);
76 | split(current);
77 | parent->link[isRightChild] = current;
78 | // Move to the next node up the path to the root
79 | current = parent;
80 | parent = CHBinaryTreeStack_POP();
81 | }
82 | done:
83 | CHBinaryTreeStack_FREE(stack);
84 | }
85 |
86 | - (void)removeObject:(id)anObject {
87 | CHRaiseInvalidArgumentExceptionIfNil(anObject);
88 | if (count == 0) {
89 | return;
90 | }
91 | ++mutations;
92 |
93 | CHBinaryTreeNode *parent, *current = header;
94 | CHBinaryTreeStack_DECLARE();
95 | CHBinaryTreeStack_INIT();
96 |
97 | sentinel->object = anObject; // Assure that we stop at a leaf if not found.
98 | NSComparisonResult comparison;
99 | while ((comparison = [current->object compare:anObject])) {
100 | CHBinaryTreeStack_PUSH(current);
101 | current = current->link[comparison == NSOrderedAscending]; // R on YES
102 | }
103 | // Exit if the specified node was not found in the tree.
104 | if (current == sentinel) {
105 | goto done;
106 | }
107 |
108 | [current->object release]; // Object must be released in any case
109 | --count;
110 | if (current->left == sentinel || current->right == sentinel) {
111 | // Single/zero child case -- replace node with non-nil child (if exists)
112 | parent = CHBinaryTreeStack_TOP;
113 | NSAssert(parent != nil, @"Illegal state, parent should never be nil!");
114 | parent->link[parent->right == current] = current->link[current->left == sentinel];
115 | free(current);
116 | } else {
117 | // Two child case -- replace with minimum object in right subtree
118 | CHBinaryTreeStack_PUSH(current); // Need to start here when rebalancing
119 | CHBinaryTreeNode *replacement = current->right;
120 | while (replacement->left != sentinel) {
121 | CHBinaryTreeStack_PUSH(replacement);
122 | replacement = replacement->left;
123 | }
124 | parent = CHBinaryTreeStack_TOP;
125 | // Grab object from replacement node, steal its right child, deallocate
126 | current->object = replacement->object;
127 | parent->link[parent->right == replacement] = replacement->right;
128 | free(replacement);
129 | }
130 |
131 | // Walk back up the path and rebalance as we go
132 | // Note that 'parent' always has the correct value coming into the loop
133 | BOOL isRightChild;
134 | while (current != NULL && stackSize > 1) {
135 | current = parent;
136 | (void)CHBinaryTreeStack_POP();
137 | parent = CHBinaryTreeStack_TOP;
138 | isRightChild = (parent->right == current);
139 |
140 | if (current->left->level < current->level-1 ||
141 | current->right->level < current->level-1)
142 | {
143 | if (current->right->level > --(current->level)) {
144 | current->right->level = current->level;
145 | }
146 | skew(current);
147 | skew(current->right);
148 | skew(current->right->right);
149 | split(current);
150 | split(current->right);
151 | }
152 | parent->link[isRightChild] = current;
153 | }
154 | done:
155 | CHBinaryTreeStack_FREE(stack);
156 | }
157 |
158 | - (NSString *)debugDescriptionForNode:(CHBinaryTreeNode *)node {
159 | return [NSString stringWithFormat:@"[%d]\t\"%@\"", node->level, node->object];
160 | }
161 |
162 | - (NSString *)dotGraphStringForNode:(CHBinaryTreeNode *)node {
163 | return [NSString stringWithFormat:@" \"%@\" [label=\"%@\\n%d\"];\n",
164 | node->object, node->object, node->level];
165 | }
166 |
167 | @end
168 |
--------------------------------------------------------------------------------
/tools/strip_comments.c:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | typedef enum {
5 | SOURCE,
6 | STRING_LITERAL,
7 | CHAR_LITERAL,
8 | SLASH,
9 | SLASH_STAR,
10 | COMMENT_LINE,
11 | COMMENT_CSTYLE,
12 | COMMENT_JAVADOC,
13 | COMMENT_HEADERDOC
14 | } State;
15 |
16 | #define YES 1
17 | #define NO 0
18 |
19 | int main (int argc, const char *argv[]) {
20 | char *inputFile = 0;
21 | int stripLine = 0, stripCStyle = 0, stripJavadoc = 0, stripHeaderDoc = 0;
22 | unsigned errors = 0;
23 |
24 | #pragma mark Process command-line options
25 |
26 | static struct option long_options[] = {
27 | {"line", no_argument, 0, 'L'},
28 | {"cstyle", no_argument, 0, 'C'},
29 | {"javadoc", no_argument, 0, 'J'},
30 | {"headerdoc", no_argument, 0, 'H'},
31 | {"input", required_argument, 0, 'i'},
32 | {"help", no_argument, 0, 'h'},
33 | {0, 0, 0, 0}
34 | };
35 |
36 | int option_index = 0; // getopt_long() stores the option index here
37 | char option; // the short character for the last processed option
38 | while ((option = getopt_long(argc, (char**)argv, "LCJHi:h",
39 | long_options, &option_index)) != -1)
40 | {
41 | if (option == 'L')
42 | stripLine = YES;
43 | else if (option == 'C')
44 | stripCStyle = YES;
45 | else if (option == 'J')
46 | stripJavadoc = YES;
47 | else if (option == 'H')
48 | stripHeaderDoc = YES;
49 | else if (option == 'i')
50 | inputFile = optarg;
51 | else if (option == 'h')
52 | errors++; // Will cause help to print, then exit before processing
53 | }
54 |
55 | #pragma mark Handle any options errors
56 |
57 | if (stripLine + stripCStyle + stripJavadoc + stripHeaderDoc == 0) {
58 | printf(" ERROR: Must specify at least one comment style.\n");
59 | printf(" (Options include -L, -C, -J, and -H.)\n");
60 | errors++;
61 | }
62 | if (inputFile == NULL) {
63 | printf(" ERROR: Must specify input file to process.\n");
64 | errors++;
65 | }
66 | if (optind < argc) {
67 | printf(" ERROR: Invalid non-option arguments:");
68 | while (optind < argc)
69 | printf(" `%s'", argv[optind++]);
70 | printf("\n");
71 | errors++;
72 | }
73 |
74 | if (errors > 0) {
75 | printf("\nusage: StripComments [options] --input file\n\n");
76 | printf(" Utility for stripping comments from source code. An input\n");
77 | printf(" file must be specified. If an output file is not specified,\n");
78 | printf(" output is printed to standard output.\n\n");
79 | printf("Valid options:\n");
80 | printf(" -L [--line] : Strip single-line comments //...\\n\n");
81 | printf(" -C [--cstyle] : Strip C-style comments /*...*/\n");
82 | printf(" -J [--javadoc] : Strip Javadoc comments /**...*/\n");
83 | printf(" -H [--headerdoc] : Strip HeaderDoc comments /*!...*/\n");
84 | printf(" -i [--input] : File from which to read input\n");
85 | printf("\n");
86 | return -1;
87 | }
88 |
89 | #pragma mark Strip comments from input
90 |
91 | FILE *file = fopen(inputFile, "r");
92 |
93 | char prevChar = '\n', thisChar = '\n';
94 | State currentState = SOURCE;
95 |
96 | while ((thisChar = fgetc(file)) != EOF) {
97 |
98 | switch (currentState) {
99 | case SOURCE:
100 | if (thisChar == '/')
101 | currentState = SLASH;
102 | else {
103 | if (thisChar == '"')
104 | currentState = STRING_LITERAL;
105 | else if (thisChar == '\'')
106 | currentState = CHAR_LITERAL;
107 | printf("%C", thisChar);
108 | }
109 | break;
110 |
111 | case STRING_LITERAL:
112 | if (thisChar == '"' && prevChar != '\\') // Account for \" char
113 | currentState = SOURCE;
114 | printf("%C", thisChar);
115 | break;
116 |
117 | case CHAR_LITERAL:
118 | if (thisChar == '\'' && prevChar != '\\') // Account for \' char
119 | currentState = SOURCE;
120 | printf("%C", thisChar);
121 | break;
122 |
123 | case SLASH:
124 | if (thisChar == '*') {
125 | currentState = SLASH_STAR;
126 | }
127 | else if (thisChar == '/') {
128 | if (!stripLine)
129 | printf("//");
130 | currentState = COMMENT_LINE;
131 | }
132 | else {
133 | printf("/%C", thisChar);
134 | currentState = SOURCE;
135 | }
136 | break;
137 |
138 | case SLASH_STAR:
139 | if (thisChar == '*') {
140 | if (!stripJavadoc)
141 | printf("/**");
142 | currentState = COMMENT_JAVADOC;
143 | }
144 | else if (thisChar == '!') {
145 | if (!stripHeaderDoc)
146 | printf("/*!");
147 | currentState = COMMENT_HEADERDOC;
148 | }
149 | else {
150 | if (!stripCStyle)
151 | printf("/*%C", thisChar);
152 | currentState = COMMENT_CSTYLE;
153 | thisChar = 0; // Prevents counting "/*/" as a block comment
154 | }
155 | break;
156 |
157 | case COMMENT_LINE:
158 | if (thisChar == '\n') {
159 | printf("\n");
160 | currentState = SOURCE;
161 | }
162 | if (!stripLine)
163 | printf("%C", thisChar);
164 | break;
165 |
166 | case COMMENT_CSTYLE:
167 | if (!stripCStyle)
168 | printf("%C", thisChar);
169 | if (prevChar == '*' && thisChar == '/')
170 | currentState = SOURCE;
171 | break;
172 |
173 | case COMMENT_JAVADOC:
174 | if (!stripJavadoc)
175 | printf("%C", thisChar);
176 | if (prevChar == '*' && thisChar == '/')
177 | currentState = SOURCE;
178 | break;
179 |
180 | case COMMENT_HEADERDOC:
181 | if (!stripHeaderDoc)
182 | printf("%C", thisChar);
183 | if (prevChar == '*' && thisChar == '/')
184 | currentState = SOURCE;
185 | break;
186 |
187 | }
188 | prevChar = thisChar;
189 |
190 | }
191 | if (thisChar != '\n')
192 | printf("\n", thisChar);
193 |
194 | fclose(file);
195 | return 0;
196 | }
197 |
--------------------------------------------------------------------------------
/source/CHAbstractBinarySearchTree.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHAbstractBinarySearchTree.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | /**
13 | @file CHAbstractBinarySearchTree.h
14 | An abstract CHSearchTree implementation with many default method implementations.
15 | */
16 |
17 | /**
18 | A node used by binary search trees for internal storage and representation.
19 |
20 |
21 | typedef struct CHBinaryTreeNode {
22 | id object;
23 | union {
24 | struct {
25 | __strong struct CHBinaryTreeNode *left;
26 | __strong struct CHBinaryTreeNode *right;
27 | };
28 | __strong struct CHBinaryTreeNode *link[2];
29 | };
30 | union {
31 | int32_t balance; // Used by CHAVLTree
32 | u_int32_t color; // Used by CHRedBlackTree
33 | u_int32_t level; // Used by CHAnderssonTree
34 | u_int32_t priority; // Used by CHTreap
35 | };
36 | } CHBinaryTreeNode;
37 |
38 | The nested anonymous union and structs are to provide flexibility for dealing with various types of trees and access. (For those not familiar, a union is a data structure in which all members are stored at the same memory location, and can take on the value of any of its fields. A union occupies only as much space as the largest member, whereas a struct requires space equal to at least the sum of the size of its members.)
39 |
40 | - The first union provides two equivalent ways to access child nodes, based on what is most convenient and efficient. Because of the order in which the fields are declared, left === link[0] and right === link[1], meaning these respective pairs point to the same memory address. (This technique is an adaptation of the idiom used in the BST tutorials on EternallyConfuzzled.com.)
41 | - The second union allows balanced trees to store extra data at each node, while using the field name and type that makes sense for its algorithms. This allows for generic reuse while promoting meaningful semantics and preserving space. These fields use 32-bit-only types since we don't need extra space in 64-bit mode.
42 |
43 | Since CHUnbalancedTree doesn't store any extra data, the second union is essentially 4 bytes of pure overhead per node. However, since unbalanced trees are generally not a good choice for sorting large data sets anyway, this is largely a moot point.
44 | */
45 | typedef struct CHBinaryTreeNode {
46 | __unsafe_unretained _Nullable id object; ///< The object stored in the node.
47 | union {
48 | struct {
49 | struct CHBinaryTreeNode *left; ///< Link to left child.
50 | struct CHBinaryTreeNode *right; ///< Link to right child.
51 | };
52 | struct CHBinaryTreeNode * _Nullable link[2]; ///< Links to both childen.
53 | };
54 | union {
55 | int32_t balance; // Used by CHAVLTree
56 | u_int32_t color; // Used by CHRedBlackTree
57 | u_int32_t level; // Used by CHAnderssonTree
58 | u_int32_t priority; // Used by CHTreap
59 | };
60 | } CHBinaryTreeNode;
61 | // NOTE: If the compiler issues "Declaration does not declare anthing" warnings for this struct, change the C Language Dialect in your Xcode build settings to GNU99; anonymous structs and unions are not properly supported by the C99 standard.
62 |
63 | /**
64 | An abstract CHSearchTree with many default method implementations. Methods for search, size, and enumeration are implemented in this class, as are methods for NSCoding, NSCopying, and NSFastEnumeration. (This works since all child classes use the CHBinaryTreeNode struct.) Any subclass @b must implement \link #addObject: -addObject:\endlink and \link #removeObject: -removeObject:\endlink according to the inner workings of that specific tree.
65 |
66 | Rather than enforcing that this class be abstract, the contract is implied. If this class were actually instantiated, it would be of little use since there is attempts to insert or remove will result in runtime exceptions being raised.
67 |
68 | Much of the code and algorithms was distilled from information in the Binary Search Trees tutorial, which is in the public domain courtesy of Julienne Walker. Method names have been changed to match the APIs of existing Cocoa collections provided by Apple.
69 | */
70 | @interface CHAbstractBinarySearchTree<__covariant ObjectType> : NSObject
71 | {
72 | CHBinaryTreeNode *header; // Dummy header; no more checks for root.
73 | CHBinaryTreeNode *sentinel; // Dummy leaf; no more checks for NULL.
74 | NSUInteger count; // The number of objects currently in the tree.
75 | unsigned long mutations; // Tracks mutations for NSFastEnumeration.
76 | }
77 |
78 | - (instancetype)initWithArray:(NSArray *)anArray NS_DESIGNATED_INITIALIZER;
79 |
80 | /**
81 | Produces a representation of the receiver that can be useful for debugging.
82 |
83 | Whereas @c -description outputs only the contents of the tree in ascending order, this method outputs the internal structure of the tree (showing the objects in each node and its children) using a pre-order traversal. Sentinel leaf nodes are represented as @c nil children.
84 |
85 | @return A string representation of the receiver, intended for debugging purposes.
86 |
87 | @note Using @c print-object or @c po within GDB automatically calls the @c -debugDescription method of the specified object.
88 |
89 | @see dotGraphString
90 | */
91 | - (NSString *)debugDescription;
92 |
93 | /**
94 | Produces a DOT language graph description for the receiver tree.
95 |
96 | A DOT graph can be rendered with GraphViz, OmniGraffle, or other similar tools. Sentinel leaf nodes are represented by a small black dot.
97 |
98 | @return A graph description for the receiver tree in the DOT language.
99 |
100 | @see debugDescription
101 | */
102 | - (NSString *)dotGraphString;
103 |
104 | @end
105 |
106 | NS_ASSUME_NONNULL_END
107 |
--------------------------------------------------------------------------------
/source/CHBinaryHeap.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHBinaryHeap.m
3 | // CHDataStructures
4 | //
5 | // Copyright © 2009-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | #pragma mark CFBinaryHeap callbacks
11 |
12 | const void * CHBinaryHeapRetain(CFAllocatorRef allocator, const void *value) {
13 | return [(id)value retain];
14 | }
15 |
16 | void CHBinaryHeapRelease(CFAllocatorRef allocator, const void *value) {
17 | [(id)value release];
18 | }
19 |
20 | CFStringRef CHBinaryHeapCopyDescription(const void *value) {
21 | return CFRetain([(id)value description]);
22 | }
23 |
24 | CFComparisonResult CHBinaryHeapCompareAscending(const void *value1, const void *value2, void *info) {
25 | return (CFComparisonResult)[(id)value1 compare:(id)value2];
26 | }
27 |
28 | CFComparisonResult CHBinaryHeapCompareDescending(const void *value1, const void *value2, void *info) {
29 | return [(id)value1 compare:(id)value2] * -1;
30 | }
31 |
32 | static const CFBinaryHeapCallBacks kCHBinaryHeapCallBacksAscending = {
33 | 0, // default version
34 | CHBinaryHeapRetain,
35 | CHBinaryHeapRelease,
36 | CHBinaryHeapCopyDescription,
37 | CHBinaryHeapCompareAscending
38 | };
39 |
40 | static const CFBinaryHeapCallBacks kCHBinaryHeapCallBacksDescending = {
41 | 0, // default version
42 | CHBinaryHeapRetain,
43 | CHBinaryHeapRelease,
44 | CHBinaryHeapCopyDescription,
45 | CHBinaryHeapCompareDescending
46 | };
47 |
48 | #pragma mark -
49 |
50 | @implementation CHBinaryHeap
51 |
52 | - (void)dealloc {
53 | CFRelease(heap); // The heap will never be null at this point.
54 | [super dealloc];
55 | }
56 |
57 | - (instancetype)init {
58 | return [self initWithOrdering:NSOrderedAscending array:@[]];
59 | }
60 |
61 | - (instancetype)initWithArray:(NSArray *)anArray {
62 | return [self initWithOrdering:NSOrderedAscending array:anArray];
63 | }
64 |
65 | - (instancetype)initWithOrdering:(NSComparisonResult)order {
66 | return [self initWithOrdering:order array:@[]];
67 | }
68 |
69 | // This is the designated initializer
70 | - (instancetype)initWithOrdering:(NSComparisonResult)order array:(NSArray *)anArray {
71 | self = [super init];
72 | if (self) {
73 | sortOrder = order;
74 | if (sortOrder == NSOrderedAscending) {
75 | heap = CFBinaryHeapCreate(kCFAllocatorDefault, 0, &kCHBinaryHeapCallBacksAscending, NULL);
76 | } else if (sortOrder == NSOrderedDescending) {
77 | heap = CFBinaryHeapCreate(kCFAllocatorDefault, 0, &kCHBinaryHeapCallBacksDescending, NULL);
78 | } else {
79 | CHRaiseInvalidArgumentException(@"Invalid sort order.");
80 | }
81 | [self addObjectsFromArray:anArray];
82 | }
83 | return self;
84 | }
85 |
86 | #pragma mark Querying Contents
87 |
88 | - (NSArray *)allObjects {
89 | return [self allObjectsInSortedOrder];
90 | }
91 |
92 | - (NSArray *)allObjectsInSortedOrder {
93 | NSUInteger count = [self count];
94 | void *values = malloc(kCHPointerSize * count);
95 | CFBinaryHeapGetValues(heap, values);
96 | NSArray *objects = [NSArray arrayWithObjects:values count:count];
97 | free(values);
98 | return objects;
99 | }
100 |
101 | - (BOOL)containsObject:(id)anObject {
102 | return CFBinaryHeapContainsValue(heap, anObject);
103 | }
104 |
105 | - (NSUInteger)count {
106 | return CFBinaryHeapGetCount(heap);
107 | }
108 |
109 | - (NSString *)description {
110 | return [[self allObjectsInSortedOrder] description];
111 | }
112 |
113 | - (NSString *)debugDescription {
114 | CFStringRef description = CFCopyDescription(heap);
115 | CFRelease([(id)description retain]);
116 | return [(id)description autorelease];
117 | }
118 |
119 | - (id)firstObject {
120 | return (id)CFBinaryHeapGetMinimum(heap);
121 | }
122 |
123 | - (NSUInteger)hash {
124 | id anObject = [self firstObject];
125 | return CHHashOfCountAndObjects([self count], anObject, anObject);
126 | }
127 |
128 | - (BOOL)isEqual:(id)otherObject {
129 | if ([otherObject conformsToProtocol:@protocol(CHHeap)]) {
130 | return [self isEqualToHeap:otherObject];
131 | } else {
132 | return NO;
133 | }
134 | }
135 |
136 | - (BOOL)isEqualToHeap:(id)otherHeap {
137 | return CHCollectionsAreEqual(self, otherHeap);
138 | }
139 |
140 | - (NSEnumerator *)objectEnumerator {
141 | return [[self allObjectsInSortedOrder] objectEnumerator];
142 | }
143 |
144 | #pragma mark Modifying Contents
145 |
146 | - (void)addObject:(id)anObject {
147 | CHRaiseInvalidArgumentExceptionIfNil(anObject);
148 | CFBinaryHeapAddValue(heap, anObject);
149 | ++mutations;
150 | }
151 |
152 | - (void)addObjectsFromArray:(NSArray *)anArray {
153 | if ([anArray count] == 0) { // includes implicit check for nil array
154 | return;
155 | }
156 | for (id anObject in anArray) {
157 | CFBinaryHeapAddValue(heap, anObject);
158 | }
159 | ++mutations;
160 | }
161 |
162 | - (void)removeAllObjects {
163 | CFBinaryHeapRemoveAllValues(heap);
164 | ++mutations;
165 | }
166 |
167 | - (void)removeFirstObject {
168 | CFBinaryHeapRemoveMinimumValue(heap);
169 | ++mutations;
170 | }
171 |
172 | #pragma mark
173 |
174 | - (instancetype)initWithCoder:(NSCoder *)decoder {
175 | return [self initWithOrdering:([decoder decodeBoolForKey:@"sortAscending"]
176 | ? NSOrderedAscending : NSOrderedDescending)
177 | array:[decoder decodeObjectForKey:@"objects"]];
178 | }
179 |
180 | - (void)encodeWithCoder:(NSCoder *)encoder {
181 | [encoder encodeObject:[self allObjectsInSortedOrder] forKey:@"objects"];
182 | [encoder encodeBool:(sortOrder == NSOrderedAscending) forKey:@"sortAscending"];
183 | }
184 |
185 | #pragma mark
186 |
187 | - (instancetype)copyWithZone:(NSZone *)zone {
188 | return [[CHBinaryHeap alloc] initWithArray:[self allObjects]];
189 | }
190 |
191 | #pragma mark
192 |
193 | // This overridden method returns the heap contents in fully-sorted order.
194 | // Just as -objectEnumerator above, the first call incurs a hidden sorting cost.
195 | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len {
196 | // Currently (in Leopard) NSEnumerators from NSArray only return 1 each time
197 | if (state->state == 0) {
198 | // Create a sorted array to use for enumeration, store it in the state.
199 | state->extra[4] = (unsigned long) [self allObjectsInSortedOrder];
200 | }
201 | NSArray *sorted = (NSArray *) state->extra[4];
202 | NSUInteger count = [sorted countByEnumeratingWithState:state
203 | objects:stackbuf
204 | count:len];
205 | state->mutationsPtr = &mutations; // point state to mutations for heap array
206 | return count;
207 | }
208 |
209 | @end
210 |
--------------------------------------------------------------------------------
/source/CHTreap.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHTreap.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | /**
13 | @file CHTreap.h
14 | A Treap implementation of CHSearchTree.
15 | */
16 |
17 | /**
18 | A Treap, a balanced binary tree with O(log n) access in general, and improbable worst cases. The name "treap" is a portmanteau of "tree" and "heap", which is fitting since treaps exhibit properties of both binary search trees and heaps.
19 |
20 | Each node in a treap contains an object and a priority value, which may be arbitrarily-selected. Nodes in a treap are arranged such that the objects are ordered as in a binary search tree, and the priorities are ordered to obey the heap property (every node must have a higher priority than both its children). A sample treap is presented below, with priorities shown below the nodes.
21 |
22 | @image html treap-sample.png "Figure 1 - A sample treap with node priorities."
23 |
24 | Notice that, unlike a binary heap, a treap need not be a complete tree, which is a tree where every level is complete, with the possible exception of the lowest level, in which case any gaps must occur only on the level's right side. Also, the priority can be any numerical value (integer or floating point, positive or negative, signed or unsigned) as long as the range is large enough to accommodate the number of objects that may be added to the treap. Uniqueness of priority values is not strictly required, but it can help.
25 |
26 | Nodes are reordered to satisfy the heap property using rotations involving only two nodes, which change the position of children in the tree, but leave the subtrees unchanged. The rotation operations are mirror images of each other, and are shown below:
27 |
28 | @image html treap-rotations.png "Figure 2 - The effect of rotation operations."
29 |
30 | Since subtrees may be rotated to satisfy the heap property without violating the BST property, these two properties never conflict. In fact, for a given set of objects and unique priorities, there is only one treap structure that can satisfy both properties. In practice, when the priority for each node is truly random, the tree is relatively well balanced, with expected height of Θ(log n). Treap performance is extremely fast on average, with a small risk of slow performance in random worst cases, which tend to be quite rare in practice.
31 |
32 | Insertion is a cross between standard BST insertion and heap insertion: a new leaf node is created in the appropriate sorted location, and a random value is assigned. The path back to the root is then retraced, rotating the node upward as necessary until the new node's priority is greater than both its children's. Deletion is generally implemented by rotating the node to be removed down the tree until it becomes a leaf and can be clipped. At each rotation, the child whose priority is higher is rotated to become the root, and the node to delete descends the opposite subtree. (It is also possible to swap with the successor node as is common in BST deletion, but in order to preserve the tree's balance, the priorities should also be swapped, and the successor be bubbled up until the heap property is again satisfied, an approach quite similar to insertion.)
33 |
34 | This treap implementation adds two methods to those in the CHSearchTree protocol:
35 | - \link #addObject:withPriority: -addObject:withPriority:\endlink
36 | - \link #priorityForObject: -priorityForObject:\endlink
37 |
38 | Treaps were originally described in the following paper:
39 |
40 |
41 | R. Seidel and C. R. Aragon. "Randomized Search Trees." Algorithmica, 16(4/5):464-497, 1996.
42 |
43 |
44 | (See PDF original
45 | or PostScript revision)
46 |
47 | @todo Examine performance issues (treaps are often the slowest balanced tree).
48 | */
49 | @interface CHTreap<__covariant ObjectType> : CHAbstractBinarySearchTree
50 |
51 | /** Priority when an object is not found in a treap (max value for u_int32_t). */
52 | #define CHTreapNotFound UINT32_MAX
53 |
54 | /**
55 | Add an object to the tree with a randomly-generated priority value. This encourages (but doesn't necessarily guarantee) well-balanced treaps. Random numbers are generated using @c arc4random and cast as an NSUInteger.
56 |
57 | @param anObject The object to add to the treap.
58 |
59 | @throw NSInvalidArgumentException if @a anObject is @c nil.
60 |
61 | @see #addObject:withPriority:
62 | */
63 | - (void)addObject:(ObjectType)anObject;
64 |
65 | /**
66 | Add an object to the treap using a given priority value. Ordering is based on an object's response to the @c -compare: message. Since no duplicates are allowed, if the tree already contains an object for which @c compare: returns @c NSOrderedSame, that object is released and replaced by @a anObject.
67 |
68 | @param anObject The object to add to the treap.
69 | @param priority The priority to assign to @a nObject. Higher values percolate to the top.
70 | @note Although @a priority is typed as NSUInteger (consistent with Foundation APIs), \link CHBinaryTreeNode CHBinaryTreeNode->priority\endlink is typed as @c u_int32_t, so the value stored is actually priority % CHTreapNotFound.
71 |
72 | @throw NSInvalidArgumentException if @a anObject is @c nil.
73 |
74 | If @a anObject already exists in the treap, @a priority replaces the existing priority, and the existing node is percolated up or down to maintain the heap property. Thus, this method can be used to manipulate the depth of an object. Using a specific priority value for an object allows the user to impose a heap ordering by giving higher priorities to objects that should bubble towards the top, and lower priorities to objects that should bubble towards the bottom. In theory, this makes it significantly faster to retrieve commonly searched-for items, at the possible cost of a less-balanced treap overall, depending on the mapping of priorities and the sorted order of the objects. Use with caution.
75 | */
76 | - (void)addObject:(ObjectType)anObject withPriority:(NSUInteger)priority;
77 |
78 | /**
79 | Returns the priority for @a anObject if it's in the treap, @c CHTreapNotFound otherwise.
80 |
81 | @param anObject The object for which to find the treap priority, if it exists.
82 | @return The priority for @a anObject if it is in the treap, otherwise @c CHTreapNotFound.
83 | */
84 | - (NSUInteger)priorityForObject:(ObjectType)anObject;
85 |
86 | @end
87 |
88 | NS_ASSUME_NONNULL_END
89 |
--------------------------------------------------------------------------------
/source/CHSearchTree.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHSearchTree.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | // Copyright © 2002, Phillip Morelock
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | /**
14 | @file CHSearchTree.h
15 |
16 | A protocol which specifes an interface for N-ary search trees.
17 | */
18 |
19 | /**
20 | A set of constant values denoting the order in which to traverse a tree structure. For details, see: http://en.wikipedia.org/wiki/Tree_traversal#Traversal_methods
21 | */
22 | typedef NS_ENUM(NSUInteger, CHTraversalOrder) {
23 | CHTraversalOrderAscending, ///< Visit left subtree, node, then right subtree.
24 | CHTraversalOrderDescending, ///< Visit right subtree, node, then left subtree.
25 | CHTraversalOrderPreOrder, ///< Visit node, left subtree, then right subtree.
26 | CHTraversalOrderPostOrder, ///< Visit left subtree, right subtree, then node.
27 | CHTraversalOrderLevelOrder ///< Visit nodes from left-right, top-bottom.
28 | };
29 |
30 | #define isValidTraversalOrder(o) (o >= CHTraversalOrderAscending && o <= CHTraversalOrderLevelOrder)
31 |
32 | /**
33 | A protocol which specifes an interface for search trees, such as standard binary trees, B-trees, N-ary trees, or any similar tree-like structure. This protocol extends the CHSortedSet protocol with two additional methods (\link #allObjectsWithTraversalOrder:\endlink and \link #objectEnumeratorWithTraversalOrder:\endlink) specific to search tree implementations of a sorted set.
34 |
35 | Trees have a hierarchical structure and make heavy use of pointers to child nodes to organize information. There are several methods for visiting each node in a tree data structure, known as tree traversal techniques. (Traversal applies to N-ary trees, not just binary trees.) Whereas linked lists and arrays have one or two logical means of stepping through the elements, because trees are branching structures, there are many different ways to choose how to visit all of the nodes. There are 5 most commonly-used tree traversal methods (4 are depth-first, 1 is breadth-first) which are shown below. Table 1 shows the results enumerating the nodes in Figure 1 using each traversal and includes the constant associated with each. These constants can be passed to \link #allObjectsWithTraversalOrder:\endlink or \link #objectEnumeratorWithTraversalOrder:\endlink to enumerate objects from a search tree in a specified order. (Both \link #allObjects\endlink and \link #objectEnumerator\endlink use @c CHTraversalOrderAscending; \link #reverseObjectEnumerator\endlink uses @c CHTraversalOrderDescending.)
36 |
37 |
38 |
39 | |
40 | @image html tree-traversal.png "Figure 1 — A sample binary search tree."
41 | |
42 |
43 |
44 |
45 |
46 | | Traversal | Visit Order | Node Ordering | CHTraversalOrder |
47 |
48 | | In-order | L, node, R | A B C D E F G H I | CHTraversalOrderAscending |
49 | | Reverse-order | R, node, L | I H G F E D C B A | CHTraversalOrderDescending |
50 | | Pre-order | node, L, R | F B A D C E G I H | CHTraversalOrderPreOrder |
51 | | Post-order | L, R, node | A C E D B H I G F | CHTraversalOrderPostOrder |
52 | | Level-order | L→R, T→B | F B G A D I C E H | CHTraversalOrderLevelOrder |
53 |
54 | Table 1 - Various traversals as performed on the tree in Figure 1.
55 |
56 | |
57 |
58 |
59 | */
60 | @protocol CHSearchTree
61 |
62 | /**
63 | Initialize a search tree with no objects.
64 |
65 | @return An initialized search tree that contains no objects.
66 |
67 | @see initWithArray:
68 | */
69 | - (instancetype)init;
70 |
71 | /**
72 | Initialize a search tree with the contents of an array. Objects are added to the tree in the order they occur in the array.
73 |
74 | @param anArray An array containing objects with which to populate a new search tree.
75 | @return An initialized search tree that contains the objects in @a anArray in sorted order.
76 | */
77 | - (instancetype)initWithArray:(NSArray *)anArray;
78 |
79 | #pragma mark Querying Contents
80 | /** @name Tree Traversals */
81 | // @{
82 |
83 | /**
84 | Returns an NSArray which contains the objects in this tree in a given ordering. The object traversed last will appear last in the array.
85 |
86 | @param order The traversal order to use for enumerating the given tree.
87 | @return An array containing the objects in this tree. If the tree is empty, the array is also empty.
88 |
89 | @see \link allObjects - allObjects\endlink
90 | @see objectEnumeratorWithTraversalOrder:
91 | @see \link reverseObjectEnumerator - reverseObjectEnumerator\endlink
92 | */
93 | - (NSArray *)allObjectsWithTraversalOrder:(CHTraversalOrder)order;
94 |
95 | /**
96 | Compares the receiving search tree to another search tree. Two search trees have equal contents if they each hold the same number of objects and objects at a given position in each search tree satisfy the \link NSObject-p#isEqual: -isEqual:\endlink test.
97 |
98 | @param otherTree A search tree.
99 | @return @c YES if the contents of @a otherTree are equal to the contents of the receiver, otherwise @c NO.
100 | */
101 | - (BOOL)isEqualToSearchTree:(id)otherTree;
102 |
103 | /**
104 | Returns an enumerator that accesses each object using a given traversal order.
105 |
106 | @param order The order in which an enumerator should traverse nodes in the tree. @return An enumerator that accesses each object in the tree in a given order. The enumerator returned is never @c nil; if the tree is empty, the enumerator will always return @c nil for \link NSEnumerator#nextObject -nextObject\endlink and an empty array for \link NSEnumerator#allObjects -allObjects\endlink.
107 |
108 | @attention The enumerator retains the collection. Once all objects in the enumerator have been consumed, the collection is released.
109 | @warning Modifying a collection while it is being enumerated is unsafe, and may cause a mutation exception to be raised.
110 |
111 | @see allObjectsWithTraversalOrder:
112 | @see \link objectEnumerator - objectEnumerator\endlink
113 | @see \link reverseObjectEnumerator - reverseObjectEnumerator\endlink
114 | */
115 | - (NSEnumerator *)objectEnumeratorWithTraversalOrder:(CHTraversalOrder)order;
116 |
117 | // @}
118 | @end
119 |
120 | NS_ASSUME_NONNULL_END
121 |
--------------------------------------------------------------------------------
/source/CHMultiDictionary.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHMultiDictionary.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | /**
13 | @file CHMultiDictionary.h
14 |
15 | A multimap in which multiple values may be associated with a given key.
16 | */
17 |
18 | /**
19 | A multimap implementation, in which multiple values may be associated with a given key.
20 |
21 | A map is the same as a "dictionary", "associative array", etc. and consists of a unique set of keys and a collection of values. In a standard map, each key is associated with one value; in a multimap, more than one value may be associated with a given key. A multimap is appropriate for any situation in which one item may correspond to (map to) multiple values, such as a term in an book index and occurrences of that term, courses for which a student is registered, etc.
22 |
23 | The values for a key may or may not be ordered. This implementation does not maintain an ordering for objects associated with a key, nor does it allow for multiple occurrences of an object associated with the same key. Internally, this class uses an NSMutableDictionary, and the associated values for each key are stored in distinct NSMutableSet instances. (Just as with NSDictionary, each key added to a CHMultiDictionary is copied using \link NSCopying#copyWithZone: -copyWithZone:\endlink and all keys must conform to the NSCopying protocol.) Objects are retained on insertion and released on removal or deallocation.
24 |
25 | Since NSDictionary and NSSet conform to the NSCoding protocol, any internal data can be serialized. However, NSSet cannot automatically be written to or read from a property list, since it has no specified order. Thus, instances of CHMultiDictionary must be encoded as an NSData object before saving to disk.
26 |
27 | Currently, this implementation does not support key-value coding, observing, or binding like NSDictionary does. Consequently, the distinction between "object" and "value" is blurrier, although hopefully consistent with the Cocoa APIs in general....
28 |
29 | Unlike NSDictionary and other Cocoa collections, CHMultiDictionary has not been designed with mutable and immutable variants. A multimap is not that much more useful if it is immutable, so any copies made of this class are mutable by definition.
30 | */
31 | @interface CHMultiDictionary<__covariant KeyType, __covariant ObjectType> : CHMutableDictionary
32 | {
33 | NSUInteger objectCount; // Number of objects currently in the dictionary.
34 | }
35 |
36 | #pragma mark Querying Contents
37 |
38 | /**
39 | Returns the number of objects in the receiver, associated with any key.
40 |
41 | @return The number of objects in the receiver. This is the sum total of objects associated with each key in the dictonary.
42 |
43 | @see allObjects
44 | */
45 | - (NSUInteger)countForAllKeys;
46 |
47 | /**
48 | Returns the number of objects associated with a given key.
49 |
50 | @param aKey The key for which to return the object count.
51 | @return The number of objects associated with a given key in the dictionary.
52 |
53 | @see objectsForKey:
54 | */
55 | - (NSUInteger)countForKey:(KeyType)aKey;
56 |
57 | /**
58 | Returns an array of objects associated with a given key.
59 |
60 | @param aKey The key for which to return the corresponding objects.
61 | @return An NSSet of objects associated with a given key, or @c nil if the key is not in the receiver.
62 |
63 | @see countForKey:
64 | @see removeObjectsForKey:
65 | */
66 | - (nullable NSSet *)objectsForKey:(KeyType)aKey;
67 |
68 | #pragma mark Modifying Contents
69 |
70 | /**
71 | Adds a given object to an entry for a given key in the receiver.
72 |
73 | @param aKey The key with which to associate @a anObject.
74 | @param anObject An object to add to an entry for @a aKey in the receiver. If an entry for @a aKey already exists in the receiver, @a anObject is added using \link NSMutableSet#addObject: -[NSMutableSet addObject:]\endlink, otherwise a new entry is created.
75 |
76 | @throw NSInvalidArgumentException if @a aKey or @a anObject is @c nil.
77 |
78 | @see addObjects:forKey:
79 | @see objectsForKey:
80 | @see removeObjectsForKey:
81 | @see setObjects:forKey:
82 | */
83 | - (void)addObject:(ObjectType)anObject forKey:(KeyType)aKey;
84 |
85 | /**
86 | Adds the given object(s) to a key entry in the receiver.
87 |
88 | @param aKey The key with which to associate @a anObject.
89 | @param objectSet A set of objects to add to an entry for @a aKey in the receiver. If an entry for @a aKey already exists in the receiver, @a anObject is added using \link NSMutableSet#unionSet: -[NSMutableSet unionSet:]\endlink, otherwise a new entry is created.
90 |
91 | @throw NSInvalidArgumentException if @a aKey or @a objectSet is @c nil.
92 |
93 | @see addObject:forKey:
94 | @see objectsForKey:
95 | @see removeObjectsForKey:
96 | @see setObjects:forKey:
97 | */
98 | - (void)addObjects:(NSSet *)objectSet forKey:(KeyType)aKey;
99 |
100 | /**
101 | Remove @b all occurrences of @a anObject associated with a given key.
102 |
103 | @param aKey The key for which to remove an entry.
104 | @param anObject An object (possibly) associated with @a aKey in the receiver. Objects are considered to be equal if -compare: returns NSOrderedSame.
105 |
106 | @throw NSInvalidArgumentException if @a aKey or @a anObject is @c nil.
107 |
108 | If @a aKey does not exist in the receiver, or if @a anObject is not associated with @a aKey, the contents of the receiver are not modified.
109 |
110 | @see containsObject
111 | @see objectsForKey:
112 | @see removeObjectsForKey:
113 | */
114 | - (void)removeObject:(ObjectType)anObject forKey:(KeyType)aKey;
115 |
116 | /**
117 | Remove a given key and its associated value(s) from the receiver.
118 |
119 | @param aKey The key for which to remove an entry.
120 |
121 | If @a aKey does not exist in the receiver, there is no effect on the receiver.
122 |
123 | @see objectsForKey:
124 | @see removeObject:forKey:
125 | */
126 | - (void)removeObjectsForKey:(KeyType)aKey;
127 |
128 | /**
129 | Sets the object(s) associated with a key entry in the receiver.
130 |
131 | @param aKey The key with which to associate the objects in @a objectSet.
132 | @param objectSet A set of objects to associate with @a key. If @a objectSet is empty, the contents of the receiver are not modified. If an entry for @a key already exists in the receiver, @a objectSet is added using \link NSMutableSet#setSet: -[NSMutableSet setSet:]\endlink, otherwise a new entry is created.
133 |
134 | @throw NSInvalidArgumentException if @a aKey or @a objectSet is @c nil.
135 |
136 | @see addObject:forKey:
137 | @see addObjects:forKey:
138 | @see objectsForKey:
139 | @see removeObjectsForKey:
140 | */
141 | - (void)setObjects:(NSSet *)objectSet forKey:(KeyType)aKey;
142 |
143 | @end
144 |
145 | NS_ASSUME_NONNULL_END
146 |
--------------------------------------------------------------------------------
/test/CHDequeTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // CHDequeTest.m
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 | #import
10 | #import
11 |
12 | @interface CHDequeTest : XCTestCase {
13 | id deque;
14 | NSArray *objects, *dequeClasses;
15 | }
16 | @end
17 |
18 | @implementation CHDequeTest
19 |
20 | - (void)setUp {
21 | objects = @[@"A",@"B",@"C"];
22 | dequeClasses = @[
23 | [CHListDeque class],
24 | [CHCircularBufferDeque class],
25 | ];
26 | }
27 |
28 | - (void)testInitWithArray {
29 | NSMutableArray *moreObjects = [NSMutableArray array];
30 | for (NSUInteger i = 0; i < 32; i++) {
31 | [moreObjects addObject:@(i)];
32 | }
33 |
34 | for (Class aClass in dequeClasses) {
35 | // Test initializing with nil and empty array parameters
36 | deque = nil;
37 | XCTAssertThrows([[[aClass alloc] initWithArray:nil] autorelease]);
38 | XCTAssertEqual([deque count], 0);
39 | deque = [[[aClass alloc] initWithArray:@[]] autorelease];
40 | XCTAssertEqual([deque count], 0);
41 | // Test initializing with a valid, non-empty array
42 | deque = [[[aClass alloc] initWithArray:objects] autorelease];
43 | XCTAssertEqual([deque count], [objects count]);
44 | XCTAssertEqualObjects([deque allObjects], objects);
45 | // Test initializing with an array larger than the default capacity
46 | deque = [[[aClass alloc] initWithArray:moreObjects] autorelease];
47 | XCTAssertEqual([deque count], [moreObjects count]);
48 | XCTAssertEqualObjects([deque allObjects], moreObjects);
49 | }
50 | }
51 |
52 | - (void)testPrependObject {
53 | for (Class aClass in dequeClasses) {
54 | deque = [[[aClass alloc] init] autorelease];
55 | XCTAssertThrows([deque prependObject:nil]);
56 |
57 | XCTAssertEqual([deque count], 0);
58 | for (id anObject in objects) {
59 | [deque prependObject:anObject];
60 | }
61 | XCTAssertEqual([deque count], 3);
62 | NSEnumerator *e = [deque objectEnumerator];
63 | XCTAssertEqualObjects([e nextObject], @"C");
64 | XCTAssertEqualObjects([e nextObject], @"B");
65 | XCTAssertEqualObjects([e nextObject], @"A");
66 | }
67 | }
68 |
69 | - (void)testAppendObject {
70 | for (Class aClass in dequeClasses) {
71 | deque = [[[aClass alloc] init] autorelease];
72 | XCTAssertThrows([deque appendObject:nil]);
73 |
74 | XCTAssertEqual([deque count], 0);
75 | for (id anObject in objects) {
76 | [deque appendObject:anObject];
77 | }
78 | XCTAssertEqual([deque count], 3);
79 | NSEnumerator *e = [deque objectEnumerator];
80 | XCTAssertEqualObjects([e nextObject], @"A");
81 | XCTAssertEqualObjects([e nextObject], @"B");
82 | XCTAssertEqualObjects([e nextObject], @"C");
83 | }
84 | }
85 |
86 | - (void)testFirstObject {
87 | for (Class aClass in dequeClasses) {
88 | deque = [[[aClass alloc] init] autorelease];
89 | XCTAssertEqualObjects([deque firstObject], nil);
90 | for (id anObject in objects) {
91 | [deque prependObject:anObject];
92 | XCTAssertEqualObjects([deque firstObject], anObject);
93 | }
94 | }
95 | }
96 |
97 | - (void)testIsEqualToDeque {
98 | NSMutableArray *emptyDeques = [NSMutableArray array];
99 | NSMutableArray *equalDeques = [NSMutableArray array];
100 | NSMutableArray *reversedDeques = [NSMutableArray array];
101 | NSArray *reversedObjects = [[objects reverseObjectEnumerator] allObjects];
102 | for (Class aClass in dequeClasses) {
103 | [emptyDeques addObject:[[aClass alloc] init]];
104 | [equalDeques addObject:[[aClass alloc] initWithArray:objects]];
105 | [reversedDeques addObject:[[aClass alloc] initWithArray:reversedObjects]];
106 | }
107 | // Add a repeat of the first class to avoid wrapping.
108 | [equalDeques addObject:[equalDeques objectAtIndex:0]];
109 |
110 | id deque1, deque2;
111 | for (NSUInteger i = 0; i < [dequeClasses count]; i++) {
112 | deque1 = [equalDeques objectAtIndex:i];
113 | XCTAssertThrowsSpecificNamed([deque1 isEqualToDeque:(id)[NSString string]], NSException, NSInvalidArgumentException);
114 | XCTAssertFalse([deque1 isEqual:[NSString string]]);
115 | XCTAssertEqualObjects(deque1, deque1);
116 | deque2 = [equalDeques objectAtIndex:i+1];
117 | XCTAssertEqualObjects(deque1, deque2);
118 | XCTAssertEqual([deque1 hash], [deque2 hash]);
119 | deque2 = [emptyDeques objectAtIndex:i];
120 | XCTAssertFalse([deque1 isEqual:deque2]);
121 | deque2 = [reversedDeques objectAtIndex:i];
122 | XCTAssertFalse([deque1 isEqual:deque2]);
123 | }
124 | }
125 |
126 | - (void)testLastObject {
127 | for (Class aClass in dequeClasses) {
128 | deque = [[[aClass alloc] init] autorelease];
129 | XCTAssertEqualObjects([deque lastObject], nil);
130 | for (id anObject in objects) {
131 | [deque appendObject:anObject];
132 | XCTAssertEqualObjects([deque lastObject], anObject);
133 | }
134 | }
135 | }
136 |
137 | - (void)testRemoveFirstObject {
138 | for (Class aClass in dequeClasses) {
139 | deque = [[[aClass alloc] init] autorelease];
140 | for (id anObject in objects) {
141 | [deque appendObject:anObject];
142 | XCTAssertEqualObjects([deque lastObject], anObject);
143 | }
144 | NSUInteger expected = [objects count];
145 | XCTAssertEqual([deque count], expected);
146 | XCTAssertEqualObjects([deque firstObject], @"A");
147 | XCTAssertEqualObjects([deque lastObject], @"C");
148 | [deque removeFirstObject];
149 | --expected;
150 | XCTAssertEqual([deque count], expected);
151 | XCTAssertEqualObjects([deque firstObject], @"B");
152 | XCTAssertEqualObjects([deque lastObject], @"C");
153 | [deque removeFirstObject];
154 | --expected;
155 | XCTAssertEqual([deque count], expected);
156 | XCTAssertEqualObjects([deque firstObject], @"C");
157 | XCTAssertEqualObjects([deque lastObject], @"C");
158 | [deque removeFirstObject];
159 | --expected;
160 | XCTAssertEqual([deque count], expected);
161 | XCTAssertNil([deque firstObject]);
162 | XCTAssertNil([deque lastObject]);
163 | // Test that removal works even with an empty deque
164 | XCTAssertNoThrow([deque removeFirstObject]);
165 | XCTAssertEqual([deque count], expected);
166 | }
167 | }
168 |
169 | - (void)testRemoveLastObject {
170 | for (Class aClass in dequeClasses) {
171 | deque = [[[aClass alloc] init] autorelease];
172 | for (id anObject in objects) {
173 | [deque appendObject:anObject];
174 | }
175 | XCTAssertEqualObjects([deque lastObject], @"C");
176 | [deque removeLastObject];
177 | XCTAssertEqualObjects([deque lastObject], @"B");
178 | [deque removeLastObject];
179 | XCTAssertEqualObjects([deque lastObject], @"A");
180 | [deque removeLastObject];
181 | XCTAssertNil([deque lastObject]);
182 | // Test that removal works even with an empty deque
183 | XCTAssertNoThrow([deque removeLastObject]);
184 | XCTAssertEqual([deque count], 0);
185 | }
186 | }
187 |
188 | - (void)testReverseObjectEnumerator {
189 | for (Class aClass in dequeClasses) {
190 | deque = [[[aClass alloc] init] autorelease];
191 | for (id anObject in objects) {
192 | [deque appendObject:anObject];
193 | }
194 | NSEnumerator *e = [deque reverseObjectEnumerator];
195 | XCTAssertEqualObjects([e nextObject], @"C");
196 | XCTAssertEqualObjects([e nextObject], @"B");
197 | XCTAssertEqualObjects([e nextObject], @"A");
198 | XCTAssertEqualObjects([e nextObject], nil);
199 | }
200 | }
201 |
202 | @end
203 |
--------------------------------------------------------------------------------
/source/CHBidirectionalDictionary.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHBidirectionalDictionary.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2010-2021, Quinn Taylor
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | /**
13 | A dictionary that allows bidirectional lookup by keys and values with equal ease. This is possible because a bidirectional dictionary enforces the restriction that there is a 1-to-1 relation between keys and values, meaning that multiple keys cannot map to the same value. This is accomplished using a second internal dictionary which stores value-to-key mappings so values can be checked for uniqueness upon insertion. (Since values become the keys in this secondary dictionary, values must also be unique.) See \link #setObject:forKey: -setObject:forKey:\endlink below for details.
14 |
15 | The main purpose of this constraint is to make it trivial to do bidirectional lookups of one-to-one relationships. A trivial example might be to store husband-wife pairs and look up by either, rather than choosing only one.
16 |
17 | There are two simple ways (equivalent in performance) to look up a key by it's value:
18 | - Call \link #keyForObject: -keyForObject:\endlink on a bidirectional dictionary.
19 | - Acquire the inverse dictionary via \link #inverseDictionary -inverseDictionary\endlink, then call \link #setObject:forKey: -objectForKey:\endlink on that.
20 |
21 | @attention Since values are used as keys in the inverse dictionary, both keys and values must conform to the NSCopying protocol. (NSDictionary requires that keys conform to NSCopying, but not values.) If they don't, a crash will result when this collection attempts to copy them.
22 |
23 | @warning If a dictionary is passed to \link NSDictionary#initWithDictionary: -initWithDictionary:\endlink which maps the same value to multiple keys, the value will be mapped to whichever key mapped to that value is enumerated last. Depending on the specifics of the dictionary, this ordering may be arbitrary.
24 |
25 | Implementations of bidirectional dictionaries (aka "maps") in other languages include the following:
26 |
27 | - BiMap / BidiMap (Java)
28 | - Boost.Bimap / bimap (C++)
29 | - BiDirHashtable (C#)
30 | */
31 | @interface CHBidirectionalDictionary<__covariant KeyType, __covariant ObjectType> : CHMutableDictionary
32 | {
33 | CFMutableDictionaryRef objectsToKeys; // Used for reverse mapping.
34 | CHBidirectionalDictionary *inverse; // Pointer to inverse dictionary.
35 | }
36 |
37 | #pragma mark Querying Contents
38 | /** @name Querying Contents */
39 | // @{
40 |
41 | /**
42 | Returns the key associated with a given value.
43 |
44 | @param anObject The value for which to return the corresponding key.
45 | @return The key associated with @a value, or @c nil if no such key exists.
46 |
47 | @see \link NSDictionary#allKeys -allKeys\endlink
48 | @see \link NSDictionary#objectForKey: -objectForKey:\endlink
49 | @see removeKeyForObject:
50 | */
51 | - (nullable KeyType)keyForObject:(ObjectType)anObject;
52 |
53 | /**
54 | Returns the inverse view of the receiver, which maps each value to its associated key. The receiver and its inverse are backed by the same data; any changes to one will appear in the other. A reference to the inverse (if one exists) is stored internally, and vice versa, so the two instances are linked. If one is released, it will cut its ties to and from the other.
55 |
56 | @return The inverse view of the receiver,
57 |
58 | @attention There is no guaranteed correspondence between the order in which keys are enumerated for a dictionary and its inverse.
59 | */
60 | - (CHBidirectionalDictionary *)inverseDictionary;
61 |
62 | // @}
63 | #pragma mark Modifying Contents
64 | /** @name Modifying Contents */
65 | // @{
66 |
67 | /**
68 | Adds the entries from another dictionary to the receiver. Keys and values are copied as described in #setObject:forKey: below. If a key or value already exists in the receiver, the existing key-value mapping is replaced.
69 |
70 | @param otherDictionary The dictionary from which to add entries. All its keys @b and values must conform to the NSCopying protocol.
71 |
72 | @attention If @a otherDictionary maps the same value to multiple keys, the value can only appear once in the receiver, and will be mapped to the key that is enumerated @b last, which may be arbitrary. However, if @a otherDictionary is also a CHBidirectionalDictionary, the results will always be deterministic.
73 |
74 | @see \link NSDictionary#initWithDictionary: -initWithDictionary:\endlink
75 | @see setObject:forKey:
76 | */
77 | - (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary;
78 |
79 | /**
80 | Removes the key for a given value (and its inverse key-value mapping) from the receiver. Does nothing if the specified value doesn't exist.
81 |
82 | @param anObject The value to remove.
83 |
84 | @throw NSInvalidArgumentException if @a anObject is @c nil.
85 |
86 | @see keyForObject:
87 | @see removeObjectForKey:
88 | */
89 | - (void)removeKeyForObject:(ObjectType)anObject;
90 |
91 | /**
92 | Removes the value for a given key (and its inverse value-key mapping) from the receiver. Does nothing if the specified key doesn't exist.
93 |
94 | @param aKey The key to remove.
95 |
96 | @throw NSInvalidArgumentException if @a aKey is @c nil.
97 |
98 | @see \link NSDictionary#objectForKey: -objectForKey:\endlink
99 | @see removeKeyForObject:
100 | */
101 | - (void)removeObjectForKey:(KeyType)aKey;
102 |
103 | /**
104 | Adds a given key-value pair to the receiver, replacing any existing pair with the given key or value.
105 |
106 | @param anObject The value for @a aKey. The object is copied, so it @b must conform to the NSCopying protocol or a crash will result.
107 | @param aKey The key for @a anObject. The key is copied, so it @b must conform to the NSCopying protocol or a crash will result.
108 |
109 | @throw NSInvalidArgumentException if @a aKey or @a anObject is @c nil. If you need to represent a nil value in the dictionary, use NSNull.
110 |
111 | @attention If @a aKey already exists in the receiver, the value previously associated with it is replaced by @a anObject, just as expected. However, if @a anObject already exists in the reciever with a different key, that mapping is removed to ensure that @a anObject only occurs once in the inverse dictionary. To check whether this will occur, call #keyForObject: first.
112 |
113 | @b Example:
114 | @code
115 | id dict = [[CHBidirectionalDictionary alloc] init];
116 | [dict setObject:@"B" forKey:@"A"]; // now contains A -> B
117 | [dict setObject:@"C" forKey:@"A"]; // now contains A -> C
118 | [dict setObject:@"C" forKey:@"B"]; // now contains B -> C
119 | // Values must be unique just like keys, so A -> C is removed
120 | [dict setObject:@"D" forKey:@"A"]; // now contains B -> C, A -> D
121 | @endcode
122 |
123 | @see keyForObject:
124 | @see \link NSDictionary#objectForKey: -objectForKey:\endlink
125 | */
126 | - (void)setObject:(ObjectType)anObject forKey:(KeyType)aKey;
127 |
128 | // @}
129 | @end
130 |
131 | NS_ASSUME_NONNULL_END
132 |
--------------------------------------------------------------------------------
/source/CHHeap.h:
--------------------------------------------------------------------------------
1 | //
2 | // CHHeap.h
3 | // CHDataStructures
4 | //
5 | // Copyright © 2008-2021, Quinn Taylor
6 | // Copyright © 2002, Phillip Morelock
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | /**
14 | @file CHHeap.h
15 |
16 | A heap protocol, suitable for use with many variations of the heap structure.
17 | */
18 |
19 | /**
20 | A heap protocol, suitable for use with many variations of the heap structure.
21 |
22 | Objects are "heapified" according to their sorted order, so they must respond to the @c -compare: selector, which accepts another object and returns @c NSOrderedAscending, @c NSOrderedSame, or @c NSOrderedDescending (constants in NSComparisonResult) as the receiver is less than, equal to, or greater than the argument, respectively. (Several Cocoa classes already implement the @c -compare: method, including NSString, NSDate, NSNumber, NSDecimalNumber, and NSCell.)
23 |
24 | @attention Due to the nature of a heap and how objects are stored internally, using NSFastEnumeration is not guaranteed to provide the objects in the order in which objects would be removed from the heap. If you want the objects to be sorted without removing them from the heap, use \link #allObjectsInSortedOrder allObjectsInSortedOrder\endlink instead.
25 | */
26 | @protocol CHHeap
27 |
28 | /**
29 | Initialize a heap with ascending ordering and no objects.
30 | @return An initialized CHHeap that contains no objects and will sort in ascending order.
31 |
32 | @see initWithOrdering:array:
33 | */
34 | - (instancetype)init;
35 |
36 | /**
37 | Initialize a heap with ascending ordering and objects from a given array. Objects are added to the heap as they occur in the array, then "heapified" with an ordering of @c NSOrderedAscending.
38 |
39 | @param anArray An array containing objects with which to populate a new heap.
40 | @return An initialized CHHeap that contains the objects in @a anArray, to be sorted in ascending order.
41 |
42 | @see initWithOrdering:array:
43 | */
44 | - (instancetype)initWithArray:(NSArray *)anArray;
45 |
46 | /**
47 | Initialize a heap with a given sort ordering and no objects.
48 |
49 | @param order The sort order to use, either @c NSOrderedAscending or @c NSOrderedDescending. The root element of the heap will be the smallest or largest (according to the @c -compare: method), respectively. For any other value, an @c NSInvalidArgumentException is raised.
50 | @return An initialized CHHeap that contains no objects and will sort in the specified order.
51 |
52 | @see initWithOrdering:array:
53 | */
54 | - (instancetype)initWithOrdering:(NSComparisonResult)order;
55 |
56 | /**
57 | Initialize a heap with a given sort ordering and objects from a given array. Objects are added to the heap as they occur in the array, then "heapified" with an ordering of @a order.
58 |
59 | @param order The sort order to use, either @c NSOrderedAscending or @c NSOrderedDescending. The root element of the heap will be the smallest or largest (according to the @c -compare: method), respectively. For any other value, an @c NSInvalidArgumentException is raised.
60 | @param anArray An array containing objects with which to populate a new heap.
61 | @return An initialized CHHeap that contains the objects in @a anArray, to be sorted in the specified order.
62 | */
63 | - (instancetype)initWithOrdering:(NSComparisonResult)order array:(NSArray *)anArray;
64 |
65 | #pragma mark Querying Contents
66 | /** @name Querying Contents */
67 | // @{
68 |
69 | /**
70 | Returns an array containing the objects in this heap in sorted order.
71 |
72 | @return An array containing the objects in this heap in sorted order. If the heap is empty, the array is also empty.
73 |
74 | @attention Since only the first object in a heap is guaranteed to be in sorted order, this method incurs extra costs of (1) time for sorting the contents and (2) memory for storing an extra array. However, it does not affect the order of elements in the heap itself.
75 |
76 | @see count
77 | @see objectEnumerator
78 | */
79 | - (NSArray *)allObjectsInSortedOrder;
80 |
81 | /**
82 | Determine whether the receiver contains a given object, matched using \link NSObject-p#isEqual: -isEqual:\endlink.
83 |
84 | @param anObject The object to test for membership in the heap.
85 | @return @c YES if @a anObject appears in the heap at least once, otherwise @c NO.
86 |
87 | @see removeObject:
88 | */
89 | - (BOOL)containsObject:(id)anObject;
90 |
91 | /**
92 | Returns the number of objects currently in the heap.
93 |
94 | @return The number of objects currently in the heap.
95 |
96 | @see allObjectsInSortedOrder
97 | */
98 | - (NSUInteger)count;
99 |
100 | /**
101 | Examine the first object in the heap without removing it.
102 |
103 | @return The first object in the heap, or @c nil if the heap is empty.
104 |
105 | @see removeFirstObject
106 | */
107 | - (nullable id)firstObject;
108 |
109 | /**
110 | Compares the receiving heap to another heap. Two heaps have equal contents if they each hold the same number of objects and (when fully sorted) objects at a given position in each heap satisfy the \link NSObject-p#isEqual: -isEqual:\endlink test.
111 |
112 | @param otherHeap A heap.
113 | @return @c YES if the contents of @a otherHeap are equal to the contents of the receiver, otherwise @c NO.
114 | */
115 | - (BOOL)isEqualToHeap:(id)otherHeap;
116 |
117 | /**
118 | Returns an enumerator that accesses each object in the heap in sorted order.
119 |
120 | @return An enumerator that accesses each object in the heap in sorted order. The enumerator returned is never @c nil; if the heap is empty, the enumerator will always return @c nil for \link NSEnumerator#nextObject -nextObject\endlink and an empty array for \link NSEnumerator#allObjects -allObjects\endlink.
121 |
122 | @attention Since only the first object in a heap is guaranteed to be in sorted order, this method incurs extra costs of (1) time for sorting the contents and (2) memory for storing an extra array. However, it does not affect the order of elements in the heap itself.
123 |
124 | @note On platforms that support NSFastEnumeration, that construct will also enumerate objects in sorted order.
125 |
126 | @warning Modifying a collection while it is being enumerated is unsafe, and may cause a mutation exception to be raised.
127 |
128 | @see allObjectsInSortedOrder
129 | */
130 | - (NSEnumerator *)objectEnumerator;
131 |
132 | // @}
133 | #pragma mark Modifying Contents
134 | /** @name Modifying Contents */
135 | // @{
136 |
137 | /**
138 | Insert a given object into the heap.
139 |
140 | @param anObject The object to add to the heap.
141 |
142 | @throw NSInvalidArgumentException if @a anObject is @c nil.
143 |
144 | @see addObjectsFromArray:
145 | */
146 | - (void)addObject:(id)anObject;
147 |
148 | /**
149 | Adds the objects in a given array to the receiver, then re-establish the heap property. After all the objects have been inserted, objects are "heapified" as necessary, proceeding backwards from index @c count/2 down to @c 0.
150 |
151 | @param anArray An array of objects to add to the receiver.
152 |
153 | @see addObject:
154 | */
155 | - (void)addObjectsFromArray:(NSArray *)anArray;
156 |
157 | /**
158 | Empty the receiver of all of its members.
159 |
160 | @see allObjectsInSortedOrder
161 | @see removeFirstObject
162 | */
163 | - (void)removeAllObjects;
164 |
165 | /**
166 | Remove the front object in the heap; if it is already empty, there is no effect.
167 |
168 | @see firstObject
169 | @see removeAllObjects
170 | */
171 | - (void)removeFirstObject;
172 |
173 | // @}
174 | @end
175 |
176 | NS_ASSUME_NONNULL_END
177 |
--------------------------------------------------------------------------------