├── .gitignore ├── doxygen ├── images │ ├── aa-tree-skew.png │ ├── aa-tree-split.png │ ├── treap-sample.png │ ├── treap.graffle.zip │ ├── aa-tree-sample.png │ ├── aa-tree-shapes.png │ ├── aa-tree.graffle.zip │ ├── avl-tree-sample.png │ ├── doubly-linked-0.png │ ├── doubly-linked-1.png │ ├── doubly-linked-N.png │ ├── red-black-tree.png │ ├── singly-linked-0.png │ ├── singly-linked-1.png │ ├── singly-linked-N.png │ ├── treap-rotations.png │ ├── tree-traversal.png │ ├── avl-tree-rotations.png │ ├── avl-tree.graffle.zip │ ├── linked-lists.graffle.zip │ ├── red-black-tree.graffle.zip │ └── tree-traversal.graffle.zip └── publish_locally.sh ├── benchmarks ├── README.txt └── benchmark-CHDeque-CHQueue-CHStack.xls ├── CHDataStructures.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── All.xcscheme │ ├── Deployment.xcscheme │ ├── Documentation.xcscheme │ ├── Benchmarks.xcscheme │ └── CHDataStructures.xcscheme ├── test ├── NSObject+TestUtilities.h ├── NSObject+TestUtilities.m ├── CHUtilTest.m ├── CHStackTest.m ├── CHQueueTest.m └── CHDequeTest.m ├── docs.html ├── tools ├── trim_trailing_whitespace.py ├── strip_comments.py └── strip_comments.c ├── source ├── CHCircularBufferDeque.h ├── CHCircularBufferQueue.h ├── CHCircularBufferQueue.m ├── CHCircularBufferStack.h ├── CHCircularBufferDeque.m ├── CHListStack.h ├── CHBinaryHeap.h ├── CHCircularBufferStack.m ├── CHListDeque.h ├── CHListQueue.h ├── CHMutableSet.h ├── CHUnbalancedTree.h ├── CHListQueue.m ├── CHListDeque.m ├── CHMutableDictionary.h ├── CHListStack.m ├── CHAbstractListCollection.h ├── CHCircularBuffer.h ├── CHUtil.m ├── CHMutableArrayHeap.h ├── CHSortedDictionary.m ├── CHUnbalancedTree.m ├── CHBidirectionalDictionary.m ├── CHMultiDictionary.m ├── CHRedBlackTree.h ├── CHAVLTree.h ├── CHMutableSet.m ├── CHSinglyLinkedList.h ├── CHSortedDictionary.h ├── CHAbstractListCollection.m ├── CHAnderssonTree.h ├── CHOrderedSet.m ├── CHDoublyLinkedList.h ├── CHMutableDictionary.m ├── CHOrderedDictionary.m ├── CHAbstractBinarySearchTree_Internal.h ├── CHTreap.m ├── CHUtil.h ├── CHAnderssonTree.m ├── CHAbstractBinarySearchTree.h ├── CHBinaryHeap.m ├── CHTreap.h ├── CHSearchTree.h ├── CHMultiDictionary.h ├── CHBidirectionalDictionary.h └── CHHeap.h ├── LICENSE ├── resources ├── Info-Tests.plist ├── Info-Framework.plist └── CHDataStructuresFormatters.plist ├── CHDataStructures.podspec └── Configs └── CHDataStructures.xcconfig /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | docs/ 3 | -------------------------------------------------------------------------------- /doxygen/images/aa-tree-skew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/aa-tree-skew.png -------------------------------------------------------------------------------- /doxygen/images/aa-tree-split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/aa-tree-split.png -------------------------------------------------------------------------------- /doxygen/images/treap-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/treap-sample.png -------------------------------------------------------------------------------- /doxygen/images/treap.graffle.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/treap.graffle.zip -------------------------------------------------------------------------------- /doxygen/images/aa-tree-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/aa-tree-sample.png -------------------------------------------------------------------------------- /doxygen/images/aa-tree-shapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/aa-tree-shapes.png -------------------------------------------------------------------------------- /doxygen/images/aa-tree.graffle.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/aa-tree.graffle.zip -------------------------------------------------------------------------------- /doxygen/images/avl-tree-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/avl-tree-sample.png -------------------------------------------------------------------------------- /doxygen/images/doubly-linked-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/doubly-linked-0.png -------------------------------------------------------------------------------- /doxygen/images/doubly-linked-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/doubly-linked-1.png -------------------------------------------------------------------------------- /doxygen/images/doubly-linked-N.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/doubly-linked-N.png -------------------------------------------------------------------------------- /doxygen/images/red-black-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/red-black-tree.png -------------------------------------------------------------------------------- /doxygen/images/singly-linked-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/singly-linked-0.png -------------------------------------------------------------------------------- /doxygen/images/singly-linked-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/singly-linked-1.png -------------------------------------------------------------------------------- /doxygen/images/singly-linked-N.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/singly-linked-N.png -------------------------------------------------------------------------------- /doxygen/images/treap-rotations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/treap-rotations.png -------------------------------------------------------------------------------- /doxygen/images/tree-traversal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/tree-traversal.png -------------------------------------------------------------------------------- /doxygen/images/avl-tree-rotations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/avl-tree-rotations.png -------------------------------------------------------------------------------- /doxygen/images/avl-tree.graffle.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/avl-tree.graffle.zip -------------------------------------------------------------------------------- /doxygen/images/linked-lists.graffle.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/linked-lists.graffle.zip -------------------------------------------------------------------------------- /doxygen/images/red-black-tree.graffle.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/red-black-tree.graffle.zip -------------------------------------------------------------------------------- /doxygen/images/tree-traversal.graffle.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/doxygen/images/tree-traversal.graffle.zip -------------------------------------------------------------------------------- /benchmarks/README.txt: -------------------------------------------------------------------------------- 1 | The .plot files in this directory are documents for the Plot program, which is 2 | free software that can be found at http://plot.micw.eu/. -------------------------------------------------------------------------------- /benchmarks/benchmark-CHDeque-CHQueue-CHStack.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinntaylor/CHDataStructures/HEAD/benchmarks/benchmark-CHDeque-CHQueue-CHStack.xls -------------------------------------------------------------------------------- /doxygen/publish_locally.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | cd .. 4 | doxygen doxygen/Doxyfile 5 | mkdir -p /Library/WebServer/Documents/CHDataStructures 6 | cp -R docs/ /Library/WebServer/Documents/CHDataStructures/ 7 | -------------------------------------------------------------------------------- /CHDataStructures.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/NSObject+TestUtilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+TestUtilities.h 3 | // CHDataStructures 4 | // 5 | // Copyright © 2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | @interface NSObject (TestUtilites) 11 | 12 | - (instancetype)copyUsingNSCoding; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /docs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redirect 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tools/trim_trailing_whitespace.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | 3 | import os.path 4 | import sys 5 | 6 | for arg in sys.argv[1:]: # skip this filename 7 | file = open(arg, "r") 8 | stripped = '\n'.join([line.rstrip(' \t\n') for line in file.readlines()]) 9 | stripped = stripped.replace("\n\n\n\n", "\n\n").replace("\n\n\n", "\n\n") 10 | file.close() 11 | 12 | file = open(arg, "w") 13 | file.write(stripped); 14 | file.close() -------------------------------------------------------------------------------- /test/NSObject+TestUtilities.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+TestUtilities.m 3 | // CHDataStructures 4 | // 5 | // Copyright © 2021, Quinn Taylor 6 | // 7 | 8 | #import "NSObject+TestUtilities.h" 9 | 10 | @implementation NSObject (TestUtilites) 11 | 12 | - (instancetype)copyUsingNSCoding { 13 | #pragma clang diagnostic push 14 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 15 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self]; 16 | return [[NSKeyedUnarchiver unarchiveObjectWithData:data] retain]; 17 | #pragma clang diagnostic pop 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /source/CHCircularBufferDeque.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHCircularBufferDeque.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 CHCircularBufferDeque.h 15 | A simple CHDeque implemented using a CHCircularBuffer. 16 | */ 17 | 18 | /** 19 | A simple CHDeque implemented using a CHCircularBuffer. 20 | */ 21 | @interface CHCircularBufferDeque : CHCircularBuffer 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /source/CHCircularBufferQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHCircularBufferQueue.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 CHCircularBufferQueue.h 15 | A simple CHQueue implemented using a CHCircularBuffer. 16 | */ 17 | 18 | /** 19 | A simple CHQueue implemented using a CHCircularBuffer. 20 | */ 21 | @interface CHCircularBufferQueue : CHCircularBuffer 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /source/CHCircularBufferQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // CHCircularBufferQueue.m 3 | // CHDataStructures 4 | // 5 | // Copyright © 2009-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | @implementation CHCircularBufferQueue 11 | 12 | - (BOOL)isEqual:(id)otherObject { 13 | if ([otherObject conformsToProtocol:@protocol(CHQueue)]) { 14 | return [self isEqualToQueue:otherObject]; 15 | } else { 16 | return NO; 17 | } 18 | } 19 | 20 | - (BOOL)isEqualToQueue:(id)otherQueue { 21 | return CHCollectionsAreEqual(self, otherQueue); 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /source/CHCircularBufferStack.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHCircularBufferStack.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 CHCircularBufferStack.h 15 | A simple CHStack implemented using a CHCircularBuffer. 16 | */ 17 | 18 | /** 19 | A simple CHStack implemented using a CHCircularBuffer. 20 | */ 21 | @interface CHCircularBufferStack : CHCircularBuffer 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /source/CHCircularBufferDeque.m: -------------------------------------------------------------------------------- 1 | // 2 | // CHCircularBufferDeque.h 3 | // CHDataStructures 4 | // 5 | // Copyright © 2009-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | @implementation CHCircularBufferDeque 11 | 12 | - (void)prependObject:(id)anObject { 13 | [self insertObject:anObject atIndex:0]; 14 | } 15 | 16 | - (void)appendObject:(id)anObject { 17 | [self insertObject:anObject atIndex:count]; 18 | } 19 | 20 | - (BOOL)isEqual:(id)otherObject { 21 | if ([otherObject conformsToProtocol:@protocol(CHDeque)]) { 22 | return [self isEqualToDeque:otherObject]; 23 | } else { 24 | return NO; 25 | } 26 | } 27 | 28 | - (BOOL)isEqualToDeque:(id)otherDeque { 29 | return CHCollectionsAreEqual(self, otherDeque); 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2008-2021 Quinn Taylor 2 | 3 | This source code is released under the ISC License. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 6 | 7 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 8 | -------------------------------------------------------------------------------- /resources/Info-Tests.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | CHDS 21 | CFBundleVersion 22 | 1.0 23 | 24 | 25 | -------------------------------------------------------------------------------- /resources/Info-Framework.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | CHDS 21 | CFBundleVersion 22 | 1.0 23 | 24 | 25 | -------------------------------------------------------------------------------- /source/CHListStack.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHListStack.h 3 | // CHDataStructures 4 | // 5 | // Copyright © 2008-2021, Quinn Taylor 6 | // Copyright © 2002, Phillip Morelock 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /** 15 | @file CHListStack.h 16 | A simple CHStack implemented using a CHSinglyLinkedList. 17 | */ 18 | 19 | /** 20 | A simple CHStack implemented using a CHSinglyLinkedList. A singly-linked list is a natural choice since objects are only inserted and removed at the top of the stack, which is easily modeled as the head of a linked list. Enumerating from the top of the stack to the bottom also follows the natural ordering of a linked list. 21 | */ 22 | @interface CHListStack : CHAbstractListCollection 23 | 24 | @end 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /source/CHBinaryHeap.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHBinaryHeap.h 3 | // CHDataStructures 4 | // 5 | // Copyright © 2009-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | /** 13 | @file CHBinaryHeap.h 14 | A CHHeap implemented using a CFBinaryHeapRef internally. 15 | */ 16 | 17 | /** 18 | A CHHeap implemented using a CFBinaryHeapRef internally. 19 | */ 20 | @interface CHBinaryHeap<__covariant ObjectType> : NSObject 21 | { 22 | CFBinaryHeapRef heap; // Used for storing objects in the heap. 23 | NSComparisonResult sortOrder; // Whether to sort objects ascending or not. 24 | unsigned long mutations; // Used to track mutations for NSFastEnumeration. 25 | } 26 | 27 | - (instancetype)initWithOrdering:(NSComparisonResult)order array:(NSArray *)array NS_DESIGNATED_INITIALIZER; 28 | 29 | @end 30 | 31 | NS_ASSUME_NONNULL_END 32 | -------------------------------------------------------------------------------- /source/CHCircularBufferStack.m: -------------------------------------------------------------------------------- 1 | // 2 | // CHCircularBufferStack.m 3 | // CHDataStructures 4 | // 5 | // Copyright © 2009-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | @implementation CHCircularBufferStack 11 | 12 | // Overridden from parent class to make the stack grow left from the last slot. 13 | - (BOOL)_insertBackToFront { 14 | return YES; 15 | } 16 | 17 | - (BOOL)isEqual:(id)otherObject { 18 | if ([otherObject conformsToProtocol:@protocol(CHStack)]) { 19 | return [self isEqualToStack:otherObject]; 20 | } else { 21 | return NO; 22 | } 23 | } 24 | 25 | - (BOOL)isEqualToStack:(id)otherStack { 26 | return CHCollectionsAreEqual(self, otherStack); 27 | } 28 | 29 | - (void)popObject { 30 | [self removeFirstObject]; 31 | } 32 | 33 | - (void)pushObject:(id)anObject { 34 | [self insertObject:anObject atIndex:0]; 35 | } 36 | 37 | - (id)topObject { 38 | return [self firstObject]; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /source/CHListDeque.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHListDeque.h 3 | // CHDataStructures 4 | // 5 | // Copyright © 2008-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | @file CHListDeque.h 15 | A simple CHDeque implemented using a CHDoublyLinkedList. 16 | */ 17 | 18 | /** 19 | A simple CHDeque implemented using a CHDoublyLinkedList. A doubly-linked list is a natural choice since a deque supports insertion and removal at both ends (removing from the tail is O(n) in a singly-linked list, but O(1) in a doubly-linked list) and enumerating objects from back to front (hopelessly inefficient in a singly-linked list). The trade-offs for these benefits are marginally higher storage cost and marginally slower operations due to handling reverse links. 20 | */ 21 | @interface CHListDeque : CHAbstractListCollection 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /source/CHListQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHListQueue.h 3 | // CHDataStructures 4 | // 5 | // Copyright © 2008-2021, Quinn Taylor 6 | // Copyright © 2002, Phillip Morelock 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /** 15 | @file CHListQueue.h 16 | A simple CHQueue implemented using a CHSinglyLinkedList. 17 | */ 18 | 19 | /** 20 | A simple CHQueue implemented using a CHSinglyLinkedList. A singly-linked list is a natural choice since a queue can only insert at one end (the back) and remove at the other end (the front). Since CHSinglyLinkedList tracks the tail node, both of these operations are O(1). Other queue operations generally only proceed from front to back, so the lack of reverse pointers is not problematic, and each object requires less storage space. 21 | */ 22 | @interface CHListQueue : CHAbstractListCollection 23 | 24 | @end 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /source/CHMutableSet.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHMutableSet.h 3 | // CHDataStructures 4 | // 5 | // Copyright © 2009-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | /** 13 | @file CHMutableSet.h 14 | 15 | A mutable set class. 16 | */ 17 | 18 | /** 19 | A mutable set class. 20 | 21 | A CFMutableSetRef is used internally to store the key-value pairs. Subclasses may choose to add other instance variables to enable a specific ordering of keys, override methods to modify behavior, and add methods to extend existing behaviors. However, all subclasses should behave like a standard Cocoa dictionary as much as possible, and document clearly when they do not. 22 | 23 | @note Any method inherited from NSSet or NSMutableSet is supported by this class and its children. Please see the documentation for those classes for details. 24 | */ 25 | @interface CHMutableSet<__covariant ObjectType> : NSMutableSet { 26 | CFMutableSetRef set; // A Core Foundation set. 27 | } 28 | 29 | - (instancetype)initWithCapacity:(NSUInteger)numItems NS_DESIGNATED_INITIALIZER; // Inherited from NSMutableSet 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /source/CHUnbalancedTree.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHUnbalancedTree.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 CHUnbalancedTree.h 15 | A generic, unbalanced implementation of CHSearchTree. 16 | */ 17 | 18 | /** 19 | A simple unbalanced binary tree that does not guarantee O(log n) access. The algorithms for insertion and removal have been adapted from code 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. 20 | 21 | Even though the tree is not balanced when items are added or removed, access is at worst linear if the tree essentially degenerates into a linked list. This class is fast, and without stack risk because it works without recursion. 22 | */ 23 | @interface CHUnbalancedTree : CHAbstractBinarySearchTree 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /source/CHListQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // CHListQueue.m 3 | // CHDataStructures 4 | // 5 | // Copyright © 2008-2021, Quinn Taylor 6 | // Copyright © 2002, Phillip Morelock 7 | // 8 | 9 | #import 10 | #import 11 | 12 | /** 13 | This implementation uses a CHSinglyLinkedList, since it's slightly faster than 14 | using a CHDoublyLinkedList, and requires a little less memory. Also, since it's 15 | a queue, it's unlikely that there is any need to enumerate over the object from 16 | back to front. 17 | */ 18 | @implementation CHListQueue 19 | 20 | - (id)_newList { 21 | return [[CHSinglyLinkedList alloc] init]; 22 | } 23 | 24 | - (void)addObject:(id)anObject { 25 | CHRaiseInvalidArgumentExceptionIfNil(anObject); 26 | [list addObject:anObject]; 27 | } 28 | 29 | - (id)firstObject { 30 | return [list firstObject]; 31 | } 32 | 33 | - (BOOL)isEqual:(id)otherObject { 34 | if ([otherObject conformsToProtocol:@protocol(CHQueue)]) { 35 | return [self isEqualToQueue:otherObject]; 36 | } else { 37 | return NO; 38 | } 39 | } 40 | 41 | - (BOOL)isEqualToQueue:(id)otherQueue { 42 | return CHCollectionsAreEqual(self, otherQueue); 43 | } 44 | 45 | - (id)lastObject { 46 | return [list lastObject]; 47 | } 48 | 49 | - (void)removeFirstObject { 50 | [list removeFirstObject]; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /source/CHListDeque.m: -------------------------------------------------------------------------------- 1 | // 2 | // CHListDeque.m 3 | // CHDataStructures 4 | // 5 | // Copyright © 2008-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | #import 10 | 11 | @implementation CHListDeque 12 | 13 | - (id)_newList { 14 | return [[CHDoublyLinkedList alloc] init]; 15 | } 16 | 17 | - (void)prependObject:(id)anObject { 18 | CHRaiseInvalidArgumentExceptionIfNil(anObject); 19 | [list prependObject:anObject]; 20 | } 21 | 22 | - (void)appendObject:(id)anObject { 23 | CHRaiseInvalidArgumentExceptionIfNil(anObject); 24 | [list addObject:anObject]; 25 | } 26 | 27 | - (id)firstObject { 28 | return [list firstObject]; 29 | } 30 | 31 | - (BOOL)isEqual:(id)otherObject { 32 | if ([otherObject conformsToProtocol:@protocol(CHDeque)]) { 33 | return [self isEqualToDeque:otherObject]; 34 | } else { 35 | return NO; 36 | } 37 | } 38 | 39 | - (BOOL)isEqualToDeque:(id)otherDeque { 40 | return CHCollectionsAreEqual(self, otherDeque); 41 | } 42 | 43 | - (id)lastObject { 44 | return [list lastObject]; 45 | } 46 | 47 | - (void)removeFirstObject { 48 | [list removeFirstObject]; 49 | } 50 | 51 | - (void)removeLastObject { 52 | [list removeLastObject]; 53 | } 54 | 55 | - (NSEnumerator *)reverseObjectEnumerator { 56 | return [(CHDoublyLinkedList *)list reverseObjectEnumerator]; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /source/CHMutableDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHMutableDictionary.h 3 | // CHDataStructures 4 | // 5 | // Copyright © 2009-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | HIDDEN CFMutableDictionaryRef CHDictionaryCreateMutable(NSUInteger initialCapacity) CF_RETURNS_RETAINED; 13 | 14 | /** 15 | @file CHMutableDictionary.h 16 | 17 | A mutable dictionary class. 18 | */ 19 | 20 | /** 21 | A mutable dictionary class. 22 | 23 | A CFMutableDictionaryRef is used internally to store the key-value pairs. Subclasses may choose to add other instance variables to enable a specific ordering of keys, override methods to modify behavior, and add methods to extend existing behaviors. However, all subclasses should behave like a standard Cocoa dictionary as much as possible, and document clearly when they do not. 24 | 25 | @note Any method inherited from NSDictionary or NSMutableDictionary is supported by this class and its children. Please see the documentation for those classes for details. 26 | 27 | @todo Implement @c -copy and @c -mutableCopy differently (so users can actually obtain an immutable copy) and make mutation methods aware of immutability? 28 | */ 29 | @interface CHMutableDictionary<__covariant KeyType, __covariant ObjectType> : NSMutableDictionary 30 | { 31 | CFMutableDictionaryRef dictionary; // A Core Foundation dictionary. 32 | } 33 | 34 | - (instancetype)initWithCapacity:(NSUInteger)numItems NS_DESIGNATED_INITIALIZER; // Inherited from NSMutableDictionary 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /source/CHListStack.m: -------------------------------------------------------------------------------- 1 | // 2 | // CHListStack.m 3 | // CHDataStructures 4 | // 5 | // Copyright © 2008-2021, Quinn Taylor 6 | // Copyright © 2002, Phillip Morelock 7 | // 8 | 9 | #import 10 | #import 11 | 12 | /** 13 | This implementation uses a CHSinglyLinkedList, since it's slightly faster than using a CHDoublyLinkedList, and requires a little less memory. Also, since it's a stack, it's unlikely that there is any need to enumerate over the object from bottom to top. 14 | */ 15 | @implementation CHListStack 16 | 17 | - (id)_newList { 18 | return [[CHSinglyLinkedList alloc] init]; 19 | } 20 | 21 | - (instancetype)initWithArray:(NSArray *)anArray { 22 | CHRaiseInvalidArgumentExceptionIfNil(anArray); 23 | self = [super initWithArray:@[]]; // Don't let superclass add objects, we prepend them below. 24 | if (self) { 25 | for (id anObject in anArray) { 26 | [list prependObject:anObject]; 27 | } 28 | } 29 | return self; 30 | } 31 | 32 | - (BOOL)isEqual:(id)otherObject { 33 | if ([otherObject conformsToProtocol:@protocol(CHStack)]) { 34 | return [self isEqualToStack:otherObject]; 35 | } else { 36 | return NO; 37 | } 38 | } 39 | 40 | - (BOOL)isEqualToStack:(id)otherStack { 41 | return CHCollectionsAreEqual(self, otherStack); 42 | } 43 | 44 | - (void)popObject { 45 | [list removeFirstObject]; 46 | } 47 | 48 | - (void)pushObject:(id)anObject { 49 | CHRaiseInvalidArgumentExceptionIfNil(anObject); 50 | [list prependObject:anObject]; 51 | } 52 | 53 | - (id)topObject { 54 | return [list firstObject]; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /source/CHAbstractListCollection.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHAbstractListCollection.h 3 | // CHDataStructures 4 | // 5 | // Copyright © 2008-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | /** 13 | @file CHAbstractListCollection.h 14 | An abstract class which implements common behaviors of list-based collections. 15 | */ 16 | 17 | /** 18 | An abstract class which implements common behaviors of list-based collections. This class has a single instance variable on which all the implemented methods act, and also conforms to several protocols: 19 | 20 | - NSCoding 21 | - NSCopying 22 | - NSFastEnumeration 23 | */ 24 | @interface CHAbstractListCollection<__covariant ObjectType> : NSObject 25 | { 26 | id list; // List used for storing contents of collection. 27 | } 28 | 29 | - (instancetype)initWithArray:(NSArray *)anArray NS_DESIGNATED_INITIALIZER; 30 | - (NSArray *)allObjects; 31 | - (BOOL)containsObject:(ObjectType)anObject; 32 | - (BOOL)containsObjectIdenticalTo:(ObjectType)anObject; 33 | - (NSUInteger)count; 34 | - (void)exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2; 35 | - (NSUInteger)indexOfObject:(ObjectType)anObject; 36 | - (NSUInteger)indexOfObjectIdenticalTo:(ObjectType)anObject; 37 | - (id)objectAtIndex:(NSUInteger)index; 38 | - (NSEnumerator *)objectEnumerator; 39 | - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes; 40 | - (void)removeAllObjects; 41 | - (void)removeObject:(ObjectType)anObject; 42 | - (void)removeObjectAtIndex:(NSUInteger)index; 43 | - (void)removeObjectIdenticalTo:(ObjectType)anObject; 44 | - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes; 45 | - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(ObjectType)anObject; 46 | 47 | @end 48 | 49 | NS_ASSUME_NONNULL_END 50 | -------------------------------------------------------------------------------- /source/CHCircularBuffer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHCircularBuffer.h 3 | // CHDataStructures 4 | // 5 | // Copyright © 2009-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | /** 13 | @file CHCircularBuffer.h 14 | 15 | A circular buffer array. 16 | */ 17 | 18 | /** 19 | A circular buffer is a structure that emulates a continuous ring of N data slots. This class uses a C array and tracks the indexes of the front and back elements in the buffer, such that the first element is treated as logical index 0 regardless of where it is actually stored. The buffer dynamically expands to accommodate added objects. This type of storage is ideal for scenarios where objects are added and removed only at one or both ends (such as a stack or queue) but still supports all normal NSMutableArray functionality. 20 | 21 | @note Any method inherited from NSArray or NSMutableArray is supported by this class and its children. Please see the documentation for those classes for details. 22 | */ 23 | @interface CHCircularBuffer<__covariant ObjectType> : NSMutableArray 24 | { 25 | __strong id *array; // Primitive C array for storing collection contents. 26 | NSUInteger arrayCapacity; // How many pointers @a array can accommodate. 27 | NSUInteger count; // The number of objects currently in the buffer. 28 | NSUInteger headIndex; // The array index of the first object. 29 | NSUInteger tailIndex; // The array index after the last object. 30 | unsigned long mutations; // Tracks mutations for NSFastEnumeration. 31 | } 32 | 33 | - (instancetype)initWithCapacity:(NSUInteger)capacity NS_DESIGNATED_INITIALIZER; // Inherited from NSMutableArray 34 | 35 | - (NSArray *)allObjects; 36 | - (BOOL)containsObjectIdenticalTo:(ObjectType)anObject; 37 | - (void)removeFirstObject; 38 | - (void)removeLastObject; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /CHDataStructures.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CHDataStructures' 3 | s.version = '0.0.1' 4 | s.homepage = 'https://github.com/quinntaylor/CHDataStructures' 5 | s.summary = 'Library of standard data structures like (queues, stacks, and trees) based on protocols.' 6 | s.description = 'Have you ever lamented the absence of queues, stacks, and trees from the Cocoa API? How many times have you considered writing your own, or scoured the web to see if someone else had? Well, look no further, the work has been done for you! CHDataStructures is a library of standard data structures which can be used in any Objective-C program, for educational purposes, or as a foundation for other data structures to build on. Data structures in this library adopt Objective-C protocols that define the functionality of and API for interacting with any implementation thereof, regardless of its internals.' 7 | s.author = { 'Quinn Taylor' => '@quinntaylor'} 8 | s.source = { :git => 'https://github.com/quinntaylor/CHDataStructures.git', :commit => 'e2321caf29b897ed4df4dd4b86e4189ce6e55375'} 9 | s.source_files = 'source' 10 | s.license = {:type => 'ISC Variant', :text => <<-LICENSE 11 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 12 | 13 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 14 | LICENSE 15 | } 16 | s.requires_arc = false 17 | end 18 | -------------------------------------------------------------------------------- /source/CHUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // CHUtil.m 3 | // CHDataStructures 4 | // 5 | // Copyright © 2008-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | size_t kCHPointerSize = sizeof(void *); 11 | 12 | BOOL CHObjectsAreEqual(id o1, id o2) { 13 | return [o1 isEqual:o2]; 14 | } 15 | 16 | BOOL CHObjectsAreIdentical(id o1, id o2) { 17 | return (o1 == o2); 18 | } 19 | 20 | BOOL CHCollectionsAreEqual(id collection1, id collection2) { 21 | if ((collection1 && ![collection1 respondsToSelector:@selector(count)]) || 22 | (collection2 && ![collection2 respondsToSelector:@selector(count)])) 23 | { 24 | [NSException raise:NSInvalidArgumentException 25 | format:@"Parameter does not respond to -count selector."]; 26 | } 27 | if (collection1 == collection2) { 28 | return YES; 29 | } 30 | if ([collection1 count] != [collection2 count]) { 31 | return NO; 32 | } 33 | NSEnumerator *otherObjects = [collection2 objectEnumerator]; 34 | for (id anObject in collection1) { 35 | if (![anObject isEqual:[otherObjects nextObject]]) { 36 | return NO; 37 | } 38 | } 39 | return YES; 40 | } 41 | 42 | NSUInteger CHHashOfCountAndObjects(NSUInteger count, id object1, id object2) { 43 | NSUInteger hash = 17 * count ^ (count << 16); 44 | return hash ^ (31*[object1 hash]) ^ ((31*[object2 hash]) << 4); 45 | } 46 | 47 | #pragma mark - 48 | 49 | void CHQuietLog(NSString *format, ...) { 50 | if (format == nil) { 51 | printf("(null)\n"); 52 | return; 53 | } 54 | // Get a reference to the arguments that follow the format parameter 55 | va_list argList; 56 | va_start(argList, format); 57 | // Do format string argument substitution, reinstate %% escapes, then print 58 | NSMutableString *string = [[NSMutableString alloc] initWithFormat:format 59 | arguments:argList]; 60 | va_end(argList); 61 | NSRange range; 62 | range.location = 0; 63 | range.length = [string length]; 64 | [string replaceOccurrencesOfString:@"%%" withString:@"%%%%" options:0 range:range]; 65 | printf("%s\n", [string UTF8String]); 66 | [string release]; 67 | } 68 | -------------------------------------------------------------------------------- /Configs/CHDataStructures.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // CHDataStructures.xcconfig 3 | // CHDataStructures 4 | // 5 | 6 | // Configuration settings file format documentation can be found at: 7 | // https://help.apple.com/xcode/#/dev745c5c974 8 | 9 | // Architectures 10 | SDKROOT = iphoneos 11 | 12 | SUPPORTED_PLATFORMS = iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator macosx 13 | 14 | // Deployment 15 | TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2 16 | TARGETED_DEVICE_FAMILY[sdk=appletv*] = 3,5 17 | TARGETED_DEVICE_FAMILY[sdk=watch*] = 4 18 | TARGETED_DEVICE_FAMILY[sdk=mac*] = 19 | 20 | IPHONEOS_DEPLOYMENT_TARGET = 12.0 21 | MACOSX_DEPLOYMENT_TARGET = 11.0 22 | 23 | // Search Paths 24 | ALWAYS_SEARCH_USER_PATHS = NO 25 | 26 | // Signing 27 | CODE_SIGN_IDENTITY = - 28 | CODE_SIGN_STYLE = Automatic 29 | 30 | // Apple Clang - Language - Modules 31 | CLANG_ENABLE_MODULES = YES 32 | CLANG_MODULES_AUTOLINK = NO 33 | 34 | // Apple Clang - Language - Objective-C 35 | CLANG_ENABLE_OBJC_WEAK = YES 36 | 37 | COMBINE_HIDPI_IMAGES = YES 38 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 39 | ENABLE_STRICT_OBJC_MSGSEND = YES 40 | GCC_C_LANGUAGE_STANDARD = gnu99 41 | PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR = NO 42 | 43 | CLANG_WARN_ASSIGN_ENUM = YES 44 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES 45 | CLANG_WARN_BOOL_CONVERSION = YES 46 | CLANG_WARN_COMMA = YES 47 | CLANG_WARN_CONSTANT_CONVERSION = YES 48 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES 49 | CLANG_WARN_EMPTY_BODY = YES 50 | CLANG_WARN_ENUM_CONVERSION = YES 51 | CLANG_WARN_INFINITE_RECURSION = YES 52 | CLANG_WARN_INT_CONVERSION = YES 53 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES 54 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 55 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES 56 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES 57 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES 58 | CLANG_WARN_STRICT_PROTOTYPES = YES_ERROR 59 | CLANG_WARN_SUSPICIOUS_MOVE = YES 60 | CLANG_WARN_UNREACHABLE_CODE = YES 61 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 62 | 63 | GCC_NO_COMMON_BLOCKS = YES 64 | GCC_WARN_UNDECLARED_SELECTOR = YES 65 | GCC_WARN_UNINITIALIZED_AUTOS = YES 66 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES 67 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR 68 | GCC_WARN_SIGN_COMPARE = YES 69 | GCC_WARN_UNUSED_FUNCTION = YES 70 | GCC_WARN_UNUSED_VARIABLE = YES 71 | -------------------------------------------------------------------------------- /source/CHMutableArrayHeap.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHMutableArrayHeap.h 3 | // CHDataStructures 4 | // 5 | // Copyright © 2008-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | /** 13 | @file CHMutableArrayHeap.h 14 | A simple CHHeap implemented as a subclass of NSMutableArray. 15 | */ 16 | 17 | /** 18 | A simple CHHeap implemented as a subclass of NSMutableArray. 19 | */ 20 | @interface CHMutableArrayHeap<__covariant ObjectType> : NSMutableArray { 21 | NSMutableArray *array; // An array to use for storing objects in the heap. 22 | NSComparisonResult sortOrder; // Whether to sort objects ascending or not. 23 | unsigned long mutations; // Used to track mutations for NSFastEnumeration. 24 | } 25 | 26 | - (instancetype)initWithCapacity:(NSUInteger)capacity NS_DESIGNATED_INITIALIZER; // Inherited from NSMutableArray 27 | - (instancetype)initWithOrdering:(NSComparisonResult)order array:(NSArray *)array NS_DESIGNATED_INITIALIZER; 28 | 29 | /** 30 | Determine whether the receiver contains a given object, matched using the == operator. 31 | 32 | @param anObject The object to test for membership in the heap. 33 | @return @c YES if @a anObject is in the heap, otherwise @c NO. 34 | 35 | @see containsObject: 36 | @see removeObjectIdenticalTo: 37 | */ 38 | - (BOOL)containsObjectIdenticalTo:(ObjectType)anObject; 39 | 40 | /** 41 | Remove @b all occurrences of @a anObject, matched using @c isEqual:. 42 | 43 | @param anObject The object to be removed from the heap. 44 | 45 | If the heap is empty, @a anObject is @c nil, or no object matching @a anObject is found, there is no effect, aside from the possible overhead of searching the contents. 46 | 47 | @see containsObject; 48 | @see removeAllObjects 49 | @see removeObjectIdenticalTo: 50 | */ 51 | - (void)removeObject:(ObjectType)anObject; 52 | 53 | /** 54 | Remove @b all occurrences of @a anObject, matched using the == operator. 55 | 56 | @param anObject The object to be removed from the heap. 57 | 58 | If the heap is empty, @a anObject is @c nil, or no object matching @a anObject is found, there is no effect, aside from the possible overhead of searching the contents. 59 | 60 | @see containsObjectIdenticalTo: 61 | @see removeAllObjects 62 | @see removeObject: 63 | */ 64 | - (void)removeObjectIdenticalTo:(ObjectType)anObject; 65 | 66 | @end 67 | 68 | NS_ASSUME_NONNULL_END 69 | -------------------------------------------------------------------------------- /source/CHSortedDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CHSortedDictionary.m 3 | // CHDataStructures 4 | // 5 | // Copyright © 2009-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | #import 10 | 11 | @implementation CHSortedDictionary 12 | 13 | - (void)dealloc { 14 | [sortedKeys release]; 15 | [super dealloc]; 16 | } 17 | 18 | - (instancetype)initWithCapacity:(NSUInteger)numItems { 19 | self = [super initWithCapacity:numItems]; 20 | if (self) { 21 | sortedKeys = [[CHAVLTree alloc] init]; 22 | } 23 | return self; 24 | } 25 | 26 | // The NSCoding methods inherited from CHMutableDictionary work fine here. 27 | 28 | #pragma mark 29 | 30 | /** @test Add unit test. */ 31 | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len { 32 | return [sortedKeys countByEnumeratingWithState:state objects:stackbuf count:len]; 33 | } 34 | 35 | #pragma mark Querying Contents 36 | 37 | - (id)firstKey { 38 | return [sortedKeys firstObject]; 39 | } 40 | 41 | - (NSUInteger)hash { 42 | return CHHashOfCountAndObjects([sortedKeys count], [sortedKeys firstObject], [sortedKeys lastObject]); 43 | } 44 | 45 | - (id)lastKey { 46 | return [sortedKeys lastObject]; 47 | } 48 | 49 | - (NSEnumerator *)keyEnumerator { 50 | return [sortedKeys objectEnumerator]; 51 | } 52 | 53 | - (NSEnumerator *)reverseKeyEnumerator { 54 | return [sortedKeys reverseObjectEnumerator]; 55 | } 56 | 57 | - (NSMutableDictionary *)subsetFromKey:(id)start 58 | toKey:(id)end 59 | options:(CHSubsetConstructionOptions)options 60 | { 61 | id keySubset = [sortedKeys subsetFromObject:start toObject:end options:options]; 62 | NSMutableDictionary *subset = [[[[self class] alloc] initWithCapacity:[keySubset count]] autorelease]; 63 | for (id aKey in keySubset) { 64 | [subset setObject:[self objectForKey:aKey] forKey:aKey]; 65 | } 66 | return subset; 67 | } 68 | 69 | #pragma mark Modifying Contents 70 | 71 | - (void)removeAllObjects { 72 | [super removeAllObjects]; 73 | [sortedKeys removeAllObjects]; 74 | } 75 | 76 | - (void)removeObjectForKey:(id)aKey { 77 | if (CFDictionaryContainsKey(dictionary, aKey)) { 78 | [super removeObjectForKey:aKey]; 79 | [sortedKeys removeObject:aKey]; 80 | } 81 | } 82 | 83 | - (void)setObject:(id)anObject forKey:(id)aKey { 84 | CHRaiseInvalidArgumentExceptionIfNil(anObject); 85 | CHRaiseInvalidArgumentExceptionIfNil(aKey); 86 | id clonedKey = [[aKey copy] autorelease]; 87 | [sortedKeys addObject:clonedKey]; 88 | CFDictionarySetValue(dictionary, clonedKey, anObject); 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /CHDataStructures.xcodeproj/xcshareddata/xcschemes/All.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /CHDataStructures.xcodeproj/xcshareddata/xcschemes/Deployment.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /CHDataStructures.xcodeproj/xcshareddata/xcschemes/Documentation.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /source/CHUnbalancedTree.m: -------------------------------------------------------------------------------- 1 | // 2 | // CHUnbalancedTree.m 3 | // CHDataStructures 4 | // 5 | // Copyright © 2008-2021, Quinn Taylor 6 | // Copyright © 2002, Phillip Morelock 7 | // 8 | 9 | #import 10 | #import "CHAbstractBinarySearchTree_Internal.h" 11 | 12 | @implementation CHUnbalancedTree 13 | 14 | - (void)addObject:(id)anObject { 15 | CHRaiseInvalidArgumentExceptionIfNil(anObject); 16 | ++mutations; 17 | 18 | CHBinaryTreeNode *parent = header, *current = header->right; 19 | sentinel->object = anObject; // Assure that we find a spot to insert 20 | NSComparisonResult comparison; 21 | while ((comparison = [current->object compare:anObject])) { 22 | parent = current; 23 | current = current->link[comparison == NSOrderedAscending]; // R on YES 24 | } 25 | 26 | [anObject retain]; // Must retain whether replacing value or adding new node 27 | if (current != sentinel) { 28 | // Replace the existing object with the new object. 29 | [current->object release]; 30 | current->object = anObject; 31 | } else { 32 | // Create a new node to hold the value being inserted 33 | current = [self _createNodeWithObject:anObject]; 34 | ++count; 35 | // Link from parent as the proper child, based on last comparison 36 | comparison = [parent->object compare:anObject]; // restore prior compare 37 | parent->link[comparison == NSOrderedAscending] = current; 38 | } 39 | } 40 | 41 | 42 | // Removal is guaranteed to not make the tree deeper/taller, since it uses the 43 | // "min of the right subtree" algorithm if the node to be removed has 2 children. 44 | - (void)removeObject:(id)anObject { 45 | CHRaiseInvalidArgumentExceptionIfNil(anObject); 46 | if (count == 0) { 47 | return; 48 | } 49 | ++mutations; 50 | 51 | CHBinaryTreeNode *parent = nil, *current = header; 52 | 53 | sentinel->object = anObject; // Assure that we find a spot to insert 54 | NSComparisonResult comparison; 55 | while ((comparison = [current->object compare:anObject])) { 56 | parent = current; 57 | current = current->link[comparison == NSOrderedAscending]; // R on YES 58 | } 59 | NSAssert(parent != nil, @"Illegal state, parent should never be nil!"); 60 | // Exit if the specified node was not found in the tree. 61 | if (current == sentinel) { 62 | return; 63 | } 64 | [current->object release]; // Object must be released in any case 65 | --count; 66 | if (current->left == sentinel || current->right == sentinel) { 67 | // One or both of the child pointers are null, so removal is simpler 68 | parent->link[parent->right == current] 69 | = current->link[current->left == sentinel]; 70 | free(current); 71 | } else { 72 | // The most complex case: removing a node with 2 non-null children 73 | // (Replace object with the leftmost object in the right subtree.) 74 | parent = current; 75 | CHBinaryTreeNode *replacement = current->right; 76 | while (replacement->left != sentinel) { 77 | parent = replacement; 78 | replacement = replacement->left; 79 | } 80 | current->object = replacement->object; 81 | parent->link[parent->right == replacement] = replacement->right; 82 | free(replacement); 83 | } 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /source/CHBidirectionalDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CHBidirectionalDictionary.m 3 | // CHDataStructures 4 | // 5 | // Copyright © 2010-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | @implementation CHBidirectionalDictionary 11 | 12 | // This macro is used as an alias for the 'dictionary' ivar in the parent class. 13 | #define keysToObjects dictionary 14 | 15 | - (void)dealloc { 16 | if (inverse != nil) { 17 | inverse->inverse = nil; // Unlink from inverse dictionary if one exists. 18 | } 19 | CFRelease(objectsToKeys); // The dictionary can never be null at this point. 20 | [super dealloc]; 21 | } 22 | 23 | - (instancetype)initWithCapacity:(NSUInteger)numItems { 24 | self = [super initWithCapacity:numItems]; 25 | if (self) { 26 | objectsToKeys = CHDictionaryCreateMutable(numItems); 27 | } 28 | return self; 29 | } 30 | 31 | #pragma mark Querying Contents 32 | 33 | /** @todo Determine the proper ownership/lifetime of the inverse dictionary. */ 34 | - (CHBidirectionalDictionary *)inverseDictionary { 35 | if (inverse == nil) { 36 | // Create a new instance of this class to represent the inverse mapping 37 | inverse = [[CHBidirectionalDictionary alloc] init]; 38 | // Release the CFMutableDictionary -init creates so we don't leak memory 39 | CFRelease(inverse->dictionary); 40 | // Set its dictionary references to the reverse of what they are here 41 | CFRetain(inverse->keysToObjects = objectsToKeys); 42 | CFRetain(inverse->objectsToKeys = keysToObjects); 43 | // Set this instance as the mutual inverse of the newly-created instance 44 | inverse->inverse = self; 45 | } 46 | return inverse; 47 | } 48 | 49 | - (id)keyForObject:(id)anObject { 50 | CHRaiseInvalidArgumentExceptionIfNil(anObject); 51 | return (id)CFDictionaryGetValue(objectsToKeys, anObject); 52 | } 53 | 54 | - (NSEnumerator *)objectEnumerator { 55 | return [(id)objectsToKeys keyEnumerator]; 56 | } 57 | 58 | #pragma mark Modifying Contents 59 | 60 | - (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary { 61 | [super addEntriesFromDictionary:otherDictionary]; 62 | } 63 | 64 | - (void)removeAllObjects { 65 | [super removeAllObjects]; 66 | CFDictionaryRemoveAllValues(objectsToKeys); 67 | } 68 | 69 | - (void)removeKeyForObject:(id)anObject { 70 | CHRaiseInvalidArgumentExceptionIfNil(anObject); 71 | id key = [self keyForObject:anObject]; 72 | if (key) { 73 | [super removeObjectForKey:[self keyForObject:anObject]]; 74 | CFDictionaryRemoveValue(objectsToKeys, anObject); 75 | } 76 | } 77 | 78 | - (void)removeObjectForKey:(id)aKey { 79 | CFDictionaryRemoveValue(objectsToKeys, [self objectForKey:aKey]); 80 | [super removeObjectForKey:aKey]; 81 | } 82 | 83 | - (void)setObject:(id)anObject forKey:(id)aKey { 84 | CHRaiseInvalidArgumentExceptionIfNil(anObject); 85 | CHRaiseInvalidArgumentExceptionIfNil(aKey); 86 | // Remove existing mappings for aKey and anObject if they currently exist. 87 | CFDictionaryRemoveValue(keysToObjects, CFDictionaryGetValue(objectsToKeys, anObject)); 88 | CFDictionaryRemoveValue(objectsToKeys, CFDictionaryGetValue(keysToObjects, aKey)); 89 | aKey = [[aKey copy] autorelease]; 90 | anObject = [[anObject copy] autorelease]; 91 | CFDictionarySetValue(keysToObjects, aKey, anObject); // May replace key-value pair 92 | CFDictionarySetValue(objectsToKeys, anObject, aKey); // May replace value-key pair 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /source/CHMultiDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CHMultiDictionary.m 3 | // CHDataStructures 4 | // 5 | // Copyright © 2008-2021, Quinn Taylor 6 | // 7 | 8 | #import 9 | 10 | /** 11 | Utility function for creating a new NSMutableSet containing object; if object is a set or array, the set contains all objects in the collection. 12 | */ 13 | static NSMutableSet * createMutableSetFromObject(id object) { 14 | if (object == nil) { 15 | return nil; 16 | } else if ([object isKindOfClass:[NSSet class]]) { 17 | return [NSMutableSet setWithSet:object]; 18 | } else if ([object isKindOfClass:[NSArray class]]) { 19 | return [NSMutableSet setWithArray:object]; 20 | } else { 21 | return [NSMutableSet setWithObject:object]; 22 | } 23 | } 24 | 25 | #pragma mark - 26 | 27 | @implementation CHMultiDictionary 28 | 29 | - (instancetype)initWithObjects:(NSArray *)objectsArray forKeys:(NSArray *)keyArray { 30 | if ([keyArray count] != [objectsArray count]) { 31 | CHRaiseInvalidArgumentException(@"Unequal array counts."); 32 | } 33 | self = [super initWithCapacity:[objectsArray count]]; 34 | if (self) { 35 | NSEnumerator *objects = [objectsArray objectEnumerator]; 36 | for (id key in keyArray) { 37 | [self setObject:[objects nextObject] forKey:key]; 38 | } 39 | } 40 | return self; 41 | } 42 | 43 | #pragma mark Querying Contents 44 | 45 | - (NSUInteger)countForAllKeys { 46 | return objectCount; 47 | } 48 | 49 | - (NSUInteger)countForKey:(id)aKey { 50 | return [[self objectForKey:aKey] count]; 51 | } 52 | 53 | - (NSSet *)objectsForKey:(id)aKey { 54 | return [[[(id)dictionary objectForKey:aKey] copy] autorelease]; 55 | } 56 | 57 | #pragma mark Modifying Contents 58 | 59 | - (void)addObject:(id)anObject forKey:(id)aKey { 60 | NSMutableSet *objects = [self objectForKey:aKey]; 61 | if (objects == nil) { 62 | [super setObject:(objects = [NSMutableSet set]) forKey:aKey]; 63 | } else { 64 | objectCount -= [objects count]; 65 | } 66 | [objects addObject:anObject]; 67 | objectCount += [objects count]; 68 | } 69 | 70 | - (void)addObjects:(NSSet *)objectSet forKey:(id)aKey { 71 | NSMutableSet *objects = [self objectForKey:aKey]; 72 | if (objects == nil) { 73 | [super setObject:(objects = [NSMutableSet set]) forKey:aKey]; 74 | } else { 75 | objectCount -= [objects count]; 76 | } 77 | [objects unionSet:objectSet]; 78 | objectCount += [objects count]; 79 | } 80 | 81 | - (void)removeAllObjects { 82 | [super removeAllObjects]; 83 | objectCount = 0; 84 | } 85 | 86 | - (void)removeObject:(id)anObject forKey:(id)aKey { 87 | NSMutableSet *objects = [self objectForKey:aKey]; 88 | if ([objects containsObject:anObject]) { 89 | [objects removeObject:anObject]; 90 | --objectCount; 91 | if ([objects count] == 0) { 92 | [self removeObjectForKey:aKey]; 93 | } 94 | } 95 | } 96 | 97 | - (void)removeObjectsForKey:(id)aKey { 98 | objectCount -= [[self objectForKey:aKey] count]; 99 | [self removeObjectForKey:aKey]; 100 | } 101 | 102 | - (void)setObject:(id)anObject forKey:(id)aKey { 103 | NSSet *objectSet = createMutableSetFromObject(anObject); 104 | if (aKey != nil) { 105 | objectCount += ([objectSet count] - [[self objectForKey:aKey] count]); 106 | } 107 | [super setObject:objectSet forKey:aKey]; 108 | } 109 | 110 | - (void)setObjects:(NSSet *)objectSet forKey:(id)aKey { 111 | [self setObject:objectSet forKey:aKey]; 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /source/CHRedBlackTree.h: -------------------------------------------------------------------------------- 1 | // 2 | // CHRedBlackTree.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 | #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 |
  1. Every node is red or black.
  2. 27 |
  3. All leaves (null children) are black, even when the parent is black.
  4. 28 |
  5. If a node is red, both of its children must be black.
  6. 29 |
  7. Every path from a node to its leaves has the same number of black nodes.
  8. 30 |
  9. The root of the tree is black. (Optional, but simplifies things.)
  10. 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 |
  1. The level of a leaf node is one.
  2. 26 |
  3. The level of a left child is less than that of its parent.
  4. 27 |
  5. The level of a right child is less than or equal to that of its parent.
  6. 28 |
  7. The level of a right grandchild is less than that of its grandparent.
  8. 29 |
  9. Every node of level greater than one must have two children.
  10. 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 | 42 | 57 |
40 | @image html tree-traversal.png "Figure 1 — A sample binary search tree." 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
Traversal Visit Order Node Ordering CHTraversalOrder
In-order L, node, R A B C D E F G H I CHTraversalOrderAscending
Reverse-order R, node, L I H G F E D C B A CHTraversalOrderDescending
Pre-order node, L, R F B A D C E G I H CHTraversalOrderPreOrder
Post-order L, R, node A C E D B H I G F CHTraversalOrderPostOrder
Level-order L→R, T→B F B G A D I C E H CHTraversalOrderLevelOrder
54 |

Table 1 - Various traversals as performed on the tree in Figure 1.

55 | 56 |
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 | --------------------------------------------------------------------------------