├── .gitignore ├── ConsoleBenchmark ├── App.config ├── Casts.cs ├── ConsoleBenchmark.csproj ├── Program.cs └── packages.config ├── HandyCollections.ncrunchsolution ├── HandyCollections.sln ├── HandyCollections.vsmdi ├── HandyCollections ├── BinaryTree │ ├── BinaryTree.cs │ └── SplayTree.cs ├── BloomFilter │ ├── BloomFilter.cs │ ├── BloomFilterSlice.cs │ ├── CountingBloomFilter.cs │ ├── IBloomFilter.cs │ └── ScalableBloomFilter.cs ├── Extensions │ ├── IEnumerableExtensions.cs │ ├── IListExtensions.cs │ └── IReadOnlyListExtensions.cs ├── Geometry │ ├── GeometricTree.cs │ ├── Octree.cs │ └── Quadtree.cs ├── HandyCollections.csproj ├── HandyCollections.ncrunchproject ├── HandyCollections.nuspec ├── Heap │ ├── IMinHeap.cs │ ├── MinHeap.cs │ ├── MinMaxHeap.cs │ └── MinMaxMedianHeap.cs ├── RandomNumber │ ├── LinearFeedbackShiftRegister16.cs │ ├── LinearFeedbackShiftRegister32.cs │ └── StaticRandomNumber.cs ├── RecentlyUsedQueue.cs ├── RingBuffer.cs ├── Set │ └── OrderedSet.cs ├── TypedWeakReference.cs └── packages.config ├── HandyCollectionsTest ├── BinaryTreeTest.cs ├── BloomFilterTest.cs ├── Extensions.cs ├── Extensions │ └── IListExtensionsTest.cs ├── Geometry │ ├── OctreeTest.cs │ └── QuadtreeTest.cs ├── HandyCollectionsTest.csproj ├── HandyCollectionsTest.ncrunchproject ├── Heap │ ├── MinHeapTest.cs │ ├── MinMaxHeapTest.cs │ └── MinMaxMedianHeapTest.cs ├── LinearFeedbackShiftRegisterTest.cs ├── Properties │ └── AssemblyInfo.cs ├── RecentlyUsedQueue.cs ├── RingBufferTest.cs ├── Set │ └── OrderedSetTest.cs ├── SplayTreeTest.cs ├── TypedWeakReference.cs └── packages.config ├── Local.testsettings ├── TraceAndTestImpact.testsettings ├── license.md └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | *.cachefile 4 | *.xnb 5 | *.sln.docstates 6 | *.cache 7 | *.pyc 8 | *.ncrunchsolution 9 | *.ncrunchproject 10 | 11 | _Resharper.* 12 | packages 13 | 14 | ConsoleBenchmark/bin 15 | ConsoleBenchmark/obj 16 | 17 | HandyCollectionsTest/bin 18 | HandyCollectionsTest/obj 19 | 20 | HandyCollections/bin 21 | HandyCollections/obj 22 | /.vs/HandyCollections/v16/TestStore/0/*.manifest 23 | /HandyCollections/push-all.ps1 24 | -------------------------------------------------------------------------------- /ConsoleBenchmark/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ConsoleBenchmark/Casts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using BenchmarkDotNet.Attributes; 4 | 5 | namespace ConsoleBenchmark 6 | { 7 | public class Casts 8 | { 9 | private static void Check(uint u, int i) 10 | { 11 | var union = new Union32 { A = i }; 12 | 13 | if (union.B != u) 14 | throw new NotImplementedException(); 15 | } 16 | 17 | [Benchmark] 18 | public static void Ptr() 19 | { 20 | for (var i = -1000; i < 1000; i++) 21 | { 22 | uint x; 23 | unsafe 24 | { 25 | x = *(uint*)&i; 26 | } 27 | 28 | Check(x, i); 29 | } 30 | } 31 | 32 | [Benchmark] 33 | public static void Cast() 34 | { 35 | for (var i = -1000; i < 1000; i++) 36 | { 37 | var x = unchecked((uint)i); 38 | 39 | Check(x, i); 40 | } 41 | } 42 | 43 | [StructLayout(LayoutKind.Explicit)] 44 | private struct Union32 45 | { 46 | [FieldOffset(0)] 47 | public int A; 48 | 49 | [FieldOffset(0)] 50 | public uint B; 51 | } 52 | 53 | [Benchmark] 54 | public static void Union() 55 | { 56 | var u = new Union32(); 57 | 58 | for (var i = -1000; i < 1000; i++) 59 | { 60 | u.A = i; 61 | 62 | var x = u.B; 63 | 64 | Check(x, i); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ConsoleBenchmark/ConsoleBenchmark.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | enable 5 | Exe 6 | net5.0 7 | 8 | 9 | 10 | true 11 | 12 | 13 | 14 | true 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ConsoleBenchmark/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BenchmarkDotNet.Running; 3 | 4 | namespace ConsoleBenchmark 5 | { 6 | public class Program 7 | { 8 | private static void Main() 9 | { 10 | BenchmarkRunner.Run(); 11 | Console.ReadLine(); 12 | } 13 | 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ConsoleBenchmark/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /HandyCollections.ncrunchsolution: -------------------------------------------------------------------------------- 1 | 2 | 1 3 | True 4 | true 5 | true 6 | UseDynamicAnalysis 7 | UseStaticAnalysis 8 | UseStaticAnalysis 9 | UseStaticAnalysis 10 | Run all tests automatically:BFRydWU=;Run all tests manually:BUZhbHNl;Run impacted tests automatically, others manually (experimental!):CklzSW1wYWN0ZWQ=;Run pinned tests automatically, others manually:CElzUGlubmVk 11 | 12 | HandyCollectionsTest\HandyCollectionsTest.csproj 13 | 14 | -------------------------------------------------------------------------------- /HandyCollections.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HandyCollections", "HandyCollections\HandyCollections.csproj", "{C18B7765-C1F8-4769-A114-291D0BDB5865}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HandyCollectionsTest", "HandyCollectionsTest\HandyCollectionsTest.csproj", "{B3A032F2-4168-4544-8B3C-2146B925E309}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0A768634-F50B-4406-8559-199F514BE340}" 11 | ProjectSection(SolutionItems) = preProject 12 | HandyCollections.vsmdi = HandyCollections.vsmdi 13 | Local.testsettings = Local.testsettings 14 | TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings 15 | EndProjectSection 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleBenchmark", "ConsoleBenchmark\ConsoleBenchmark.csproj", "{9A5CA4E2-8662-48C2-A153-D65FE69B22B0}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {C18B7765-C1F8-4769-A114-291D0BDB5865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {C18B7765-C1F8-4769-A114-291D0BDB5865}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {C18B7765-C1F8-4769-A114-291D0BDB5865}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {C18B7765-C1F8-4769-A114-291D0BDB5865}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {B3A032F2-4168-4544-8B3C-2146B925E309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {B3A032F2-4168-4544-8B3C-2146B925E309}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {B3A032F2-4168-4544-8B3C-2146B925E309}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {B3A032F2-4168-4544-8B3C-2146B925E309}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {9A5CA4E2-8662-48C2-A153-D65FE69B22B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {9A5CA4E2-8662-48C2-A153-D65FE69B22B0}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {9A5CA4E2-8662-48C2-A153-D65FE69B22B0}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {9A5CA4E2-8662-48C2-A153-D65FE69B22B0}.Release|Any CPU.Build.0 = Release|Any CPU 37 | EndGlobalSection 38 | GlobalSection(SolutionProperties) = preSolution 39 | HideSolutionNode = FALSE 40 | EndGlobalSection 41 | GlobalSection(ExtensibilityGlobals) = postSolution 42 | CodeStyleEnforcer_RulesLocation = 43 | CodeStyleEnforcer_Active = 0 44 | EndGlobalSection 45 | GlobalSection(CodealikeProperties) = postSolution 46 | SolutionGuid = 6a343836-779d-4eae-b447-3a6dd50a46d2 47 | EndGlobalSection 48 | GlobalSection(TestCaseManagementSettings) = postSolution 49 | CategoryFile = HandyCollections.vsmdi 50 | EndGlobalSection 51 | GlobalSection(SubversionScc) = preSolution 52 | Svn-Managed = True 53 | Manager = AnkhSVN - Subversion Support for Visual Studio 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /HandyCollections.vsmdi: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /HandyCollections/BinaryTree/BinaryTree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using JetBrains.Annotations; 4 | 5 | namespace HandyCollections.BinaryTree 6 | { 7 | /// 8 | /// A Binary search tree with support for tree rotations 9 | /// 10 | /// Type of keys 11 | /// Type of values 12 | public class BinaryTree 13 | { 14 | #region fields and properties 15 | /// 16 | /// Gets the root of this tree 17 | /// 18 | public Node? Root 19 | { 20 | get; 21 | private set; 22 | } 23 | 24 | public IComparer Comparer { get; } 25 | #endregion 26 | 27 | #region constructors 28 | /// 29 | /// 30 | /// 31 | public BinaryTree() 32 | :this(Comparer.Default) 33 | { 34 | 35 | } 36 | 37 | /// 38 | /// 39 | /// 40 | /// 41 | public BinaryTree([NotNull] IComparer comparer) 42 | { 43 | Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); 44 | } 45 | #endregion 46 | 47 | #region add/remove 48 | /// 49 | /// Add a node to the tree 50 | /// 51 | /// 52 | /// 53 | /// 54 | /// 55 | [NotNull] public virtual Node Add(TK key, TV value) 56 | { 57 | var n = CreateNode(key, value); 58 | 59 | var (node, b) = FindParent(n.Key, out var duplicate); 60 | 61 | if (duplicate) 62 | throw new ArgumentException("Duplicate keys not allowed"); 63 | 64 | if (node == null) 65 | return (Root = n); 66 | 67 | SetChild(node, n, b); 68 | 69 | return n; 70 | } 71 | 72 | /// 73 | /// Remove the node with the given key from this tree 74 | /// 75 | /// 76 | /// 77 | public TV? Remove(TK key) 78 | { 79 | var node = FindParent(key, out var duplicate); 80 | 81 | if (!duplicate) 82 | return default; 83 | 84 | throw new NotImplementedException(); 85 | } 86 | #endregion 87 | 88 | #region search 89 | 90 | /// 91 | /// Finds the parent for inserting this key 92 | /// 93 | /// 94 | /// indicates if a node with the same key was located 95 | /// 96 | private (Node?, bool) FindParent(TK key, out bool duplicate) 97 | { 98 | duplicate = false; 99 | var r = Root; 100 | 101 | if (r == null) 102 | return (null, false); 103 | 104 | while (true) 105 | { 106 | if (IsLessThan(key, r.Key)) 107 | { 108 | if (r.Left == null) 109 | return (r, true); 110 | 111 | r = r.Left; 112 | } 113 | else if (IsEqual(key, r.Key)) 114 | { 115 | duplicate = true; 116 | return (r, false); 117 | } 118 | else 119 | { 120 | if (r.Right == null) 121 | return (r, false); 122 | 123 | r = r.Right; 124 | } 125 | } 126 | } 127 | 128 | /// 129 | /// Find a node with the given key 130 | /// 131 | /// 132 | /// 133 | /// 134 | /// 135 | public virtual Node Find(TK key) 136 | { 137 | var v = FindParent(key, out var duplicate); 138 | 139 | if (!duplicate) 140 | throw new KeyNotFoundException("No such key in this tree"); 141 | 142 | return v.Item1!; 143 | } 144 | #endregion 145 | 146 | #region tree rotation 147 | /// 148 | /// Rotates around the pivot node 149 | /// 150 | /// http://webdocs.cs.ualberta.ca/~holte/T26/tree-rotation.html 151 | /// 152 | /// 153 | public void Rotate([NotNull] Node pivot, bool rotateRight) 154 | { 155 | var pivotParent = pivot.Parent; 156 | var parentLeftSide = pivotParent == null || ReferenceEquals(pivotParent.Left, pivot); 157 | 158 | var rotator = rotateRight ? pivot.Left : pivot.Right; 159 | //Node otherSubtree = rotateRight ? pivot.Right : pivot.Left; 160 | var insideSubtree = rotator == null ? null : rotateRight ? rotator.Right : rotator.Left; 161 | //Node outsideSubtree = rotator == null ? null : rotateRight ? rotator.Left : rotator.Right; 162 | 163 | SetChild(pivot, null, rotateRight); 164 | if (pivotParent != null) 165 | SetChild(pivotParent, null, parentLeftSide); 166 | if (rotator != null) 167 | SetChild(rotator, null, !rotateRight); 168 | 169 | SetChild(pivot, insideSubtree, rotateRight); 170 | if (rotator != null) 171 | SetChild(rotator, pivot, !rotateRight); 172 | if (pivotParent != null) 173 | SetChild(pivotParent, rotator, parentLeftSide); 174 | else if (rotator != null) 175 | Root = rotator; 176 | else 177 | Root = pivot; 178 | } 179 | #endregion 180 | 181 | #region helpers 182 | private bool IsLessThan(TK a, TK b) 183 | { 184 | return Comparer.Compare(a, b) < 0; 185 | } 186 | 187 | private bool IsEqual(TK a, TK b) 188 | { 189 | return Comparer.Compare(a, b) == 0; 190 | } 191 | 192 | private static Node CreateNode(TK key, TV value) 193 | { 194 | return new Node(key, value); 195 | } 196 | 197 | private static void SetChild(Node parent, Node? child, bool left) 198 | { 199 | if (parent == null) throw new ArgumentNullException(nameof(parent)); 200 | 201 | if (left) 202 | parent.Left = child; 203 | else 204 | parent.Right = child; 205 | } 206 | #endregion 207 | 208 | /// 209 | /// A node in a binary tree 210 | /// 211 | public class Node 212 | { 213 | /// 214 | /// The key of this node 215 | /// 216 | public readonly TK Key; 217 | /// 218 | /// The value of this node 219 | /// 220 | // ReSharper disable MemberCanBePrivate.Global 221 | public readonly TV Value; 222 | // ReSharper restore MemberCanBePrivate.Global 223 | 224 | private Node? _parent; 225 | /// 226 | /// Gets the parent element of this node 227 | /// 228 | /// 229 | public Node? Parent 230 | { 231 | get => _parent; 232 | private set 233 | { 234 | if (_parent != null) 235 | { 236 | var p = _parent; 237 | _parent = null; 238 | if (ReferenceEquals(p.Left, this)) 239 | p.Left = null; 240 | else if (ReferenceEquals(p.Right, this)) 241 | p.Right = null; 242 | else 243 | throw new InvalidOperationException("parent of this node does not count this node as it's child"); 244 | } 245 | 246 | _parent = value; 247 | } 248 | } 249 | 250 | private Node? _left; 251 | /// 252 | /// Gets the left child of this node 253 | /// 254 | public Node? Left 255 | { 256 | get => _left; 257 | protected internal set => SetChild(value, ref _left); 258 | } 259 | 260 | private Node? _right; 261 | /// 262 | /// Gets the right child of this node 263 | /// 264 | public Node? Right 265 | { 266 | get => _right; 267 | protected internal set => SetChild(value, ref _right); 268 | } 269 | 270 | private void SetChild(Node? value, ref Node? field) 271 | { 272 | if (value?.Parent != null) 273 | throw new ArgumentException("Parent must be null"); 274 | 275 | if (field != null) 276 | field._parent = null; 277 | 278 | field = value; 279 | 280 | if (field != null) 281 | field.Parent = this; 282 | } 283 | 284 | /// 285 | /// Indicates if this is the root node of the tree 286 | /// 287 | public bool IsRoot => Parent == null; 288 | 289 | /// 290 | /// Indicates if this is the left child of it's parent 291 | /// 292 | public bool IsLeftChild 293 | { 294 | get 295 | { 296 | if (Parent == null) 297 | return false; 298 | return ReferenceEquals(Parent.Left, this); 299 | } 300 | } 301 | 302 | /// 303 | /// Indicates if this is the right child of it's parent 304 | /// 305 | public bool IsRightChild 306 | { 307 | get 308 | { 309 | if (Parent == null) 310 | return false; 311 | return ReferenceEquals(Parent.Right, this); 312 | } 313 | } 314 | 315 | /// 316 | /// 317 | /// 318 | /// 319 | /// 320 | protected internal Node(TK key, TV value) 321 | { 322 | Key = key; 323 | Value = value; 324 | } 325 | 326 | /// 327 | /// 328 | /// 329 | /// 330 | public override string ToString() 331 | { 332 | return "Node " + new KeyValuePair(Key, Value); 333 | } 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /HandyCollections/BinaryTree/SplayTree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace HandyCollections.BinaryTree 5 | { 6 | /// 7 | /// A binary tree which reorders itself to make more recently accessed items more efficient to access 8 | /// 9 | /// 10 | /// 11 | public class SplayTree 12 | :BinaryTree 13 | { 14 | /// 15 | /// Add a new items to this tree 16 | /// 17 | /// The key this node (used for finding) 18 | /// The value stored in this node 19 | /// 20 | public override Node Add(TK key, TV value) 21 | { 22 | return Splay(base.Add(key, value)); 23 | } 24 | 25 | /// 26 | /// Find the node with the given key (or null) 27 | /// 28 | /// 29 | /// 30 | public override Node Find(TK key) 31 | { 32 | var f = base.Find(key); 33 | 34 | Splay(f); 35 | 36 | return f; 37 | } 38 | 39 | private Node Splay(Node n) 40 | { 41 | while (n != Root) 42 | { 43 | if (n.Parent!.IsRoot) 44 | { 45 | Zig(n); 46 | } 47 | else 48 | { 49 | if (n.IsLeftChild == n.Parent.IsLeftChild) 50 | ZigZig(n); 51 | else 52 | ZigZag(n); 53 | } 54 | } 55 | 56 | return n; 57 | } 58 | 59 | private void Zig(Node x) 60 | { 61 | if (x.Parent == null) 62 | throw new InvalidOperationException("Cannot `Zig` root node"); 63 | 64 | Rotate(x.Parent, x.IsLeftChild); 65 | } 66 | 67 | private void ZigZig(Node n) 68 | { 69 | if (n.Parent == null) 70 | throw new InvalidOperationException("Cannot `ZigZig` root node"); 71 | 72 | Zig(n.Parent); 73 | Zig(n); 74 | } 75 | 76 | private void ZigZag(Node n) 77 | { 78 | Zig(n); 79 | Zig(n); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /HandyCollections/BloomFilter/BloomFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using HandyCollections.RandomNumber; 4 | 5 | namespace HandyCollections.BloomFilter 6 | { 7 | /// 8 | /// A Bloom filter, supports adding but not removing, and never returns false negatives on containment queries 9 | /// http://en.wikipedia.org/wiki/Bloom_filter 10 | /// 11 | /// 12 | public class BloomFilter 13 | :IBloomFilter 14 | { 15 | private readonly BitArray _array; 16 | 17 | /// 18 | /// The amount of space this filter is using (in bytes) 19 | /// 20 | public int Size => _array.Length * 8; 21 | 22 | /// 23 | /// The number of keys generated for a given item 24 | /// 25 | private readonly int _keyCount; 26 | 27 | /// 28 | /// Gets the number of items which have been added to this filter 29 | /// 30 | /// The count. 31 | public int Count 32 | { 33 | get; 34 | private set; 35 | } 36 | 37 | /// 38 | /// Gets the current false positive rate. 39 | /// 40 | /// The false positive rate. 41 | public double FalsePositiveRate => CalculateFalsePositiveRate(_keyCount, _array.Count, Count); 42 | 43 | /// 44 | /// Initializes a new instance of the class. 45 | /// 46 | /// The size in bits 47 | /// The key count 48 | public BloomFilter(int size, int keys) 49 | { 50 | _array = new BitArray(size, false); 51 | _keyCount = keys; 52 | } 53 | 54 | /// 55 | /// Initializes a new instance of the class. 56 | /// 57 | /// The estimated number of items to add to the filter 58 | /// The target positive rate. 59 | public BloomFilter(int estimatedsize, double targetFalsePositiveRate) 60 | { 61 | var size = (int)Math.Ceiling(-(estimatedsize * Math.Log(targetFalsePositiveRate)) / 0.480453014f); 62 | var keys = (int)(0.7f * size / estimatedsize); 63 | _array = new BitArray(size, false); 64 | _keyCount = keys; 65 | } 66 | 67 | /// 68 | /// Adds the specified item to the filter 69 | /// 70 | /// The item. 71 | /// Returns true if this item was already in the set 72 | public bool Add(T item) 73 | { 74 | if (item is null) 75 | throw new ArgumentNullException(nameof(item)); 76 | 77 | Count++; 78 | 79 | var b = true; 80 | var hash = item.GetHashCode(); 81 | for (var i = 0; i < _keyCount; i++) 82 | { 83 | hash++; 84 | var ik = GetIndex(hash, _array.Length); 85 | if (!_array.Get(ik)) 86 | { 87 | b = false; 88 | _array.Set(ik, true); 89 | } 90 | } 91 | 92 | return b; 93 | } 94 | 95 | /// 96 | /// Clears this instance. 97 | /// 98 | public void Clear() 99 | { 100 | Count = 0; 101 | _array.SetAll(false); 102 | } 103 | 104 | /// 105 | /// Determines whether this filter contains the specificed object, this will sometimes return false positives but never false negatives 106 | /// 107 | /// The item. 108 | /// 109 | /// true if the filter might contain the item; otherwise, false. 110 | /// 111 | public bool Contains(T item) 112 | { 113 | if (item is null) 114 | return false; 115 | 116 | var hash = item.GetHashCode(); 117 | for (var i = 0; i < _keyCount; i++) 118 | { 119 | hash++; 120 | if (!_array.Get(GetIndex(hash, _array.Length))) 121 | return false; 122 | } 123 | return true; 124 | } 125 | 126 | #region static helpers 127 | internal static int GetIndex(int hash, int arrayLength) 128 | { 129 | var k = StaticRandomNumber.Random(unchecked((uint)hash), (uint)arrayLength); 130 | return (int)k; 131 | } 132 | 133 | internal static double CalculateFalsePositiveRate(int keyCount, int arrayCount, int count) 134 | { 135 | return Math.Pow(1 - Math.Exp(-keyCount * count / ((float)arrayCount)), keyCount); 136 | } 137 | 138 | /// 139 | /// Uses the system "GetHashFunction" method to hash an object 140 | /// 141 | /// A. 142 | /// 143 | public static int SystemHash(T a) 144 | { 145 | return a?.GetHashCode() ?? 0; 146 | } 147 | #endregion 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /HandyCollections/BloomFilter/BloomFilterSlice.cs: -------------------------------------------------------------------------------- 1 | namespace HandyCollections.BloomFilter 2 | { 3 | class BloomFilterSlice 4 | :IBloomFilter 5 | { 6 | private readonly BloomFilter _filter; 7 | private readonly int _capacity; 8 | 9 | public int Count => _filter.Count; 10 | 11 | public int SizeInBytes => _filter.Size; 12 | 13 | public BloomFilterSlice(int capacity, double falsePositiveProbability) 14 | { 15 | _filter = new BloomFilter(capacity, falsePositiveProbability); 16 | _capacity = capacity; 17 | } 18 | 19 | internal bool IsFull() 20 | { 21 | return _filter.Count >= _capacity; 22 | } 23 | 24 | public bool Add(T item) 25 | { 26 | return _filter.Add(item); 27 | } 28 | 29 | public bool Contains(T item) 30 | { 31 | return _filter.Contains(item); 32 | } 33 | 34 | public void Clear() 35 | { 36 | _filter.Clear(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /HandyCollections/BloomFilter/CountingBloomFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HandyCollections.BloomFilter 4 | { 5 | /// 6 | /// A bloom filter. False positives are possible, false negatives are not. Removing items is possible 7 | /// 8 | /// 9 | public class CountingBloomFilter 10 | { 11 | internal readonly byte[] Array; 12 | /// 13 | /// The number of keys to use for this filter 14 | /// 15 | private readonly int _keyCount; 16 | 17 | /// 18 | /// A hash generation function 19 | /// 20 | public delegate int GenerateHash(T a); 21 | private readonly GenerateHash _hashGenerator; 22 | 23 | /// 24 | /// Gets the number of items which have been added to this filter 25 | /// 26 | /// The count. 27 | public int Count 28 | { 29 | get; 30 | private set; 31 | } 32 | 33 | /// 34 | /// Gets the current false positive rate. 35 | /// 36 | /// The false positive rate. 37 | public double FalsePositiveRate => BloomFilter.CalculateFalsePositiveRate(_keyCount, Array.Length, Count); 38 | 39 | /// 40 | /// Initializes a new instance of the class. 41 | /// 42 | /// The size in bits 43 | /// The key count 44 | public CountingBloomFilter(int size, int keys) 45 | : this(size, keys, BloomFilter.SystemHash) 46 | { 47 | 48 | } 49 | 50 | /// 51 | /// Initializes a new instance of the class. 52 | /// 53 | /// The estimated number of items to add to the filter 54 | /// The target positive rate. 55 | public CountingBloomFilter(int estimatedsize, float targetFalsePositiveRate) 56 | : this(estimatedsize, targetFalsePositiveRate, BloomFilter.SystemHash) 57 | { 58 | 59 | } 60 | 61 | /// 62 | /// Initializes a new instance of the class. 63 | /// 64 | /// The size of the filter in bytes 65 | /// The number of keys to use 66 | /// The hash generation function 67 | public CountingBloomFilter(int size, int keys, GenerateHash hashgen) 68 | { 69 | Array = new byte[size]; 70 | _keyCount = keys; 71 | _hashGenerator = hashgen; 72 | } 73 | 74 | /// 75 | /// Initializes a new instance of the class. 76 | /// 77 | /// The estimated number of members of the set 78 | /// The target false positive rate when the estimated size is attained 79 | /// The hash generation function 80 | public CountingBloomFilter(int estimatedsize, float targetFalsePositiveRate, GenerateHash hashgen) 81 | { 82 | int size = (int)(-(estimatedsize * Math.Log(targetFalsePositiveRate)) / 0.480453014f); 83 | int keys = (int)(0.7f * size / estimatedsize); 84 | Array = new byte[size]; 85 | _keyCount = keys; 86 | 87 | _hashGenerator = hashgen; 88 | } 89 | 90 | /// 91 | /// Adds the specified item to the filter 92 | /// 93 | /// The item. 94 | /// Returns true if this item was already in the set 95 | public bool Add(T item) 96 | { 97 | bool b = true; 98 | int hash = _hashGenerator.Invoke(item); 99 | for (int i = 0; i < _keyCount; i++) 100 | { 101 | hash++; 102 | int index = BloomFilter.GetIndex(hash, Array.Length); 103 | if (Array[index] == byte.MaxValue) 104 | { 105 | //Rollback changes 106 | for (int r = i - 1; r >= 0; r--) 107 | { 108 | hash--; 109 | Array[BloomFilter.GetIndex(hash, Array.Length)]--; 110 | } 111 | throw new OverflowException("Bloom filter overflowed"); 112 | } 113 | if (Array[index]++ == 0) 114 | b = false; 115 | } 116 | 117 | Count++; 118 | 119 | return b; 120 | } 121 | 122 | /// 123 | /// Removes the specified item from the filter 124 | /// 125 | /// The item. 126 | /// Returns true if this item was successfully removed 127 | public bool Remove(T item) 128 | { 129 | int hash = _hashGenerator.Invoke(item); 130 | for (int i = 0; i < _keyCount; i++) 131 | { 132 | hash++; 133 | int index = BloomFilter.GetIndex(hash, Array.Length); 134 | if (Array[index] == 0) 135 | { 136 | //Rollback changes 137 | for (int r = i - 1; r >= 0; r--) 138 | { 139 | hash--; 140 | Array[BloomFilter.GetIndex(hash, Array.Length)]++; 141 | } 142 | return false; 143 | } 144 | Array[index]--; 145 | } 146 | 147 | Count--; 148 | 149 | return true; 150 | } 151 | 152 | /// 153 | /// Clears this instance. 154 | /// 155 | public void Clear() 156 | { 157 | Count = 0; 158 | for (int i = 0; i < Array.Length; i++) 159 | Array[i] = 0; 160 | } 161 | 162 | /// 163 | /// Determines whether this filter contains the specificed object, this will sometimes return false positives but never false negatives 164 | /// 165 | /// The item. 166 | /// 167 | /// true if the filter might contain the item; otherwise, false. 168 | /// 169 | public bool Contains(T item) 170 | { 171 | int hash = _hashGenerator(item); 172 | for (int i = 0; i < _keyCount; i++) 173 | { 174 | hash++; 175 | if (Array[BloomFilter.GetIndex(hash, Array.Length)] == 0) 176 | return false; 177 | } 178 | return true; 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /HandyCollections/BloomFilter/IBloomFilter.cs: -------------------------------------------------------------------------------- 1 | namespace HandyCollections.BloomFilter 2 | { 3 | /// 4 | /// A probabalistic data set, which may return false positives but never false negatives 5 | /// 6 | /// 7 | public interface IBloomFilter 8 | { 9 | /// 10 | /// Adds an item to the set if it was not already present 11 | /// 12 | /// 13 | /// true if the item was already in the set 14 | bool Add(T item); 15 | 16 | /// 17 | /// Checks if the set might contains this item (bounded by the false probability rate) 18 | /// 19 | /// 20 | /// 21 | bool Contains(T item); 22 | 23 | /// 24 | /// Clear this set 25 | /// 26 | void Clear(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /HandyCollections/BloomFilter/ScalableBloomFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace HandyCollections.BloomFilter 6 | { 7 | /// 8 | /// A bloom filter which grows as more elements are added to maintain a certain upper error bound 9 | /// 10 | public class ScalableBloomFilter 11 | :IBloomFilter 12 | { 13 | private readonly int _initialCapacity; 14 | private const double Scale = 3; //The constant scaling rate. Larger will query faster but consume memory faster 15 | private readonly double _ratio; 16 | private readonly double _falsePositiveProbability; 17 | 18 | private int _lastSlice = -1; 19 | private readonly List> _slices = new(); 20 | 21 | /// 22 | /// Gets the number of items in this filter 23 | /// 24 | public int Count 25 | { 26 | get { return _slices.Select(f => f.Count).Sum(); } 27 | } 28 | 29 | /// 30 | /// The amount of bytes this bloom filter is using 31 | /// 32 | public int SizeInBytes 33 | { 34 | get { return _slices.Select(f => f.SizeInBytes).Sum(); } 35 | } 36 | 37 | /// 38 | /// Constructs a new Scalable bloom filter 39 | /// 40 | /// The rate at which the false probability shrinks 41 | /// The capacity of the bloom filter to start with 42 | /// The initial probability of a false positive 43 | public ScalableBloomFilter(double ratio, int initialCapacity, double falsePositiveProbability) 44 | { 45 | _ratio = ratio; 46 | _initialCapacity = initialCapacity; 47 | _falsePositiveProbability = falsePositiveProbability; 48 | } 49 | 50 | /// 51 | /// Add a new item to the filter 52 | /// 53 | /// 54 | /// 55 | public bool Add(T item) 56 | { 57 | if (Contains(item)) 58 | return true; 59 | 60 | if (IsNewFilterNeeded()) 61 | AddNewSlice(); 62 | 63 | GetCurrentActiveSlice().Add(item); 64 | return false; 65 | } 66 | 67 | /// 68 | /// Test if the filter contains the given item- this is probabilistic 69 | /// 70 | /// 71 | /// 72 | public bool Contains(T item) 73 | { 74 | foreach (var slice in _slices) 75 | { 76 | if (slice.Contains(item)) 77 | return true; 78 | } 79 | return false; 80 | } 81 | 82 | private bool IsNewFilterNeeded() 83 | { 84 | return _lastSlice < 0 || GetCurrentActiveSlice().IsFull(); 85 | } 86 | 87 | private BloomFilterSlice GetCurrentActiveSlice() 88 | { 89 | return _slices[_lastSlice]; 90 | } 91 | 92 | private void AddNewSlice() 93 | { 94 | _lastSlice++; 95 | if (_lastSlice >= _slices.Count) 96 | { 97 | _slices.Add(new BloomFilterSlice((int) (_initialCapacity * Math.Pow(Scale, _slices.Count)), _falsePositiveProbability * Math.Pow(_ratio, _slices.Count))); 98 | _lastSlice = _slices.Count - 1; 99 | } 100 | } 101 | 102 | /// 103 | /// Empty the bloom filter and resit to it's initial state 104 | /// 105 | public void Clear() 106 | { 107 | _lastSlice = -1; 108 | for (int i = 0; i < _slices.Count; i++) 109 | _slices[i].Clear(); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /HandyCollections/Extensions/IEnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using HandyCollections.Heap; 5 | using JetBrains.Annotations; 6 | 7 | namespace HandyCollections.Extensions 8 | { 9 | /// 10 | /// Extensions to the IEnumerable interface 11 | /// 12 | // ReSharper disable InconsistentNaming 13 | public static class IEnumerableExtensions 14 | // ReSharper restore InconsistentNaming 15 | { 16 | /// 17 | /// enumerates the start and then the end 18 | /// 19 | /// 20 | /// The start. 21 | /// The end. 22 | /// 23 | [Pure, Obsolete] public static IEnumerable Append(this IEnumerable start, IEnumerable end) 24 | { 25 | if (start == null) throw new ArgumentNullException(nameof(start)); 26 | if (end == null) throw new ArgumentNullException(nameof(end)); 27 | 28 | foreach (var item in start) 29 | yield return item; 30 | 31 | foreach (var item in end) 32 | yield return item; 33 | } 34 | 35 | /// 36 | /// Appends the given items onto this enumeration 37 | /// 38 | /// 39 | /// The start. 40 | /// The end. 41 | /// 42 | [Pure] public static IEnumerable Append(this IEnumerable start, params T[] end) 43 | { 44 | if (start == null) throw new ArgumentNullException(nameof(start)); 45 | if (end == null) throw new ArgumentNullException(nameof(end)); 46 | 47 | return start.Concat(end); 48 | } 49 | 50 | /// 51 | /// Determines whether the specified enumerable is empty. 52 | /// 53 | /// 54 | /// The enumerable. 55 | /// 56 | /// true if the specified enumerable is empty; otherwise, false. 57 | /// 58 | [Pure] public static bool IsEmpty(this IEnumerable enumerable) 59 | { 60 | if (enumerable == null) throw new ArgumentNullException(nameof(enumerable)); 61 | 62 | return !enumerable.Any(); 63 | } 64 | 65 | /// 66 | /// Given a item => value function selects the highest value item from a set of items 67 | /// 68 | /// 69 | /// 70 | /// 71 | /// 72 | [Pure] public static T? MaxItem([NotNull] this IEnumerable items, Func value) 73 | { 74 | if (items == null) throw new ArgumentNullException(nameof(items)); 75 | if (value == null) throw new ArgumentNullException(nameof(value)); 76 | 77 | var bestScore = float.NegativeInfinity; 78 | var best = default(T); 79 | 80 | foreach (var item in items) 81 | { 82 | var s = value(item); 83 | if (s > bestScore) 84 | { 85 | bestScore = s; 86 | best = item; 87 | } 88 | } 89 | 90 | return best; 91 | } 92 | 93 | /// 94 | /// Given a item => value function selects the lowest value item from a set of items 95 | /// 96 | /// 97 | /// 98 | /// 99 | /// 100 | [Pure] public static T? MinItem(this IEnumerable items, Func value) 101 | { 102 | if (items == null) throw new ArgumentNullException(nameof(items)); 103 | if (value == null) throw new ArgumentNullException(nameof(value)); 104 | 105 | return items.MaxItem(a => -value(a)); 106 | } 107 | 108 | /// 109 | /// Convert given enumeration of items to a MinHeap 110 | /// 111 | /// 112 | /// 113 | /// 114 | /// 115 | public static IMinHeap> ToMinHeap(this IEnumerable items, Func key) 116 | { 117 | //Create a heap which order on the key of a KVP 118 | MinHeap> heap = new((a, b) => a.Key.CompareTo(b.Key)); 119 | 120 | //Add all the items in bulk 121 | heap.Add(items.Select(item => new KeyValuePair(key(item), item))); 122 | 123 | return heap; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /HandyCollections/Extensions/IListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace HandyCollections.Extensions 5 | { 6 | /// 7 | /// A set of extensions to the IList interface 8 | /// 9 | public static class IListExtensions 10 | { 11 | /// 12 | /// Selects the index of the item from the list which would be in the given position if the list were sorted, using the default comparer. 13 | /// This will leave the list in an undefined order afterwards 14 | /// 15 | /// The list to select values from 16 | /// Position to select 17 | /// 18 | public static int OrderSelect(this IList list, int position) 19 | { 20 | return list.OrderSelect(Comparer.Default, position); 21 | } 22 | 23 | /// 24 | /// Selects the index of the item from the list which would be in the given position if the list were sorted. 25 | /// This will leave the list in an undefined order afterwards 26 | /// 27 | /// The list to select values from 28 | /// Position to select 29 | /// The comparer to use 30 | /// 31 | public static int OrderSelect(this IList list, IComparer comparer, int position) 32 | { 33 | if (position < 0) 34 | throw new ArgumentOutOfRangeException("position"); 35 | if (position >= list.Count) 36 | throw new ArgumentOutOfRangeException("position"); 37 | 38 | return list.QuickSelect(comparer, position, 0, list.Count); 39 | } 40 | 41 | private static int QuickSelect(this IList list, IComparer comparer, int position, int start, int length) 42 | { 43 | //Quickselec algorithm 44 | //http://www.ics.uci.edu/~eppstein/161/960125.html 45 | 46 | int pivotIndex = start + length / 2; //todo: need to pick a better pivot 47 | 48 | //partition list 49 | pivotIndex = list.Partition(comparer, start, start + length - 1, pivotIndex); 50 | 51 | int lengthL1 = pivotIndex - start; 52 | const int lengthL2 = 1; 53 | int lengthL3 = start + length - pivotIndex - 1; 54 | 55 | if (position < lengthL1) //position is in L1 56 | return list.QuickSelect(comparer, position, start, lengthL1); 57 | else if (position >= lengthL1 + lengthL2) //position is in L3 58 | return list.QuickSelect(comparer, position - lengthL1 - lengthL2, start + lengthL1 + lengthL2, lengthL3); 59 | else //position must be the pivot 60 | return pivotIndex; 61 | } 62 | 63 | /// 64 | /// Partitions the list around a given pivot index using the default comparer 65 | /// 66 | /// 67 | /// The list. 68 | /// The leftmost index of the sublist 69 | /// The rightmost index of the sublist 70 | /// Index of the pivot. 71 | /// the new index of the pivot element 72 | public static int Partition(this IList list, int left, int right, int pivotIndex) 73 | { 74 | return Partition(list, Comparer.Default, left, right, pivotIndex); 75 | } 76 | 77 | /// 78 | /// Partition an IList around a given pivot index 79 | /// 80 | /// type 81 | /// list to reorder 82 | /// comparer to use 83 | /// left index of the sublist to order 84 | /// right index of the sublist to order 85 | /// the index of the pivot 86 | /// the new index of the pivot 87 | public static int Partition(this IList list, IComparer comparer, int left, int right, int pivotIndex) 88 | { 89 | var pivotValue = list[pivotIndex]; 90 | list.Swap(pivotIndex, right); 91 | var storeIndex = left; 92 | for (var i = left; i < right; i++) 93 | { 94 | if (comparer.Compare(list[i], pivotValue) < 0) 95 | { 96 | list.Swap(i, storeIndex); 97 | storeIndex++; 98 | } 99 | } 100 | list.Swap(storeIndex, right); 101 | return storeIndex; 102 | } 103 | 104 | /// 105 | /// 106 | /// 107 | /// 108 | /// 109 | /// 110 | /// 111 | /// 112 | /// 113 | public static int Partition(this IList list, Func predicate, int left, int right) 114 | { 115 | //Close in two indices until they overlap 116 | while (left != right) { 117 | 118 | //Sweep up left hand side, looking for something which needs swapping 119 | while (left < right && predicate(list[left])) 120 | left++; 121 | 122 | //Sweep down right hand side looking for something which needs swapping 123 | while (right > left && !predicate(list[right])) 124 | right--; 125 | 126 | //Swap them! 127 | if (left < right) 128 | Swap(list, left, right); 129 | }; 130 | 131 | return left; 132 | } 133 | 134 | /// 135 | /// Swap the items in the two given positions 136 | /// 137 | /// Type of the list 138 | /// a list to swap the items inside 139 | /// index of the first item 140 | /// index of the second item 141 | public static void Swap(this IList list, int a, int b) 142 | { 143 | T aa = list[a]; 144 | list[a] = list[b]; 145 | list[b] = aa; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /HandyCollections/Extensions/IReadOnlyListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace HandyCollections.Extensions 5 | { 6 | public static class IReadOnlyListExtensions 7 | { 8 | public static int FindIndex(this IReadOnlyList list, Predicate predicate) 9 | { 10 | for (var i = 0; i < list.Count; i++) 11 | if (predicate(list[i])) 12 | return i; 13 | 14 | return -1; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /HandyCollections/Geometry/GeometricTree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace HandyCollections.Geometry 6 | { 7 | /// 8 | /// Base class for trees with partition space 9 | /// 10 | /// Items in the tree 11 | /// Type of vectors 12 | /// Type of bounds (rectangle or equivalent) 13 | public abstract class GeometricTree 14 | : IEnumerable> 15 | where TVector : struct 16 | where TBound : struct 17 | { 18 | private readonly int _threshold; 19 | private readonly Node _root; 20 | 21 | /// 22 | /// The bounds of the root of this tree (inserting outside the root bounds is possible, but inefficient) 23 | /// 24 | public TBound Bounds => _root.Bounds; 25 | 26 | /// 27 | /// The number of items in the entire tree 28 | /// 29 | public int Count { get; private set; } 30 | 31 | /// 32 | /// 33 | /// 34 | /// 35 | /// 36 | /// 37 | protected GeometricTree(TBound bounds, int threshold, int maxDepth) 38 | { 39 | _threshold = threshold; 40 | _root = new Node(this, bounds, 0, maxDepth); 41 | } 42 | 43 | /// 44 | /// Check if the given bound is contained entirely within the other bound 45 | /// 46 | /// 47 | /// 48 | /// 49 | protected abstract bool Contains(TBound container, ref TBound contained); 50 | 51 | /// 52 | /// Check if the given bounds intersects the other bound 53 | /// 54 | /// 55 | /// 56 | /// 57 | protected abstract bool Intersects(TBound a, ref TBound b); 58 | 59 | /// 60 | /// Split the given bound is even parts 61 | /// 62 | /// 63 | /// 64 | protected abstract TBound[] Split(TBound bound); 65 | 66 | #region add 67 | /// 68 | /// Insert a new item into the tree 69 | /// 70 | /// 71 | /// 72 | public void Insert(TBound bounds, TItem item) 73 | { 74 | var a = new Member { Bounds = bounds, Value = item }; 75 | _root.Insert(a, _threshold); 76 | 77 | Count++; 78 | } 79 | #endregion 80 | 81 | #region query 82 | /// 83 | /// Find all items which intersect the given bounds 84 | /// 85 | /// 86 | /// 87 | public IEnumerable Intersects(TBound bounds) 88 | { 89 | foreach (var item in _root.Intersects(bounds)) 90 | yield return item.Value; 91 | } 92 | 93 | /// 94 | /// Find all items which are completely contained byhe given bound 95 | /// 96 | /// 97 | /// 98 | public IEnumerable ContainedBy(TBound bounds) 99 | { 100 | foreach (var item in _root.Intersects(bounds)) 101 | { 102 | var b = item.Bounds; 103 | if (Contains(bounds, ref b)) 104 | yield return item.Value; 105 | } 106 | } 107 | #endregion 108 | 109 | #region remove 110 | /// 111 | /// Remove all items from the tree 112 | /// 113 | public void Clear() 114 | { 115 | _root.Clear(); 116 | Count = 0; 117 | } 118 | 119 | /// 120 | /// Remove the specific item from the tree 121 | /// 122 | /// Hint used to find the item 123 | /// 124 | /// 125 | public bool Remove(TBound bounds, TItem item) 126 | { 127 | int count = _root.Remove(bounds, item); 128 | Count -= count; 129 | return count > 0; 130 | } 131 | 132 | /// 133 | /// Remove *all* items which match the specified predicate which intersect the given bound 134 | /// 135 | /// 136 | /// 137 | /// 138 | public bool Remove(TBound bounds, Predicate pred) 139 | { 140 | int count = _root.Remove(bounds, pred); 141 | Count -= count; 142 | return count > 0; 143 | } 144 | #endregion 145 | 146 | #region helper types 147 | private class Node 148 | { 149 | public readonly List Items = new(); 150 | public readonly TBound Bounds; 151 | public Node[]? Children; 152 | 153 | private readonly int _depth; 154 | private readonly int _maxDepth; 155 | 156 | private readonly GeometricTree _tree; 157 | 158 | public Node(GeometricTree tree, TBound bounds, int depth, int maxDepth) 159 | { 160 | _tree = tree; 161 | _depth = depth; 162 | _maxDepth = maxDepth; 163 | 164 | Bounds = bounds; 165 | } 166 | 167 | private void Split(int splitThreshold) 168 | { 169 | var bounds = _tree.Split(Bounds); 170 | 171 | Children = new Node[bounds.Length]; 172 | for (var i = 0; i < bounds.Length; i++) 173 | Children[i] = new Node(_tree, bounds[i], _depth + 1, _maxDepth); 174 | 175 | for (var i = Items.Count - 1; i >= 0; i--) 176 | { 177 | var item = Items[i]; 178 | 179 | //Try to insert this item into each child (if successful, it's removed from this node) 180 | foreach (var child in Children) 181 | { 182 | var cb = child.Bounds; 183 | if (_tree.Contains(cb, ref item.Bounds)) 184 | { 185 | child.Insert(item, splitThreshold); 186 | Items.RemoveAt(i); 187 | break; 188 | } 189 | } 190 | } 191 | } 192 | 193 | public void Insert(Member m, int splitThreshold) 194 | { 195 | if (Children == null) 196 | { 197 | Items.Add(m); 198 | 199 | if (Items.Count > splitThreshold && _depth < _maxDepth) 200 | Split(splitThreshold); 201 | } 202 | else 203 | { 204 | //Try to put this item into a child node 205 | foreach (var child in Children) 206 | { 207 | var cb = child.Bounds; 208 | if (_tree.Contains(cb, ref m.Bounds)) 209 | { 210 | child.Insert(m, splitThreshold); 211 | return; 212 | } 213 | } 214 | 215 | //Failed! Can't find a child to contain this, store it here instead 216 | Items.Add(m); 217 | } 218 | } 219 | 220 | public IEnumerable Intersects(TBound bounds) 221 | { 222 | var nodes = new List(50) { this }; 223 | 224 | var root = true; 225 | while (nodes.Count > 0) 226 | { 227 | //Remove node 228 | var n = nodes[^1]; 229 | nodes.RemoveAt(nodes.Count - 1); 230 | 231 | //Skip nodes we do not intersect (unless this is the root, in which case we always want to check it) 232 | if (!root && !_tree.Intersects(n.Bounds, ref bounds)) 233 | continue; 234 | 235 | //yield items as appropriate 236 | foreach (var member in n.Items) 237 | if (_tree.Intersects(member.Bounds, ref bounds)) 238 | yield return member; 239 | 240 | //push children onto stack to be checked 241 | if (n.Children != null) 242 | nodes.AddRange(n.Children); 243 | 244 | root = false; 245 | } 246 | } 247 | 248 | public int Remove(TBound bounds, TItem item) 249 | { 250 | var pred = new Predicate(a => a.Value!.Equals(item)); 251 | 252 | return RemoveRecursive(bounds, pred, true); 253 | } 254 | 255 | public int Remove(TBound bounds, Predicate pred) 256 | { 257 | var predInner = new Predicate(a => pred(a.Value)); 258 | 259 | return RemoveRecursive(bounds, predInner, false); 260 | } 261 | 262 | private int RemoveRecursive(TBound bounds, Predicate predicate, bool removeSingle) 263 | { 264 | //We want to skip this node if the boundary doesn't intersect it... unless it's the root which can contain items outside it's boundary! 265 | if (!_tree.Intersects(Bounds, ref bounds) && !_tree._root.Equals(this)) 266 | return 0; 267 | 268 | int removed = 0; 269 | 270 | //Either we're removing the first item which matches the predicate, or all items which match 271 | if (removeSingle) 272 | { 273 | // Find single item and remove it, then exit instantly 274 | var index = Items.FindIndex(predicate); 275 | if (index != -1) 276 | { 277 | Items.RemoveAt(index); 278 | return 1; 279 | } 280 | } 281 | else 282 | { 283 | //We're removing all predicate matches 284 | removed += Items.RemoveAll(predicate); 285 | } 286 | 287 | //Remove items from children (and aggregate total removed count) 288 | if (Children != null) 289 | { 290 | foreach (var child in Children) 291 | { 292 | removed += child.RemoveRecursive(bounds, predicate, removeSingle); 293 | 294 | //Early exit if we can 295 | if (removed > 0 && removeSingle) 296 | return removed; 297 | } 298 | } 299 | 300 | return removed; 301 | } 302 | 303 | public void Clear() 304 | { 305 | Children = null; 306 | 307 | Items.Clear(); 308 | Items.Capacity = 4; 309 | } 310 | } 311 | 312 | private struct Member 313 | { 314 | public TItem Value; 315 | public TBound Bounds; 316 | } 317 | #endregion 318 | 319 | #region enumeration 320 | /// 321 | /// Enumerate all items in the tree 322 | /// 323 | /// 324 | public IEnumerator> GetEnumerator() 325 | { 326 | var nodes = new List() { _root }; 327 | while (nodes.Count > 0) 328 | { 329 | var n = nodes[^1]; 330 | nodes.RemoveAt(nodes.Count - 1); 331 | if (n == null) 332 | continue; 333 | 334 | foreach (var item in n.Items) 335 | yield return new KeyValuePair(item.Bounds, item.Value); 336 | 337 | if (n.Children != null) 338 | nodes.AddRange(n.Children); 339 | } 340 | } 341 | 342 | IEnumerator IEnumerable.GetEnumerator() 343 | { 344 | return GetEnumerator(); 345 | } 346 | #endregion 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /HandyCollections/Geometry/Octree.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using SwizzleMyVectors.Geometry; 3 | 4 | namespace HandyCollections.Geometry 5 | { 6 | /// 7 | /// 3D Space Partitioning Tree 8 | /// 9 | /// 10 | public class Octree 11 | : GeometricTree 12 | { 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | public Octree(BoundingBox bounds, int threshold, int maxDepth) 20 | : base(bounds, threshold, maxDepth) 21 | { 22 | } 23 | 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | protected override bool Contains(BoundingBox container, ref BoundingBox contained) 31 | { 32 | container.Contains(ref contained, out var result); 33 | return result == ContainmentType.Contains; 34 | } 35 | 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// 41 | /// 42 | protected override bool Intersects(BoundingBox a, ref BoundingBox b) 43 | { 44 | a.Intersects(ref b, out var result); 45 | return result; 46 | } 47 | 48 | /// 49 | /// 50 | /// 51 | /// 52 | /// 53 | protected override BoundingBox[] Split(BoundingBox bound) 54 | { 55 | var bounds = new BoundingBox[8]; 56 | var min = bound.Min; 57 | var size = (bound.Max - bound.Min) / 2f; 58 | 59 | var i = 0; 60 | for (var x = 0; x < 2; x++) 61 | { 62 | for (var y = 0; y < 2; y++) 63 | { 64 | for (var z = 0; z < 2; z++) 65 | { 66 | var positionOffset = size * new Vector3(x, y, z); 67 | bounds[i++] = new BoundingBox(min + positionOffset, min + size + positionOffset); 68 | } 69 | } 70 | } 71 | 72 | return bounds; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /HandyCollections/Geometry/Quadtree.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using SwizzleMyVectors.Geometry; 3 | 4 | namespace HandyCollections.Geometry 5 | { 6 | /// 7 | /// A 2D Partitioning Tree 8 | /// 9 | /// 10 | public class Quadtree 11 | : GeometricTree 12 | { 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | public Quadtree(BoundingRectangle bounds, int threshold, int maxDepth) 20 | : base(bounds, threshold, maxDepth) 21 | { 22 | } 23 | 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | protected override bool Contains(BoundingRectangle container, ref BoundingRectangle contained) 31 | { 32 | return container.Contains(contained); 33 | } 34 | 35 | /// 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// 41 | protected override bool Intersects(BoundingRectangle a, ref BoundingRectangle b) 42 | { 43 | return a.Intersects(b); 44 | } 45 | 46 | /// 47 | /// 48 | /// 49 | /// 50 | /// 51 | protected override BoundingRectangle[] Split(BoundingRectangle bound) 52 | { 53 | var bounds = new BoundingRectangle[4]; 54 | var min = bound.Min; 55 | var size = (bound.Max - bound.Min) / 2f; 56 | 57 | var i = 0; 58 | for (var x = 0; x < 2; x++) 59 | { 60 | for (var y = 0; y < 2; y++) 61 | { 62 | var positionOffset = size * new Vector2(x, y); 63 | bounds[i++] = new BoundingRectangle(min + positionOffset, min + size + positionOffset); 64 | } 65 | } 66 | 67 | return bounds; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /HandyCollections/HandyCollections.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | enable 5 | latest 6 | netstandard2.1 7 | Martin Evans 8 | 9 | 8.0.0 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /HandyCollections/HandyCollections.ncrunchproject: -------------------------------------------------------------------------------- 1 | 2 | false 3 | false 4 | false 5 | true 6 | false 7 | false 8 | false 9 | false 10 | true 11 | true 12 | false 13 | true 14 | true 15 | 60000 16 | 17 | 18 | 19 | AutoDetect 20 | STA 21 | x86 22 | 23 | 24 | .* 25 | 26 | 27 | -------------------------------------------------------------------------------- /HandyCollections/HandyCollections.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | Martin Evans 7 | Martin Evans 8 | https://github.com/martindevans/HandyCollections/blob/master/License.md 9 | https://github.com/martindevans/HandyCollections 10 | false 11 | $description$ 12 | Copyright 2014 13 | 14 | -------------------------------------------------------------------------------- /HandyCollections/Heap/IMinHeap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace HandyCollections.Heap 5 | { 6 | /// 7 | /// A binary heap which allows efficient querying and removal of the minimum item in the heap 8 | /// 9 | /// 10 | public interface IMinHeap 11 | : IEnumerable 12 | { 13 | /// 14 | /// Number of items in the heap 15 | /// 16 | int Count { get; } 17 | 18 | /// 19 | /// Peek the minimum value on the heap 20 | /// 21 | T Minimum { get; } 22 | 23 | /// 24 | /// Add a new item to the heap 25 | /// 26 | /// 27 | void Add(T item); 28 | 29 | /// 30 | /// Add a lot of items to the heap (more efficient than calling Add(item) lots) 31 | /// 32 | /// 33 | void Add(IEnumerable items); 34 | 35 | /// 36 | /// Remove the minimum item from the heap 37 | /// 38 | /// 39 | T RemoveMin(); 40 | 41 | /// 42 | /// Remove the item at the given index 43 | /// 44 | /// 45 | /// 46 | T RemoveAt(int index); 47 | 48 | /// 49 | /// Get the index of a given item (or -1 if cannot be found) 50 | /// 51 | /// 52 | /// 53 | int IndexOf(T item); 54 | 55 | /// 56 | /// Get the index of the first item which matches the given predicate (or -1 if cannot be found) 57 | /// 58 | /// 59 | /// 60 | int IndexOf(Predicate predicate); 61 | 62 | /// 63 | /// Remove all items from the heap 64 | /// 65 | void Clear(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /HandyCollections/Heap/MinHeap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using JetBrains.Annotations; 6 | 7 | namespace HandyCollections.Heap 8 | { 9 | /// 10 | /// 11 | /// 12 | /// 13 | public class MinHeap 14 | : IMinHeap 15 | { 16 | #region fields and properties 17 | private readonly List _heap; 18 | private readonly IComparer _comparer; 19 | 20 | /// 21 | /// Get the number of items in this heap 22 | /// 23 | public int Count => _heap.Count; 24 | 25 | /// 26 | /// Get the minimum value in this heap 27 | /// 28 | public T Minimum => _heap[0]; 29 | 30 | public bool AllowHeapResize { get; set; } = true; 31 | #endregion 32 | 33 | #region constructors 34 | /// 35 | /// 36 | /// 37 | public MinHeap() 38 | : this(64) 39 | { 40 | 41 | } 42 | 43 | /// 44 | /// 45 | /// 46 | public MinHeap(int capacity) 47 | : this(capacity, Comparer.Default) 48 | { 49 | } 50 | 51 | /// 52 | /// 53 | /// 54 | /// 55 | /// 56 | public MinHeap(int capacity, IComparer comparer) 57 | { 58 | if (capacity <= 0) 59 | throw new ArgumentOutOfRangeException(nameof(capacity)); 60 | 61 | _heap = new List(capacity); 62 | _comparer = comparer; 63 | } 64 | 65 | /// 66 | /// 67 | /// 68 | /// 69 | /// 70 | public MinHeap(int capacity, Comparison comparison) 71 | : this(capacity, Comparer.Create(comparison)) 72 | { 73 | } 74 | 75 | /// 76 | /// 77 | /// 78 | /// 79 | public MinHeap(IComparer comparer) 80 | { 81 | _heap = new List(); 82 | _comparer = comparer; 83 | } 84 | 85 | /// 86 | /// 87 | /// 88 | /// 89 | public MinHeap(Comparison comparison) 90 | : this(Comparer.Create(comparison)) 91 | { 92 | } 93 | #endregion 94 | 95 | #region add 96 | /// 97 | /// 98 | /// 99 | /// 100 | /// 101 | public void Add(T item) 102 | { 103 | if (!AllowHeapResize && _heap.Count == _heap.Capacity) 104 | throw new InvalidOperationException("Heap is full and resizing is disabled"); 105 | 106 | _heap.Add(item); 107 | BubbleUp(_heap.Count - 1); 108 | 109 | DebugCheckHeapProperty(); 110 | } 111 | 112 | /// 113 | /// Add a large number of items to the heap. This is more efficient that simply calling add on each item individually 114 | /// 115 | /// 116 | public void Add(IEnumerable items) 117 | { 118 | if (items == null) 119 | throw new ArgumentNullException(nameof(items)); 120 | 121 | _heap.AddRange(items); 122 | Heapify(); 123 | 124 | DebugCheckHeapProperty(); 125 | } 126 | 127 | /// 128 | /// Establish the heap property (use this if you mutate an item already in the heap) 129 | /// 130 | public void Heapify() 131 | { 132 | // Using sorting is tempting, for it's sheer simplicity: 133 | // _heap.Sort(_comparer); 134 | // There's also the possibility the framework implemented sorting algorithm is way better/faster than my heapify function in practical cases. 135 | // Benchmarking reveals sorting to *always* be slower, on both tiny heaps (10 items) and massive heaps (1000000 items) 136 | 137 | for (var i = _heap.Count - 1; i >= 0; i--) 138 | TrickleDown(i); 139 | 140 | DebugCheckHeapProperty(); 141 | } 142 | 143 | /// 144 | /// Establish the heap property (use this if you mutate an item already in the heap) 145 | /// 146 | /// The index of the item which was mutated 147 | public void Heapify(int mutated) 148 | { 149 | if (mutated < 0 || mutated >= Count) 150 | throw new IndexOutOfRangeException(nameof(mutated)); 151 | 152 | //We either need to move this item up or down the heap. Try trickling down, and if that does nothing bubble up instead 153 | if (TrickleDown(mutated) == mutated) 154 | BubbleUp(mutated); 155 | } 156 | #endregion 157 | 158 | #region remove 159 | /// 160 | /// 161 | /// 162 | /// 163 | /// 164 | public T RemoveMin() 165 | { 166 | return RemoveAt(0); 167 | } 168 | 169 | /// 170 | /// 171 | /// 172 | /// 173 | /// 174 | /// 175 | /// 176 | public T RemoveAt(int index) 177 | { 178 | if (index < 0 || index > _heap.Count) 179 | throw new ArgumentOutOfRangeException(nameof(index)); 180 | 181 | var removed = _heap[index]; 182 | 183 | //Move the last item into the first position 184 | _heap[index] = _heap[^1]; 185 | _heap.RemoveAt(_heap.Count - 1); 186 | 187 | if (_heap.Count > 0 && index < _heap.Count) 188 | Heapify(0); 189 | 190 | DebugCheckHeapProperty(); 191 | 192 | return removed; 193 | } 194 | 195 | /// 196 | /// 197 | /// 198 | public void Clear() 199 | { 200 | _heap.Clear(); 201 | } 202 | #endregion 203 | 204 | #region private helpers 205 | private void BubbleUp(int index) 206 | { 207 | while (index > 0) 208 | { 209 | var parent = ParentIndex(index); 210 | if (IsLessThan(_heap[index], _heap[parent])) 211 | { 212 | Swap(parent, index); 213 | index = parent; 214 | } 215 | else 216 | break; 217 | } 218 | } 219 | 220 | private int TrickleDown(int index) 221 | { 222 | // This code was automatically converted to iteration instead of tail recursion 223 | // WTB C# tail call keyword! 224 | /* if (index >= _heap.Count) 225 | throw new ArgumentException(); 226 | int smallestChildIndex = SmallestChildSmallerThan(index, _heap[index]); 227 | if (smallestChildIndex == -1) 228 | return index; 229 | Swap(smallestChildIndex, index); 230 | return TrickleDown(smallestChildIndex); */ 231 | 232 | while (true) 233 | { 234 | if (index >= _heap.Count) 235 | throw new ArgumentException(); 236 | 237 | var smallestChildIndex = SmallestChildSmallerThan(index, _heap[index]); 238 | if (smallestChildIndex == -1) 239 | return index; 240 | 241 | Swap(smallestChildIndex, index); 242 | index = smallestChildIndex; 243 | } 244 | } 245 | 246 | // ReSharper disable once MemberCanBeMadeStatic.Local 247 | private void DebugCheckHeapProperty() 248 | { 249 | //// ReSharper disable once InvokeAsExtensionMethod 250 | //if (Enumerable.Any(_heap, t => IsLessThan(t, Minimum))) 251 | // throw new Exception("Heap property violated"); 252 | } 253 | 254 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 255 | private bool IsLessThan(T a, T b) 256 | { 257 | return _comparer.Compare(a, b) < 0; 258 | } 259 | 260 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 261 | private static int ParentIndex(int i) 262 | { 263 | return (i - 1) / 2; 264 | } 265 | 266 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 267 | private void Swap(int a, int b) 268 | { 269 | var temp = _heap[a]; 270 | _heap[a] = _heap[b]; 271 | _heap[b] = temp; 272 | } 273 | 274 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 275 | private static int LeftChild(int i) 276 | { 277 | return 2 * i + 1; 278 | } 279 | 280 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 281 | private static int RightChild(int i) 282 | { 283 | return 2 * i + 2; 284 | } 285 | 286 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 287 | private int SmallestChildSmallerThan(int i, T item) 288 | { 289 | var leftChildIndex = LeftChild(i); 290 | var rightChildIndex = RightChild(i); 291 | 292 | var smallest = -1; 293 | if (leftChildIndex < _heap.Count) 294 | smallest = leftChildIndex; 295 | if (rightChildIndex < _heap.Count && IsLessThan(_heap[rightChildIndex], _heap[leftChildIndex])) 296 | smallest = rightChildIndex; 297 | 298 | if (smallest > -1 && IsLessThan(_heap[smallest], item)) 299 | return smallest; 300 | 301 | return -1; 302 | } 303 | #endregion 304 | 305 | #region searching 306 | /// 307 | /// 308 | /// 309 | /// 310 | /// 311 | public int IndexOf(T item) 312 | { 313 | return _heap.IndexOf(item); 314 | } 315 | 316 | /// 317 | /// 318 | /// 319 | /// 320 | /// 321 | public int IndexOf(Predicate predicate) 322 | { 323 | return _heap.FindIndex(predicate); 324 | } 325 | #endregion 326 | 327 | IEnumerator IEnumerable.GetEnumerator() 328 | { 329 | return ((IEnumerable)this).GetEnumerator(); 330 | } 331 | 332 | /// 333 | /// Enumerate this heap *in no particular order* 334 | /// 335 | /// 336 | IEnumerator IEnumerable.GetEnumerator() 337 | { 338 | return _heap.GetEnumerator(); 339 | } 340 | } 341 | } -------------------------------------------------------------------------------- /HandyCollections/Heap/MinMaxMedianHeap.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using System.Collections.Generic; 3 | //using System.Linq; 4 | 5 | //namespace HandyCollections.Heap 6 | //{ 7 | // /// 8 | // /// a heap which allows O(1) extraction of minimum, maximum and median items. With insertion/deletion in O(logn) time 9 | // /// 10 | // /// 11 | // public class MinMaxMedianHeap 12 | // : ICollection, IMinHeap 13 | // { 14 | // #region fields and properties 15 | // private readonly MinMaxHeap _lesserOrEqual; 16 | // private readonly MinMaxHeap _greaterOrEqual; 17 | 18 | // private readonly IComparer _comparer = Comparer.Default; 19 | // /// 20 | // /// The comparer to use for items in this collection. Changing this comparer will trigger a heapify operation 21 | // /// 22 | // public IComparer Comparer 23 | // { 24 | // get 25 | // { 26 | // return _comparer; 27 | // } 28 | // set 29 | // { 30 | // throw new NotImplementedException("Not implemented, need to reorder all the heaps"); 31 | // } 32 | // } 33 | 34 | // /// 35 | // /// Gets the number of elements contained in the . 36 | // /// 37 | // /// 38 | // /// The number of elements contained in the . 39 | // public int Count 40 | // { 41 | // get 42 | // { 43 | // return _lesserOrEqual.Count + _greaterOrEqual.Count; 44 | // } 45 | // } 46 | // #endregion 47 | 48 | // #region constructors 49 | // /// 50 | // /// Initializes a new instance of the class. 51 | // /// 52 | // /// The initial capacity. 53 | // public MinMaxMedianHeap(int capacity) 54 | // : this(Comparer.Default, capacity) 55 | // { 56 | // } 57 | 58 | // /// 59 | // /// Initializes a new instance of the class. 60 | // /// 61 | // /// The initial items. 62 | // public MinMaxMedianHeap(T[] initialItems) 63 | // : this(Comparer.Default, initialItems) 64 | // { 65 | // } 66 | 67 | // /// 68 | // /// Initializes a new instance of the class. 69 | // /// 70 | // public MinMaxMedianHeap() 71 | // : this(0) 72 | // { 73 | // } 74 | 75 | // /// 76 | // /// Initializes a new instance of the class. 77 | // /// 78 | // /// The comparer to use 79 | // /// The initial capacity of the heap 80 | // public MinMaxMedianHeap(Comparer comparer, int capacity = 0) 81 | // { 82 | // _comparer = comparer; 83 | // _lesserOrEqual = new MinMaxHeap(comparer, capacity / 2); 84 | // _greaterOrEqual = new MinMaxHeap(comparer, capacity / 2); 85 | // } 86 | 87 | // /// 88 | // /// Initializes a new instance of the class. 89 | // /// 90 | // /// The comparer to use 91 | // /// The initial items to put into the heap 92 | // public MinMaxMedianHeap(Comparer comparer, T[] initialItems) 93 | // : this(comparer, initialItems.Length) 94 | // { 95 | // AddMany(initialItems); 96 | // } 97 | // #endregion 98 | 99 | // #region Add 100 | // /// 101 | // /// Adds the specified item to the heap 102 | // /// 103 | // /// item to add to the heap 104 | // public void Add(T item) 105 | // { 106 | // if (Count == 0) 107 | // { 108 | // _lesserOrEqual.Add(item); 109 | // } 110 | // else 111 | // { 112 | // int comparision = _comparer.Compare(item, Median); 113 | // if (comparision < 0) 114 | // { 115 | // _lesserOrEqual.Add(item); 116 | // } 117 | // else if (comparision > 0) 118 | // { 119 | // _greaterOrEqual.Add(item); 120 | // } 121 | // else 122 | // { 123 | // SelectSmallerHeap(_lesserOrEqual).Add(item); 124 | // } 125 | // } 126 | // Rebalance(); 127 | // } 128 | 129 | // /// 130 | // /// Add many items to the heap 131 | // /// 132 | // /// 133 | // public void AddMany(IEnumerable a) 134 | // { 135 | // var sorted = a.OrderBy(i => i, _comparer).ToArray(); 136 | 137 | // int mid = sorted.Length / 2; 138 | // _lesserOrEqual.AddMany(sorted, 0, mid); 139 | // _greaterOrEqual.AddMany(sorted, mid, sorted.Length - mid); 140 | // } 141 | // #endregion 142 | 143 | // #region Remove 144 | // /// 145 | // /// Removes the maximum item from the heap 146 | // /// 147 | // /// 148 | // public T RemoveMax() 149 | // { 150 | // if (Count == 0) 151 | // throw new InvalidOperationException("Heap is empty"); 152 | 153 | // T value = _greaterOrEqual.Count == 0 ? _lesserOrEqual.RemoveMax() : _greaterOrEqual.RemoveMax(); 154 | 155 | // Rebalance(); 156 | // return value; 157 | // } 158 | 159 | // /// 160 | // /// Removes the minimum item from the heap 161 | // /// 162 | // /// 163 | // public T RemoveMin() 164 | // { 165 | // if (Count == 0) 166 | // throw new InvalidOperationException("Heap is empty"); 167 | 168 | // T value = _lesserOrEqual.Count == 0 ? _greaterOrEqual.RemoveMin() : _lesserOrEqual.RemoveMin(); 169 | 170 | // Rebalance(); 171 | // return value; 172 | // } 173 | 174 | // /// 175 | // /// Removes the median item from the heap 176 | // /// 177 | // /// 178 | // public T RemoveMedian() 179 | // { 180 | // if (Count == 0) 181 | // throw new InvalidOperationException("Heap is empty"); 182 | 183 | // T value = _lesserOrEqual.RemoveMax(); 184 | 185 | // Rebalance(); 186 | // return value; 187 | // } 188 | 189 | // /// 190 | // /// Removes all items from the . 191 | // /// 192 | // /// The is read-only. 193 | // public void Clear() 194 | // { 195 | // _lesserOrEqual.Clear(); 196 | // _greaterOrEqual.Clear(); 197 | // } 198 | 199 | // /// 200 | // /// Removes the first occurrence of a specific object from the . 201 | // /// 202 | // /// The object to remove from the . 203 | // /// 204 | // /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . 205 | // /// 206 | // /// The is read-only. 207 | // public bool Remove(T item) 208 | // { 209 | // int comparison = Comparer.Compare(item, Median); 210 | 211 | // if (comparison <= 0) 212 | // return _lesserOrEqual.Remove(item); 213 | // else 214 | // return _greaterOrEqual.Remove(item); 215 | // } 216 | // #endregion 217 | 218 | // #region helpers 219 | // /// 220 | // /// Rebalances the two trees, so (lesser.count == greater.count) | (lesser.count == greater.count + 1) 221 | // /// 222 | // private void Rebalance() 223 | // { 224 | // while (_lesserOrEqual.Count != _greaterOrEqual.Count && _lesserOrEqual.Count < _greaterOrEqual.Count + 1) 225 | // { 226 | // //pop off greater, and push onto lessser 227 | // _lesserOrEqual.Add(_greaterOrEqual.RemoveMin()); 228 | // } 229 | // while (_lesserOrEqual.Count > _greaterOrEqual.Count + 1) 230 | // { 231 | // //pop off lesser, and push onto greater 232 | // _greaterOrEqual.Add(_lesserOrEqual.RemoveMax()); 233 | // } 234 | // } 235 | 236 | // /// 237 | // /// Selects the smaller heap. 238 | // /// 239 | // /// The heap to return when equal. 240 | // /// 241 | // private MinMaxHeap SelectSmallerHeap(MinMaxHeap whenEqual) 242 | // { 243 | // return (_lesserOrEqual.Count < _greaterOrEqual.Count ? _lesserOrEqual : (_lesserOrEqual.Count > _greaterOrEqual.Count ? _greaterOrEqual : whenEqual)); 244 | // } 245 | // #endregion 246 | 247 | // #region peeking 248 | // /// 249 | // /// Gets the maximum item in the heap 250 | // /// 251 | // /// The max. 252 | // public T Maximum 253 | // { 254 | // get 255 | // { 256 | // if (_greaterOrEqual.Count == 0) 257 | // return _lesserOrEqual.Maximum; 258 | // else 259 | // return _greaterOrEqual.Maximum; 260 | // } 261 | // } 262 | 263 | // /// 264 | // /// Gets the minimum item in the heap 265 | // /// 266 | // /// The min. 267 | // public T Minimum 268 | // { 269 | // get 270 | // { 271 | // if (_lesserOrEqual.Count == 0) 272 | // return _greaterOrEqual.Minimum; 273 | // else 274 | // return _lesserOrEqual.Minimum; 275 | // } 276 | // } 277 | 278 | // /// 279 | // /// Gets the median item in the heap. If there are a even number of items, the smaller of the two is selected 280 | // /// 281 | // /// The median. 282 | // public T Median 283 | // { 284 | // get 285 | // { 286 | // return LowMedian; 287 | // } 288 | // } 289 | 290 | // /// 291 | // /// Gets the median, when there are an even number of items this selects the smaller of the two medians 292 | // /// 293 | // /// The low median. 294 | // public T LowMedian 295 | // { 296 | // get 297 | // { 298 | // return _lesserOrEqual.Maximum; 299 | // } 300 | // } 301 | 302 | // /// 303 | // /// Gets the median, when there are an even number of items this selects the larger of the two medians 304 | // /// 305 | // /// The high median. 306 | // public T HighMedian 307 | // { 308 | // get 309 | // { 310 | // if (_greaterOrEqual.Count == 0) 311 | // return LowMedian; 312 | // if (_greaterOrEqual.Count < _lesserOrEqual.Count) 313 | // return LowMedian; 314 | // return _greaterOrEqual.Minimum; 315 | // } 316 | // } 317 | 318 | // /// 319 | // /// Determines whether the contains a specific value. 320 | // /// 321 | // /// The object to locate in the . 322 | // /// 323 | // /// true if is found in the ; otherwise, false. 324 | // /// 325 | // public bool Contains(T item) 326 | // { 327 | // int comparison = Comparer.Compare(item, Median); 328 | 329 | // if (comparison < 0) 330 | // return _lesserOrEqual.Contains(item); 331 | // else if (comparison > 0) 332 | // return _greaterOrEqual.Contains(item); 333 | // else 334 | // return _lesserOrEqual.Contains(item) || _greaterOrEqual.Contains(item); 335 | // } 336 | // #endregion 337 | 338 | // /// 339 | // /// Returns a that represents this instance. 340 | // /// 341 | // /// 342 | // /// A that represents this instance. 343 | // /// 344 | // public override string ToString() 345 | // { 346 | // return "Count = " + Count; 347 | // } 348 | 349 | // #region ICollection Members 350 | // /// 351 | // /// Copies the elements of the to an , starting at a particular index. 352 | // /// 353 | // /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. 354 | // /// The zero-based index in at which copying begins. 355 | // /// 356 | // /// is null. 357 | // /// 358 | // /// is less than 0. 359 | // /// 360 | // /// is multidimensional.-or- is equal to or greater than the length of .-or-The number of elements in the source is greater than the available space from to the end of the destination .-or-Type T cannot be cast automatically to the type of the destination . 361 | // void ICollection.CopyTo(T[] array, int arrayIndex) 362 | // { 363 | // if (array == null) 364 | // throw new ArgumentNullException("array"); 365 | // if (arrayIndex < 0) 366 | // throw new ArgumentOutOfRangeException("arrayIndex"); 367 | // if (array.Rank != 1) 368 | // throw new ArgumentException("Array is multidimensional!"); 369 | // if (arrayIndex >= array.Length) 370 | // throw new ArgumentException("index > length of array"); 371 | // if (array.Length - arrayIndex < Count) 372 | // throw new ArgumentException("Not enough space in given array"); 373 | 374 | // T[] items = this.ToArray(); 375 | 376 | // for (int i = 0; i < items.Length; i++) 377 | // { 378 | // array[arrayIndex + i] = items[i]; 379 | // } 380 | // } 381 | 382 | // /// 383 | // /// Gets a value indicating whether the is read-only. 384 | // /// 385 | // /// 386 | // /// always returns false 387 | // bool ICollection.IsReadOnly 388 | // { 389 | // get 390 | // { 391 | // return false; 392 | // } 393 | // } 394 | // #endregion 395 | 396 | // #region IEnumerable Members 397 | // /// 398 | // /// Returns an enumerator that iterates through the collection. 399 | // /// 400 | // /// 401 | // /// A that can be used to iterate through the collection. 402 | // /// 403 | // IEnumerator IEnumerable.GetEnumerator() 404 | // { 405 | // return _lesserOrEqual.Concat(_greaterOrEqual).GetEnumerator(); 406 | // } 407 | // #endregion 408 | 409 | // #region IEnumerable Members 410 | // /// 411 | // /// Returns an enumerator that iterates through a collection. 412 | // /// 413 | // /// 414 | // /// An object that can be used to iterate through the collection. 415 | // /// 416 | // System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 417 | // { 418 | // return (this as IEnumerable).GetEnumerator(); 419 | // } 420 | // #endregion 421 | // } 422 | //} 423 | -------------------------------------------------------------------------------- /HandyCollections/RandomNumber/LinearFeedbackShiftRegister16.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace HandyCollections.RandomNumber 4 | { 5 | /// 6 | /// Creates a set of 16 bit numbers which repeats after 2^16 numbers (ie. longest possible period of non repeating numbers) 7 | /// 8 | public class LinearFeedbackShiftRegister16 9 | :IEnumerable 10 | { 11 | #region fields 12 | /// 13 | /// The number of numbers this sequence will go through before repeating 14 | /// 15 | public const int Period = ushort.MaxValue; 16 | 17 | private ushort _lfsr; 18 | private ushort _bit; 19 | #endregion 20 | 21 | #region constructors 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The seed to initialise the sequence with 26 | public LinearFeedbackShiftRegister16(ushort seed) 27 | { 28 | if (seed == 0) 29 | seed++; 30 | _lfsr = seed; 31 | } 32 | 33 | /// 34 | /// Initializes a new instance of the class. 35 | /// 36 | public LinearFeedbackShiftRegister16() 37 | :this((ushort)StaticRandomNumber.Random()) 38 | { 39 | 40 | } 41 | #endregion 42 | 43 | /// 44 | /// Gets the next random number in the sequence 45 | /// 46 | /// 47 | public ushort NextRandom() 48 | { 49 | _bit = (ushort)(((_lfsr >> 0) ^ (_lfsr >> 2) ^ (_lfsr >> 3) ^ (_lfsr >> 5)) & 1); 50 | _lfsr = (ushort)((_lfsr >> 1) | (_bit << 15)); 51 | 52 | return _lfsr; 53 | } 54 | 55 | #region IEnumerable 56 | /// 57 | /// Gets the enumerator which will iterate through all the values of this instance without repeating 58 | /// 59 | /// 60 | public IEnumerator GetEnumerator() 61 | { 62 | for (var i = 0; i < Period; i++) 63 | yield return NextRandom(); 64 | } 65 | 66 | /// 67 | /// Returns an enumerator that iterates through a collection. 68 | /// 69 | /// 70 | /// An object that can be used to iterate through the collection. 71 | /// 72 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 73 | { 74 | return (this as IEnumerable).GetEnumerator(); 75 | } 76 | #endregion 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /HandyCollections/RandomNumber/LinearFeedbackShiftRegister32.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace HandyCollections.RandomNumber 4 | { 5 | /// 6 | /// Creates a set of 32 bit numbers which repeats after 2^32 numbers (ie. longest possible period of non repeating numbers) 7 | /// 8 | public class LinearFeedbackShiftRegister32 9 | : IEnumerable 10 | { 11 | /// 12 | /// The number of numbers this sequence will go through before repeating 13 | /// 14 | public const uint Period = uint.MaxValue; 15 | 16 | readonly ushort _repeatThreshold; 17 | readonly LinearFeedbackShiftRegister16 _mostSignificantBits; 18 | 19 | ushort _lsb; 20 | readonly LinearFeedbackShiftRegister16 _leastSignificantBits; 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The seed to initialise the sequence with 26 | public LinearFeedbackShiftRegister32(uint seed) 27 | { 28 | _mostSignificantBits = new LinearFeedbackShiftRegister16((ushort)(seed & ushort.MaxValue)); 29 | _leastSignificantBits = new LinearFeedbackShiftRegister16((ushort)((seed >> 16) & ushort.MaxValue)); 30 | 31 | _repeatThreshold = _mostSignificantBits.NextRandom(); 32 | _lsb = _leastSignificantBits.NextRandom(); 33 | } 34 | 35 | /// 36 | /// Gets the next random number in the sequence 37 | /// 38 | /// 39 | public uint NextRandom() 40 | { 41 | ushort msb = _mostSignificantBits.NextRandom(); 42 | 43 | if (msb == _repeatThreshold) 44 | _lsb = _leastSignificantBits.NextRandom(); 45 | 46 | int a = msb << 16 | _lsb; 47 | 48 | unsafe 49 | { 50 | return *((uint*)&a); 51 | } 52 | } 53 | 54 | /// 55 | /// Gets the enumerator which will iterate through all the values of this instance without repeating 56 | /// 57 | /// 58 | public IEnumerator GetEnumerator() 59 | { 60 | for (uint i = 0; i < Period; i++) 61 | yield return NextRandom(); 62 | } 63 | 64 | /// 65 | /// Returns an enumerator that iterates through a collection. 66 | /// 67 | /// 68 | /// An object that can be used to iterate through the collection. 69 | /// 70 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 71 | { 72 | return (this as IEnumerable).GetEnumerator(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /HandyCollections/RandomNumber/StaticRandomNumber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HandyCollections.RandomNumber 4 | { 5 | /// 6 | /// A threadsafe static random number generator 7 | /// 8 | public class StaticRandomNumber 9 | { 10 | #region random number generation 11 | const uint U = 273326509 >> 19; 12 | 13 | /// 14 | /// Creates a random number from the specified seed 15 | /// 16 | /// The seed value 17 | /// The maximum value (exclusive) 18 | /// 19 | public static uint Random(uint seed, uint upperBound) 20 | { 21 | uint t = (seed ^ (seed << 11)); 22 | const uint w = 273326509; 23 | long i = (int)(0x7FFFFFFF & ((w ^ U) ^ (t ^ (t >> 8)))); 24 | return (uint)(i % upperBound); 25 | } 26 | #endregion 27 | 28 | /// 29 | /// Creates a random number, using the time as a seed 30 | /// 31 | /// The maximum value (exclusive) 32 | /// 33 | public static uint Random(uint upperBound = uint.MaxValue) 34 | { 35 | var ticks = DateTime.Now.Ticks; 36 | var time = ((uint) (ticks & uint.MaxValue)) | ((uint) ((ticks >> 32) & uint.MaxValue)); 37 | 38 | return Random(time, upperBound); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /HandyCollections/RecentlyUsedQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using JetBrains.Annotations; 3 | 4 | namespace HandyCollections 5 | { 6 | /// 7 | /// A double ended queue providing Most recently used and least recently used data 8 | /// 9 | /// 10 | public class RecentlyUsedQueue 11 | :IEnumerable 12 | { 13 | [NotNull] private readonly LinkedList _list = new(); 14 | 15 | /// 16 | /// Gets the count. 17 | /// 18 | /// The count. 19 | public int Count => _list.Count; 20 | 21 | /// 22 | /// 'Uses' the specified item, ie. moves/adds it to the Most recently used position 23 | /// 24 | /// The item. 25 | /// True, if the item was newly added, otherwise false 26 | public bool Use(T item) 27 | { 28 | bool newlyAdded = false; 29 | 30 | var node = _list.Find(item); 31 | if (node == null) 32 | { 33 | node = new LinkedListNode(item); 34 | newlyAdded = true; 35 | } 36 | else 37 | _list.Remove(node); 38 | 39 | _list.AddLast(node); 40 | 41 | return newlyAdded; 42 | } 43 | 44 | /// 45 | /// Gets the least recently used. 46 | /// 47 | /// The least recently used. 48 | public T LeastRecentlyUsed => _list.First.Value; 49 | 50 | /// 51 | /// Removes the least recently used. 52 | /// 53 | /// the item which was removed 54 | public T RemoveLeastRecentlyUsed() 55 | { 56 | var v = _list.First.Value; 57 | _list.RemoveFirst(); 58 | return v; 59 | } 60 | 61 | /// 62 | /// Gets the most recently used. 63 | /// 64 | /// The most recently used. 65 | public T MostRecentlyUsed => _list.Last.Value; 66 | 67 | /// 68 | /// Removes the most recently used. 69 | /// 70 | /// the item which was removed 71 | public T RemoveMostRecentlyUsed() 72 | { 73 | var v = _list.Last.Value; 74 | _list.RemoveLast(); 75 | return v; 76 | } 77 | 78 | /// 79 | /// Removes the specified item. 80 | /// 81 | /// The item. 82 | /// True; if anything was removed, otherwise false 83 | public bool Remove(T item) 84 | { 85 | return _list.Remove(item); 86 | } 87 | 88 | /// 89 | /// Returns an enumerator which goes from least to most recently used 90 | /// 91 | /// 92 | public IEnumerator GetEnumerator() 93 | { 94 | return _list.GetEnumerator(); 95 | } 96 | 97 | /// 98 | /// Returns an enumerator which goes from least to most recently used 99 | /// 100 | /// 101 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 102 | { 103 | return (this as IEnumerable).GetEnumerator(); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /HandyCollections/RingBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using JetBrains.Annotations; 5 | 6 | namespace HandyCollections 7 | { 8 | /// 9 | /// Stores the N most recently added items 10 | /// 11 | public class RingBuffer 12 | : IEnumerable 13 | { 14 | [NotNull] private readonly T[] _items; 15 | 16 | /// 17 | /// Indicates the number of items added to the collection and currently stored 18 | /// 19 | public int Count { get; private set; } 20 | 21 | /// 22 | /// The size of this ring buffer 23 | /// 24 | public int Capacity => _items.Length; 25 | 26 | private int _end; 27 | 28 | /// 29 | /// 30 | /// 31 | /// 32 | public T this[int index] 33 | { 34 | get 35 | { 36 | 37 | if (index < 0 || index >= Count) throw new IndexOutOfRangeException(); 38 | 39 | if (Count < Capacity) 40 | return _items[index]; 41 | else 42 | return _items[(_end + index) % _items.Length]; 43 | } 44 | } 45 | 46 | /// 47 | /// 48 | /// 49 | /// 50 | public RingBuffer(int size) 51 | { 52 | if (size < 0) throw new ArgumentOutOfRangeException(nameof(size)); 53 | 54 | _items = new T[size]; 55 | } 56 | 57 | 58 | /// 59 | /// 60 | /// 61 | /// 62 | public void Add(T item) 63 | { 64 | _items[_end] = item; 65 | _end = (_end + 1) % _items.Length; 66 | 67 | if (Count < _items.Length) 68 | Count++; 69 | } 70 | 71 | public void Add([NotNull] T[] items) 72 | { 73 | if (items == null) throw new ArgumentNullException(nameof(items)); 74 | 75 | Add(new ArraySegment(items)); 76 | } 77 | 78 | public void Add(ArraySegment items) 79 | { 80 | if (items.Count > Capacity) 81 | { 82 | //this single operation will overwrite the entire buffer 83 | //Reset buffer to empty and copy in last items 84 | 85 | _end = Capacity; 86 | Count = Capacity; 87 | Array.Copy(items.Array, items.Offset + items.Count - Capacity, _items, 0, Capacity); 88 | } 89 | else 90 | { 91 | if (_end + items.Count > Capacity) 92 | { 93 | // going to run off the end of the buffer; 94 | // copy as much as we can then put the rest at the start of the buffer 95 | var remainingSpace = Capacity - _end; 96 | Array.Copy(items.Array, items.Offset, _items, _end, remainingSpace); 97 | Array.Copy(items.Array, items.Offset + remainingSpace, _items, 0, items.Count - remainingSpace); 98 | _end = (_end + items.Count) % _items.Length; 99 | } 100 | else 101 | { 102 | // copy the data into the buffer 103 | Array.Copy(items.Array, items.Offset, _items, _end, items.Count); 104 | _end += items.Count; 105 | } 106 | 107 | Count = Math.Min(Count + items.Count, Capacity); 108 | } 109 | } 110 | 111 | /// 112 | /// Copy as much data as possible into the given array segment 113 | /// 114 | /// 115 | /// A subsection of the given segment, which contains the data 116 | public ArraySegment CopyTo(ArraySegment output) 117 | { 118 | var count = Math.Min(Count, output.Count); 119 | var start = (_end + Capacity - Count) % Capacity; 120 | if (start + count < Capacity) 121 | { 122 | //We can copy all the data we need in a single operation (no wrapping) 123 | Array.Copy(_items, start, output.Array, output.Offset, count); 124 | } 125 | else 126 | { 127 | //Copying this much data wraps around, so we need 2 copies 128 | var cp = Capacity - start; 129 | Array.Copy(_items, start, output.Array, output.Offset, cp); 130 | Array.Copy(_items, 0, output.Array, output.Offset + cp, count - cp); 131 | } 132 | 133 | return new ArraySegment(output.Array, output.Offset, count); 134 | } 135 | 136 | public void Clear() 137 | { 138 | Count = 0; 139 | _end = 0; 140 | } 141 | 142 | /// 143 | /// Enumerate items in the ringbuffer (oldest to newest) 144 | /// 145 | /// 146 | public IEnumerator GetEnumerator() 147 | { 148 | for (var i = 0; i < Count; i++) 149 | yield return this[i]; 150 | } 151 | 152 | IEnumerator IEnumerable.GetEnumerator() 153 | { 154 | return GetEnumerator(); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /HandyCollections/Set/OrderedSet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace HandyCollections.Set 5 | { 6 | /// 7 | /// A set which preserves the order in which items were added 8 | /// 9 | /// 10 | public class OrderedSet 11 | : ISet 12 | { 13 | #region fields 14 | private uint _next = 0; 15 | private readonly Dictionary _items; 16 | 17 | private readonly IEnumerable _enumerable; 18 | #endregion 19 | 20 | #region constructor 21 | /// 22 | /// Create a new ordered set with the given equality comparer 23 | /// 24 | /// 25 | public OrderedSet(IEqualityComparer comparer) 26 | { 27 | _items = new Dictionary(comparer); 28 | 29 | _enumerable = _items 30 | .OrderBy(a => a.Value) 31 | .Select(a => a.Key); 32 | } 33 | 34 | /// 35 | /// Create a new ordered set with the default equality comparer 36 | /// 37 | public OrderedSet() 38 | : this(EqualityComparer.Default) 39 | { 40 | } 41 | #endregion 42 | 43 | /// 44 | /// Add a new item to the set 45 | /// 46 | /// 47 | /// 48 | public bool Add(T item) 49 | { 50 | if (_items.ContainsKey(item)) 51 | return false; 52 | 53 | _items.Add(item, _next++); 54 | return true; 55 | } 56 | 57 | /// 58 | /// Remove all items in this set which are in the given set 59 | /// 60 | /// 61 | public void ExceptWith(IEnumerable other) 62 | { 63 | foreach (var item in other) 64 | Remove(item); 65 | } 66 | 67 | /// 68 | /// Modify this collection so that it only contains items which are in both collections 69 | /// 70 | /// 71 | public void IntersectWith(IEnumerable other) 72 | { 73 | foreach (var key in _items.Keys.Where(key => !other.Contains(key)).ToArray()) 74 | _items.Remove(key); 75 | } 76 | 77 | /// 78 | /// Determine whether this set is a proper subset of given set 79 | /// 80 | /// 81 | /// 82 | public bool IsProperSubsetOf(IEnumerable other) 83 | { 84 | if (_items.Keys.Any(key => !other.Contains(key))) 85 | return false; 86 | 87 | return other.Count() > _items.Count; 88 | } 89 | 90 | /// 91 | /// Determines if this set is a proper superset of the given collection (this is a superset, and no merely equal) 92 | /// 93 | /// 94 | /// 95 | public bool IsProperSupersetOf(IEnumerable other) 96 | { 97 | var count = 0; 98 | foreach (var item in other) 99 | { 100 | count++; 101 | if (!_items.ContainsKey(item)) 102 | return false; 103 | } 104 | 105 | return count < _items.Count; 106 | } 107 | 108 | /// 109 | /// Determine if this set is a subset of the given collection 110 | /// 111 | /// 112 | /// 113 | public bool IsSubsetOf(IEnumerable other) 114 | { 115 | return _items.Keys.All(other.Contains); 116 | } 117 | 118 | /// 119 | /// Determine if this set is a superset of the given collection 120 | /// 121 | /// 122 | /// 123 | public bool IsSupersetOf(IEnumerable other) 124 | { 125 | return other.All(_items.ContainsKey); 126 | } 127 | 128 | /// 129 | /// Determine if either collection contains an item which is also in the other collection 130 | /// 131 | /// 132 | /// 133 | public bool Overlaps(IEnumerable other) 134 | { 135 | //we'll put each item into the temp container and check the set 136 | return other.Any(item => _items.ContainsKey(item)); 137 | } 138 | 139 | /// 140 | /// Determine if this set equals the given collection (contains the same items) 141 | /// 142 | /// 143 | /// 144 | public bool SetEquals(IEnumerable other) 145 | { 146 | //Check that the set contains all the items in other 147 | var count = 0; 148 | foreach (var item in other) 149 | { 150 | count++; 151 | 152 | if (!_items.ContainsKey(item)) 153 | return false; 154 | } 155 | 156 | //If the count is the same then they are identical 157 | return count == _items.Count; 158 | } 159 | 160 | /// 161 | /// Modify this set to contain items which are in itself, or the other set, but *not* both 162 | /// 163 | /// 164 | public void SymmetricExceptWith(IEnumerable other) 165 | { 166 | var addItems = other.Where(a => !Contains(a)).ToArray(); 167 | var removeItems = other.Where(Contains).ToArray(); 168 | 169 | ExceptWith(removeItems); 170 | UnionWith(addItems); 171 | } 172 | 173 | /// 174 | /// Add all items to this collection 175 | /// 176 | /// 177 | public void UnionWith(IEnumerable other) 178 | { 179 | foreach (var item in other) 180 | Add(item); 181 | } 182 | 183 | void ICollection.Add(T item) 184 | { 185 | Add(item); 186 | } 187 | 188 | /// 189 | /// Remove all items from this set 190 | /// 191 | public void Clear() 192 | { 193 | _items.Clear(); 194 | _next = 0; 195 | } 196 | 197 | /// 198 | /// Determine if this set contains the given item 199 | /// 200 | /// 201 | /// 202 | public bool Contains(T item) 203 | { 204 | return _items.ContainsKey(item); 205 | } 206 | 207 | /// 208 | /// Copy items into the given array, starting at the given index (in the order the items were added) 209 | /// 210 | /// 211 | /// 212 | public void CopyTo(T[] array, int arrayIndex) 213 | { 214 | _items.Keys.CopyTo(array, arrayIndex); 215 | } 216 | 217 | /// 218 | /// Number of items in this collection 219 | /// 220 | public int Count => _items.Count; 221 | 222 | /// 223 | /// False 224 | /// 225 | public bool IsReadOnly => false; 226 | 227 | /// 228 | /// Remove the given item from this collection 229 | /// 230 | /// 231 | /// 232 | public bool Remove(T item) 233 | { 234 | return _items.Remove(item); 235 | } 236 | 237 | /// 238 | /// Enumerate the items in the order they were added 239 | /// 240 | /// 241 | public IEnumerator GetEnumerator() 242 | { 243 | return _enumerable.GetEnumerator(); 244 | } 245 | 246 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 247 | { 248 | return _items.GetEnumerator(); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /HandyCollections/TypedWeakReference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace HandyCollections 5 | { 6 | /// 7 | /// A weak reference to a an object of a specific type. The item may still be garbage collected even while a weak reference is held. 8 | /// 9 | /// 10 | public class TypedWeakReference where T : class 11 | { 12 | [NotNull] private readonly WeakReference _reference; 13 | 14 | /// 15 | /// Gets a value indicating whether this instance is alive. 16 | /// 17 | /// true if this instance is alive; otherwise, false. 18 | public bool IsAlive => _reference.IsAlive; 19 | 20 | /// 21 | /// Gets or sets the target. 22 | /// 23 | /// The target. 24 | [CanBeNull] public T Target 25 | { 26 | get => _reference.Target as T; 27 | set => _reference.Target = value; 28 | } 29 | 30 | /// 31 | /// Gets a value indicating whether the object referenced by this weak reference is tracked after finalisation. 32 | /// 33 | /// true if the object referenced by this weak reference is tracked after finalisation; otherwise, false. 34 | public bool TrackResurrection => _reference.TrackResurrection; 35 | 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// 39 | /// The target. 40 | public TypedWeakReference(T target) 41 | { 42 | _reference = new WeakReference(target); 43 | } 44 | 45 | /// 46 | /// Initializes a new instance of the class. 47 | /// 48 | /// The target. 49 | /// if set to true the object referenced by this weak reference is tracked after finalisation. 50 | public TypedWeakReference(T target, bool trackResurrection) 51 | { 52 | _reference = new WeakReference(target, trackResurrection); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /HandyCollections/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /HandyCollectionsTest/BinaryTreeTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using HandyCollections.BinaryTree; 7 | 8 | namespace HandyCollectionsTest 9 | { 10 | [TestClass] 11 | public class BinaryTreeTest 12 | { 13 | [TestMethod] 14 | public void BuildTree() 15 | { 16 | BinaryTree tree = new BinaryTree(); 17 | 18 | var a = tree.Add(3, "a"); 19 | var b = tree.Add(2, "b"); 20 | var c = tree.Add(1, "c"); 21 | var d = tree.Add(4, "d"); 22 | 23 | Assert.IsTrue(a.Parent == null); 24 | Assert.IsTrue(a == tree.Root); 25 | Assert.IsTrue(a.Left == b); 26 | Assert.IsTrue(b.Left == c); 27 | Assert.IsTrue(a.Right == d); 28 | } 29 | 30 | [TestMethod] 31 | [ExpectedException(typeof(ArgumentException))] 32 | public void DuplicateKeysNotAllowed() 33 | { 34 | BinaryTree tree = new BinaryTree(); 35 | 36 | tree.Add(3, "a"); 37 | tree.Add(3, "a"); 38 | } 39 | 40 | [TestMethod] 41 | [ExpectedException(typeof(KeyNotFoundException))] 42 | public void SearchingEmptyTreeFails() 43 | { 44 | var tree = new BinaryTree(); 45 | tree.Find(1); 46 | } 47 | 48 | [TestMethod] 49 | [ExpectedException(typeof(KeyNotFoundException))] 50 | public void SearchingForNonExistantKeyFails() 51 | { 52 | BinaryTree tree = new BinaryTree(); 53 | 54 | var a = tree.Add(4, "a"); 55 | var b = tree.Add(3, "b"); 56 | var c = tree.Add(5, "c"); 57 | var d = tree.Add(2, "d"); 58 | var e = tree.Add(6, "e"); 59 | var f = tree.Add(-1, "f"); 60 | var g = tree.Add(10, "g"); 61 | var h = tree.Add(44, "h"); 62 | 63 | tree.Find(100); 64 | } 65 | 66 | [TestMethod] 67 | public void Find() 68 | { 69 | BinaryTree tree = new BinaryTree(); 70 | 71 | var a = tree.Add(4, "a"); 72 | var b = tree.Add(3, "b"); 73 | var c = tree.Add(5, "c"); 74 | var d = tree.Add(2, "d"); 75 | var e = tree.Add(6, "e"); 76 | var f = tree.Add(-1, "f"); 77 | var g = tree.Add(10, "g"); 78 | var h = tree.Add(44, "h"); 79 | 80 | Assert.IsTrue(tree.Find(4) == a); 81 | Assert.IsTrue(tree.Find(3) == b); 82 | Assert.IsTrue(tree.Find(5) == c); 83 | Assert.IsTrue(tree.Find(2) == d); 84 | Assert.IsTrue(tree.Find(6) == e); 85 | Assert.IsTrue(tree.Find(-1) == f); 86 | Assert.IsTrue(tree.Find(10) == g); 87 | Assert.IsTrue(tree.Find(44) == h); 88 | } 89 | 90 | [TestMethod] 91 | public void Rotate() 92 | { 93 | BinaryTree tree = new BinaryTree(); 94 | 95 | var root = tree.Add(100, "ROOT"); 96 | 97 | var q = tree.Add(5, "Q"); 98 | var p = tree.Add(3, "P"); 99 | var c = tree.Add(6, "C"); 100 | var a = tree.Add(2, "A"); 101 | var b = tree.Add(4, "B"); 102 | 103 | Action testLeftTree = () => 104 | { 105 | Assert.IsTrue(tree.Root == root); 106 | 107 | Assert.IsTrue(root.Left == q); 108 | Assert.IsTrue(root.Right == null); 109 | 110 | Assert.IsTrue(q.Left == p); 111 | Assert.IsTrue(q.Right == c); 112 | 113 | Assert.IsTrue(c.Left == null); 114 | Assert.IsTrue(c.Right == null); 115 | 116 | Assert.IsTrue(p.Left == a); 117 | Assert.IsTrue(p.Right == b); 118 | }; 119 | testLeftTree(); 120 | 121 | tree.Rotate(q, true); 122 | 123 | Assert.IsTrue(tree.Root == root); 124 | 125 | Assert.IsTrue(root.Left == p); 126 | Assert.IsTrue(root.Right == null); 127 | 128 | Assert.IsTrue(p.Left == a); 129 | Assert.IsTrue(p.Right == q); 130 | 131 | Assert.IsTrue(q.Left == b); 132 | Assert.IsTrue(q.Right == c); 133 | 134 | tree.Rotate(p, false); 135 | 136 | testLeftTree(); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /HandyCollectionsTest/BloomFilterTest.cs: -------------------------------------------------------------------------------- 1 | using HandyCollections.BloomFilter; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace HandyCollectionsTest 5 | { 6 | [TestClass] 7 | public class BloomFilterTest 8 | { 9 | [TestMethod] 10 | public void BasicBloomFilterCorrectlyActsAsASet() 11 | { 12 | BloomFilter filter = new BloomFilter(100, 2); 13 | 14 | //10 cannot already be in the collection, so inserting it must succeed 15 | Assert.IsFalse(filter.Add(10)); 16 | Assert.IsTrue(filter.Add(10)); 17 | 18 | //10 is in the collection 19 | Assert.IsTrue(filter.Contains(10)); 20 | 21 | //check a load more numbers 22 | for (int i = 0; i < 100; i++) 23 | { 24 | filter.Add(i); 25 | Assert.IsTrue(filter.Contains(i)); 26 | } 27 | } 28 | 29 | [TestMethod] 30 | public void FalsePostiveRateCrossesThresholdAtCorrectCount() 31 | { 32 | var filter = new BloomFilter(100, 0.1f); 33 | 34 | for (int i = 0; i < 99; i++) 35 | { 36 | filter.Add(i); 37 | Assert.IsTrue(filter.Contains(i)); 38 | } 39 | 40 | Assert.IsFalse(filter.FalsePositiveRate > 0.1f); 41 | 42 | filter.Add(1000); 43 | filter.Add(1001); 44 | filter.Add(1002); 45 | 46 | Assert.IsTrue(filter.FalsePositiveRate > 0.1f); 47 | } 48 | 49 | [TestMethod] 50 | public void CountingBloomFilter() 51 | { 52 | CountingBloomFilter filter = new CountingBloomFilter(10, 2); 53 | 54 | Assert.IsFalse(filter.Add(10)); 55 | Assert.IsTrue(filter.Contains(10)); 56 | Assert.IsTrue(filter.Remove(10)); 57 | Assert.IsFalse(filter.Contains(10)); 58 | } 59 | 60 | [TestMethod] 61 | public void RollbackWhenRemovingANonExistantItem() 62 | { 63 | var a = new CountingBloomFilter(1000, 0.01f); 64 | 65 | for (var i = 0; i < 1000; i++) 66 | a.Add(i); 67 | 68 | byte[] copy = (byte[])a.Array.Clone(); 69 | 70 | Assert.IsFalse(a.Remove(1001)); 71 | 72 | for (int i = 0; i < a.Array.Length; i++) 73 | { 74 | Assert.AreEqual(a.Array[i], copy[i]); 75 | } 76 | } 77 | 78 | [TestMethod] 79 | public void ScalableBloomFilterCorrectlyActsAsASet() 80 | { 81 | IBloomFilter filter = new ScalableBloomFilter(0.9, 10, 0.001); 82 | 83 | //10 cannot already be in the collection, so inserting it must succeed 84 | Assert.IsFalse(filter.Add(10)); 85 | 86 | //10 is in the collection 87 | Assert.IsTrue(filter.Contains(10)); 88 | 89 | //check a load more numbers 90 | for (int i = 0; i < 100; i++) 91 | { 92 | filter.Add(i); 93 | Assert.IsTrue(filter.Contains(i)); 94 | } 95 | } 96 | 97 | [TestMethod] 98 | public void ClearingScalabeBloomFilterResetsEverything() 99 | { 100 | ScalableBloomFilter filter = new ScalableBloomFilter(0.9, 1000, 0.001); 101 | 102 | //Add a load of numbers 103 | for (int i = 0; i < 100; i++) 104 | { 105 | filter.Add(i); 106 | Assert.IsTrue(filter.Contains(i)); 107 | } 108 | Assert.AreEqual(100, filter.Count); 109 | 110 | filter.Clear(); 111 | Assert.AreEqual(0, filter.Count); 112 | 113 | //Add them again 114 | for (int i = 0; i < 100; i++) 115 | { 116 | filter.Add(i); 117 | Assert.IsTrue(filter.Contains(i)); 118 | } 119 | Assert.AreEqual(100, filter.Count); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /HandyCollectionsTest/Extensions.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using System.Text; 3 | //using System.Collections.Generic; 4 | //using System.Linq; 5 | //using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | //using HandyCollections.Extensions; 7 | 8 | //namespace HandyCollectionsTest 9 | //{ 10 | // [TestClass] 11 | // public class Extensions 12 | // { 13 | // [TestMethod] 14 | // public void TestMethod1() 15 | // { 16 | 17 | // } 18 | 19 | // public static void CorrectnessTest() 20 | // { 21 | // Console.WriteLine("Testing selection on an in order list"); 22 | // TestSelection(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); 23 | 24 | // Console.WriteLine("Testing selection on a reversed list"); 25 | // TestSelection(new int[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }); 26 | 27 | // Console.WriteLine("Testing selection on a random order list"); 28 | // TestSelection(new int[] { 1, 32, 7, 45, 56, 980, 23, 29580, 3857, 1 }); 29 | // } 30 | 31 | // private static void TestSelection(IList values) 32 | // { 33 | // List sorted = new List(values); 34 | // sorted.Sort(); 35 | 36 | // for (int i = 0; i < values.Count; i++) 37 | // { 38 | // Assert.IsTrue(sorted[i] == values[values.OrderSelect(i)], "Incorrect order select, (sorted = " + sorted[i] + ") != (OrderSelect = " + values.OrderSelect(i) + ")"); 39 | // } 40 | // } 41 | // } 42 | //} 43 | -------------------------------------------------------------------------------- /HandyCollectionsTest/Extensions/IListExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | using HandyCollections.Extensions; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace HandyCollectionsTest.Extensions 5 | { 6 | [TestClass] 7 | public class ArrayExtensionsTest 8 | { 9 | private readonly int[] _array = { 10 | 1, 7, 10, 234, 12, 235, 213, 35, 475, 352, 21, 76, 987, 576, 64, 85, 253, 12, 634, 765, 45, 24, 31, 365, 5876, 457, 4, 643, 765, 64, 42, 13, 65 11 | }; 12 | 13 | private void AssertPartitioned(int pivotIndex, int pivotValue) 14 | { 15 | for (var i = 0; i < _array.Length; i++) 16 | { 17 | if (i < pivotIndex) 18 | Assert.IsTrue(_array[i] < pivotValue); 19 | else 20 | Assert.IsTrue(_array[i] >= pivotValue); 21 | } 22 | } 23 | 24 | [TestMethod] 25 | public void AssertThat_PartitionIntegers_PartitionsUnorderedArray_WithElement() 26 | { 27 | var pivot = _array.Partition(0, _array.Length - 1, 23); 28 | 29 | AssertPartitioned(pivot, _array[pivot]); 30 | } 31 | 32 | [TestMethod] 33 | public void AssertThat_PartitionIntegers_PartitionsUnorderedArray_WithFunc() 34 | { 35 | var pivot = _array.Partition(a => a < 350, 0, _array.Length - 1); 36 | 37 | AssertPartitioned(pivot, 350); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /HandyCollectionsTest/Geometry/OctreeTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Numerics; 5 | using HandyCollections.Geometry; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using SwizzleMyVectors.Geometry; 8 | 9 | namespace HandyCollectionsTest.Geometry 10 | { 11 | [TestClass] 12 | public class OctreeTest 13 | { 14 | private readonly Octree _octree = new Octree(new BoundingBox(new Vector3(0), new Vector3(1000)), 1, 32); 15 | 16 | [TestInitialize] 17 | public void Initialize() 18 | { 19 | } 20 | 21 | [TestMethod] 22 | public void AssertThat_QueryingOctreeContainedBy_ReturnsItemsContainedInQueryBounds() 23 | { 24 | _octree.Insert(new BoundingBox(new Vector3(1), new Vector3(2)), "hello"); 25 | _octree.Insert(new BoundingBox(new Vector3(8), new Vector3(9)), "world"); 26 | Assert.AreEqual(2, _octree.Count); 27 | 28 | Assert.AreEqual("hello", _octree.ContainedBy(new BoundingBox(new Vector3(0), new Vector3(3))).Single()); 29 | } 30 | 31 | [TestMethod] 32 | public void AssertThat_QueryingOctreeIntersects_ReturnsItemsIntersectingQueryBounds() 33 | { 34 | _octree.Insert(new BoundingBox(new Vector3(1), new Vector3(2)), "hello"); 35 | _octree.Insert(new BoundingBox(new Vector3(2), new Vector3(3)), "world"); 36 | _octree.Insert(new BoundingBox(new Vector3(3), new Vector3(4)), "こにちは"); 37 | Assert.AreEqual(3, _octree.Count); 38 | 39 | var items = _octree.Intersects(new BoundingBox(new Vector3(2.5f), new Vector3(4.5f))).ToArray(); 40 | Assert.AreEqual(2, items.Length); 41 | 42 | Assert.IsFalse(items.Contains("hello")); 43 | Assert.IsTrue(items.Contains("world")); 44 | Assert.IsTrue(items.Contains("こにちは")); 45 | } 46 | 47 | [TestMethod] 48 | public void AssertThat_RemovingFromOctreeByItem_PreventsItemsFromBeingReturnedInQuery() 49 | { 50 | //Add 2 items 51 | _octree.Insert(new BoundingBox(new Vector3(2), new Vector3(3)), "world"); 52 | _octree.Insert(new BoundingBox(new Vector3(3), new Vector3(4)), "こにちは"); 53 | Assert.AreEqual(2, _octree.Count); 54 | 55 | //Remove one of them 56 | _octree.Remove(new BoundingBox(Vector3.Zero, new Vector3(10)), "こにちは"); 57 | Assert.AreEqual(1, _octree.Count); 58 | 59 | //This bounds would return *both* items if the removal was not there 60 | var items = _octree.Intersects(new BoundingBox(new Vector3(2.5f), new Vector3(4.5f))).ToArray(); 61 | 62 | //Check that we didn't find the deleted item 63 | Assert.AreEqual(1, items.Length); 64 | Assert.IsTrue(items.Contains("world")); 65 | Assert.IsFalse(items.Contains("こにちは")); 66 | } 67 | 68 | [TestMethod] 69 | public void AssertThat_RemovingFromOctreeByPredicate_PreventsItemsFromBeingReturnedInQuery() 70 | { 71 | //Insert 3 items 72 | _octree.Insert(new BoundingBox(new Vector3(1), new Vector3(2)), "hello"); 73 | _octree.Insert(new BoundingBox(new Vector3(2), new Vector3(3)), "world"); 74 | _octree.Insert(new BoundingBox(new Vector3(3), new Vector3(4)), "こにちは"); 75 | Assert.AreEqual(3, _octree.Count); 76 | 77 | //Remove all but one 78 | _octree.Remove(new BoundingBox(new Vector3(0), new Vector3(100)), a => a != "world"); 79 | Assert.AreEqual(1, _octree.Count); 80 | 81 | var items = _octree.Intersects(new BoundingBox(new Vector3(2.5f), new Vector3(4.5f))).ToArray(); 82 | 83 | Assert.AreEqual(1, items.Length); 84 | 85 | Assert.IsFalse(items.Contains("hello")); 86 | Assert.IsTrue(items.Contains("world")); 87 | Assert.IsFalse(items.Contains("こにちは")); 88 | } 89 | 90 | [TestMethod] 91 | public void AssertThat_EnumeratingQuadtree_ReturnsAllItems() 92 | { 93 | //Insert item out of bounds 94 | _octree.Insert(new BoundingBox(new Vector3(-1), new Vector3(-2)), "hello"); 95 | Assert.AreEqual(1, _octree.Count); 96 | 97 | //Insert items in bounds 98 | _octree.Insert(new BoundingBox(new Vector3(20), new Vector3(30)), "world"); 99 | _octree.Insert(new BoundingBox(new Vector3(300), new Vector3(400)), "こにちは"); 100 | Assert.AreEqual(3, _octree.Count); 101 | 102 | //Enumerate and check we have all items 103 | var items = _octree.ToArray(); 104 | Assert.AreEqual(3, items.Length); 105 | } 106 | 107 | [TestMethod] 108 | public void AssertThat_RemovingItemsOutsideBoundary_RemovesItems() 109 | { 110 | _octree.Insert(new BoundingBox(new Vector3(-3), new Vector3(-4)), "hello"); 111 | _octree.Insert(new BoundingBox(new Vector3(20), new Vector3(30)), "world"); 112 | Assert.AreEqual(2, _octree.Count); 113 | 114 | _octree.Remove(new BoundingBox(new Vector3(-10), new Vector3(-1)), "hello"); 115 | Assert.AreEqual(1, _octree.Count); 116 | 117 | Assert.AreEqual(1, _octree.Count); 118 | } 119 | 120 | [TestMethod] 121 | public void StressTest() 122 | { 123 | Random r = new Random(); 124 | Func randomBounds = () => { 125 | var min = new Vector3((float)r.NextDouble() * 999, (float) r.NextDouble() * 999, (float) r.NextDouble() * 999); 126 | var max = new Vector3((float) r.NextDouble() * (1000 - min.X), (float) r.NextDouble() * (1000 - min.Y), (float) r.NextDouble() * (1000 - min.Z)) + min; 127 | return new BoundingBox(min, max); 128 | }; 129 | 130 | Stopwatch w = new Stopwatch(); 131 | w.Start(); 132 | const int COUNT = 100000; 133 | for (int i = 0; i < COUNT; i++) 134 | _octree.Insert(randomBounds(), "Hello"); 135 | Console.WriteLine(w.Elapsed.TotalMilliseconds / COUNT + "ms"); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /HandyCollectionsTest/Geometry/QuadtreeTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Numerics; 3 | using HandyCollections.Geometry; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using SwizzleMyVectors.Geometry; 6 | 7 | namespace HandyCollectionsTest.Geometry 8 | { 9 | [TestClass] 10 | public class QuadtreeTest 11 | { 12 | private readonly Quadtree _tree = new Quadtree(new BoundingRectangle(Vector2.Zero, new Vector2(100, 100)), 3, 3); 13 | 14 | [TestMethod] 15 | public void AssertThat_Intersects_FindsItemInBounds() 16 | { 17 | _tree.Insert(new BoundingRectangle(new Vector2(10, 10), new Vector2(20, 20)), "A"); 18 | _tree.Insert(new BoundingRectangle(new Vector2(90, 90), new Vector2(80, 80)), "B"); 19 | 20 | var results = _tree.Intersects(new BoundingRectangle(new Vector2(0, 0), new Vector2(20, 20))).ToArray(); 21 | 22 | Assert.AreEqual(1, results.Length); 23 | Assert.AreEqual("A", results.Single()); 24 | } 25 | 26 | [TestMethod] 27 | public void AssertThat_Intersects_FindsItemOutOfBounds() 28 | { 29 | _tree.Insert(new BoundingRectangle(new Vector2(-10, -10), new Vector2(-5, -5)), "A"); 30 | _tree.Insert(new BoundingRectangle(new Vector2(90, 90), new Vector2(80, 80)), "B"); 31 | 32 | var results = _tree.Intersects(new BoundingRectangle(new Vector2(-15, -15), new Vector2(-5, -5))).ToArray(); 33 | 34 | Assert.AreEqual(1, results.Length); 35 | Assert.AreEqual("A", results.Single()); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /HandyCollectionsTest/HandyCollectionsTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 7 | 8 | 2.0 9 | {B3A032F2-4168-4544-8B3C-2146B925E309} 10 | Library 11 | Properties 12 | HandyCollectionsTest 13 | HandyCollectionsTest 14 | v4.6 15 | 512 16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | false 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | false 37 | 38 | 39 | 40 | 41 | ..\packages\SwizzleMyVectors.1.1.1.0\lib\net46\SwizzleMyVectors.dll 42 | True 43 | 44 | 45 | 46 | 3.5 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | False 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | {C18B7765-C1F8-4769-A114-291D0BDB5865} 81 | HandyCollections 82 | 83 | 84 | 85 | 86 | 87 | 88 | 95 | -------------------------------------------------------------------------------- /HandyCollectionsTest/HandyCollectionsTest.ncrunchproject: -------------------------------------------------------------------------------- 1 | 2 | false 3 | false 4 | false 5 | true 6 | false 7 | false 8 | false 9 | false 10 | true 11 | true 12 | false 13 | true 14 | true 15 | 60000 16 | 17 | 18 | 19 | AutoDetect 20 | MSTestAccessorsExist 21 | -------------------------------------------------------------------------------- /HandyCollectionsTest/Heap/MinHeapTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using HandyCollections.Heap; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace HandyCollectionsTest.Heap 8 | { 9 | [TestClass] 10 | public class MinHeapTest 11 | { 12 | private void AssertHeapIsInOrder(IMinHeap heap, IComparer comparer = null) 13 | { 14 | comparer = comparer ?? Comparer.Default; 15 | 16 | var first = true; 17 | var min = default(T); 18 | 19 | while (heap.Count > 0) 20 | { 21 | var i = heap.RemoveMin(); 22 | if (!first) 23 | Assert.IsTrue(comparer.Compare(i, min) >= 0); 24 | 25 | min = i; 26 | first = false; 27 | } 28 | } 29 | 30 | [TestMethod] 31 | [ExpectedException(typeof(ArgumentOutOfRangeException))] 32 | public void AssertThat_Construct_Throws_WithNegativeCapacity() 33 | { 34 | new MinHeap(-1); 35 | } 36 | 37 | [TestMethod] 38 | [ExpectedException(typeof(ArgumentOutOfRangeException))] 39 | public void AssertThat_Construct_Throws_WithNegativeCapacity_WithComparer() 40 | { 41 | new MinHeap(-1, Comparer.Default); 42 | } 43 | 44 | [TestMethod] 45 | [ExpectedException(typeof(ArgumentOutOfRangeException))] 46 | public void AssertThat_Construct_Throws_WithNegativeCapacity_WithComparison() 47 | { 48 | new MinHeap(-1, Comparer.Default.Compare); 49 | } 50 | 51 | [TestMethod] 52 | public void AssertThat_Clear_EmptiesHeap() 53 | { 54 | var heap = new MinHeap(); 55 | heap.Add(1); 56 | heap.Add(3); 57 | heap.Add(2); 58 | 59 | Assert.AreEqual(3, heap.Count); 60 | heap.Clear(); 61 | Assert.AreEqual(0, heap.Count); 62 | } 63 | 64 | [TestMethod] 65 | public void AssertThat_Add_UpdatesMinimum() 66 | { 67 | IMinHeap heap = new MinHeap(); 68 | 69 | heap.Add(5); 70 | Assert.AreEqual(5, heap.Minimum); 71 | 72 | heap.Add(3); 73 | Assert.AreEqual(3, heap.Minimum); 74 | 75 | heap.Add(7); 76 | Assert.AreEqual(3, heap.Minimum); 77 | } 78 | 79 | [TestMethod] 80 | public void AssertThat_Remove_UpdatesMinimum() 81 | { 82 | IMinHeap heap = new MinHeap(); 83 | 84 | heap.Add(5); 85 | heap.Add(3); 86 | heap.Add(7); 87 | 88 | Assert.AreEqual(3, heap.RemoveMin()); 89 | Assert.AreEqual(5, heap.RemoveMin()); 90 | Assert.AreEqual(7, heap.RemoveMin()); 91 | } 92 | 93 | [TestMethod] 94 | public void AssertThat_AddingRange_CorrectlyOrdersHeap() 95 | { 96 | const int itemCount = 5000; 97 | 98 | var r = new Random(12434); 99 | var numbers = new List(itemCount); 100 | 101 | for (var i = 0; i < itemCount; i++) 102 | { 103 | var n = r.Next(-100, 100); 104 | numbers.Add(n); 105 | } 106 | 107 | IMinHeap heap = new MinHeap(itemCount, Comparer.Default); 108 | heap.Add(numbers); 109 | 110 | while (heap.Count > 0) 111 | { 112 | Assert.AreEqual(heap.Count, numbers.Count); 113 | 114 | var min = numbers.Min(); 115 | numbers.Remove(min); 116 | 117 | Assert.AreEqual(min, heap.RemoveMin()); 118 | } 119 | } 120 | 121 | [TestMethod] 122 | public void AssertThat_AddingAndRemoving_UpdatesMinimum_WithManyValues() 123 | { 124 | var r = new Random(12434); 125 | var numbers = new List(1000); 126 | IMinHeap heap = new MinHeap(1000, Comparer.Default); 127 | 128 | for (var i = 0; i < 1000; i++) 129 | { 130 | var n = r.Next(-100, 100); 131 | numbers.Add(n); 132 | heap.Add(n); 133 | } 134 | 135 | while (heap.Count > 0) 136 | { 137 | Assert.AreEqual(heap.Count, numbers.Count); 138 | 139 | var min = numbers.Min(); 140 | numbers.Remove(min); 141 | 142 | Assert.AreEqual(min, heap.RemoveMin()); 143 | } 144 | } 145 | 146 | [TestMethod] 147 | public void AssertThat_MutatingItemUp_IsCorrectedByHeapify() 148 | { 149 | var item = new Item(1); 150 | 151 | //Create a heap with a mutable item in it 152 | var heap = new MinHeap(); 153 | heap.Add(new[] { 154 | new Item(42), 155 | new Item(17), 156 | new Item(4), 157 | new Item(8), 158 | new Item(15), 159 | item, 160 | new Item(32), 161 | new Item(64), 162 | new Item(25), 163 | new Item(99), 164 | }); 165 | 166 | //Mutate the item, and then call heapify to fix things 167 | item.Value = 1000; 168 | heap.Heapify(); 169 | 170 | AssertHeapIsInOrder(heap); 171 | } 172 | 173 | [TestMethod] 174 | public void AssertThat_MutatingItemDown_IsCorrectedByHeapify() 175 | { 176 | var item = new Item(1000); 177 | 178 | //Create a heap with a mutable item in it 179 | var heap = new MinHeap(); 180 | heap.Add(new[] { 181 | new Item(42), 182 | new Item(17), 183 | new Item(4), 184 | new Item(8), 185 | new Item(15), 186 | item, 187 | new Item(32), 188 | new Item(64), 189 | new Item(25), 190 | new Item(99), 191 | }); 192 | 193 | //Mutate the item, and then call heapify to fix things 194 | item.Value = 1; 195 | heap.Heapify(); 196 | 197 | AssertHeapIsInOrder(heap); 198 | } 199 | 200 | [TestMethod] 201 | public void AssertThat_MutatingItemUp_IsCorrectedByHeapify_WithHint() 202 | { 203 | var item = new Item(1); 204 | 205 | //Create a heap with a mutable item in it 206 | var heap = new MinHeap(); 207 | heap.Add(new[] { 208 | new Item(42), 209 | new Item(17), 210 | new Item(4), 211 | new Item(8), 212 | new Item(15), 213 | item, 214 | new Item(32), 215 | new Item(64), 216 | new Item(25), 217 | new Item(99), 218 | }); 219 | 220 | //Mutate the item, and then call heapify to fix things 221 | var index = heap.IndexOf(item); 222 | item.Value = 1000; 223 | heap.Heapify(index); 224 | 225 | AssertHeapIsInOrder(heap); 226 | } 227 | 228 | [TestMethod] 229 | public void AssertThat_MutatingItemDown_IsCorrectedByHeapify_WithHint() 230 | { 231 | var item = new Item(1000); 232 | 233 | //Create a heap with a mutable item in it 234 | var heap = new MinHeap(); 235 | heap.Add(new[] { 236 | new Item(42), 237 | new Item(17), 238 | new Item(4), 239 | new Item(8), 240 | new Item(15), 241 | item, 242 | new Item(32), 243 | new Item(64), 244 | new Item(25), 245 | new Item(99), 246 | }); 247 | 248 | //Mutate the item, and then call heapify to fix things 249 | var index = heap.IndexOf(item); 250 | item.Value = 1; 251 | heap.Heapify(index); 252 | 253 | AssertHeapIsInOrder(heap); 254 | } 255 | 256 | [TestMethod] 257 | public void AssertThat_RemoveAt_UpdatesMinimum() 258 | { 259 | var heap = new MinHeap { 10, 14, 7, 11, 0 }; 260 | 261 | Assert.AreEqual(0, heap.Minimum); 262 | heap.RemoveAt(0); 263 | Assert.AreEqual(7, heap.Minimum); 264 | } 265 | 266 | [TestMethod] 267 | public void AssertThat_RemoveAt_MaintainsHeapOrder() 268 | { 269 | var heap = new MinHeap { 10, 14, 7, 11, 0 }; 270 | 271 | Assert.AreEqual(0, heap.Minimum); 272 | heap.RemoveAt(3); 273 | Assert.AreEqual(0, heap.Minimum); 274 | 275 | AssertHeapIsInOrder(heap); 276 | } 277 | 278 | [TestMethod] 279 | public void AssertThat_EnumeratingHeap_GetsAllItemsInHeap() 280 | { 281 | var heap = new MinHeap { 10, 14, 7, 11, 0 }; 282 | 283 | var items = heap.ToArray(); 284 | 285 | Assert.AreEqual(heap.Count, items.Length); 286 | Assert.IsTrue(items.Contains(10)); 287 | Assert.IsTrue(items.Contains(14)); 288 | Assert.IsTrue(items.Contains(7)); 289 | Assert.IsTrue(items.Contains(11)); 290 | Assert.IsTrue(items.Contains(0)); 291 | } 292 | 293 | private class Item 294 | : IComparable 295 | { 296 | public int Value; 297 | 298 | public Item(int value) 299 | { 300 | Value = value; 301 | } 302 | 303 | public int CompareTo(Item other) 304 | { 305 | return Value.CompareTo(other.Value); 306 | } 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /HandyCollectionsTest/Heap/MinMaxHeapTest.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using System.Collections.Generic; 3 | //using System.Linq; 4 | //using HandyCollections; 5 | //using HandyCollections.Extensions; 6 | //using HandyCollections.Heap; 7 | //using HandyCollections.RandomNumber; 8 | //using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | 10 | //namespace HandyCollectionsTest 11 | //{ 12 | // [TestClass] 13 | // public class MinMaxHeapTest 14 | // { 15 | // private MinMaxHeap_Accessor _intHeap; 16 | 17 | // [TestInitialize] 18 | // public void TestSetup() 19 | // { 20 | // _intHeap = new MinMaxHeap_Accessor(); 21 | // } 22 | 23 | // private List PopulateHeap(int seed, int count) 24 | // { 25 | // List values = new List(); 26 | 27 | // Random r = new Random(seed); 28 | // for (int i = 0; i < count; i++) 29 | // { 30 | // var v = r.Next(); 31 | // _intHeap.Add(v); 32 | // values.Add(v); 33 | // } 34 | 35 | // AssertHeap(_intHeap); 36 | 37 | // return values; 38 | // } 39 | 40 | // [TestMethod] 41 | // public void IsMinLevel() 42 | // { 43 | // int levelLength = 1; 44 | // bool minlevel = true; 45 | // int nextSwitch = 1; 46 | // for (int i = 0; i < 30000; i++) 47 | // { 48 | // if (i == nextSwitch) 49 | // { 50 | // minlevel = !minlevel; 51 | // levelLength *= 2; 52 | // nextSwitch += levelLength; 53 | // } 54 | 55 | // if (minlevel) 56 | // Assert.IsTrue(MinMaxHeap_Accessor.IsMinLevel(i)); 57 | // else 58 | // Assert.IsFalse(MinMaxHeap_Accessor.IsMinLevel(i)); 59 | // } 60 | // } 61 | 62 | // [TestMethod] 63 | // public void BulkRandomNumbersBuildValidHeap() 64 | // { 65 | // List numbers = new List(); 66 | // Random r = new Random(235246); 67 | // for (int i = 0; i < 1000; i++) 68 | // numbers.Add(r.Next(0, 1000)); 69 | 70 | // _intHeap.AddMany(numbers); 71 | 72 | // AssertHeap(_intHeap); 73 | // Assert.AreEqual(numbers.Max(), _intHeap.Max()); 74 | // Assert.AreEqual(numbers.Min(), _intHeap.Min()); 75 | // } 76 | 77 | // [TestMethod] 78 | // public void ManySingleRandomNumbersBuildValidHeap() 79 | // { 80 | // Random r = new Random(12423); 81 | 82 | // List numbers = new List(); 83 | // for (int i = 0; i < 10000; i++) 84 | // { 85 | // var v = r.Next(); 86 | // numbers.Add(v); 87 | // _intHeap.Add(v); 88 | 89 | // AssertHeap(_intHeap); 90 | // Assert.AreEqual(numbers.Max(), _intHeap.Max()); 91 | // Assert.AreEqual(numbers.Min(), _intHeap.Min()); 92 | // } 93 | 94 | // Assert.AreEqual(numbers.Count, _intHeap.Count); 95 | // } 96 | 97 | // [TestMethod] 98 | // public void IndexOfHeapItemIsCorrect() 99 | // { 100 | // List numbers = new List(); 101 | // Random r = new Random(235246); 102 | // for (int i = 0; i < 1000; i++) 103 | // numbers.Add(r.Next(0, 1000)); 104 | 105 | // _intHeap.AddMany(numbers); 106 | 107 | // for (int i = 0; i < _intHeap.Count; i++) 108 | // { 109 | // var index = _intHeap.IndexOf(_intHeap._heap[i]); 110 | // Assert.IsTrue(i >= index); 111 | // } 112 | // } 113 | 114 | // [TestMethod] 115 | // public void RemoveMaxSuccessfullyUpdatesMinAndMax() 116 | // { 117 | // var numbers = PopulateHeap(23521, 1000); 118 | // for (int i = 0; i < 100; i++) 119 | // { 120 | // Assert.AreEqual(numbers.Max(), _intHeap.Maximum); 121 | // Assert.AreEqual(numbers.Min(), _intHeap.Minimum); 122 | 123 | // numbers.Remove(_intHeap.Maximum); 124 | // _intHeap.RemoveMax(); 125 | // } 126 | // } 127 | 128 | // [TestMethod] 129 | // public void RemoveMinSuccessfullyUpdatesMinAndMax() 130 | // { 131 | // var numbers = PopulateHeap(3742, 1000); 132 | // for (int i = 0; i < 100; i++) 133 | // { 134 | // Assert.AreEqual(numbers.Max(), _intHeap.Maximum); 135 | // Assert.AreEqual(numbers.Min(), _intHeap.Minimum); 136 | 137 | // numbers.Remove(_intHeap.Minimum); 138 | // _intHeap.RemoveMin(); 139 | // } 140 | // } 141 | 142 | // [TestMethod] 143 | // public void MineMaxHeapUpdateKeySmaller() 144 | // { 145 | // MinMaxHeap_Accessor heap = new MinMaxHeap_Accessor(); 146 | 147 | // List items = new List(); 148 | // for (int i = 0; i < 1000; i++) 149 | // { 150 | // items.Add(new HeapItem(i)); 151 | // heap.Add(items.Last()); 152 | // } 153 | 154 | // int index = heap.IndexOf(items[645]); 155 | // items[645].Value = 14; 156 | // heap.Update(index); 157 | 158 | // HeapItem value = null; 159 | // while (heap.Count > 0) 160 | // { 161 | // var removed = heap.RemoveMax(); 162 | // if (value != null) 163 | // Assert.IsTrue(removed.Value <= value.Value); 164 | // value = removed; 165 | // } 166 | // } 167 | 168 | // private class HeapItem 169 | // :IComparable 170 | // { 171 | // public int Value; 172 | 173 | // public HeapItem(int v) 174 | // { 175 | // Value = v; 176 | // } 177 | 178 | // public int CompareTo(HeapItem other) 179 | // { 180 | // return Value.CompareTo(other.Value); 181 | // } 182 | 183 | // public override string ToString() 184 | // { 185 | // return Value.ToString(); 186 | // } 187 | // } 188 | 189 | // private void AssertHeap(MinMaxHeap_Accessor heap) 190 | // { 191 | // for (int i = 0; i < heap.Count; i++) 192 | // { 193 | // if (MinMaxHeap_Accessor.IsMinLevel(i)) 194 | // { 195 | // var errors = heap._heap.Skip(i).Where(item => !(item >= heap._heap[i])).ToArray(); 196 | // Assert.IsTrue(heap._heap.Skip(i).All(item => item >= heap._heap[i])); 197 | // } 198 | // else 199 | // { 200 | // var errors = heap._heap.Skip(i).Where(item => !(item <= heap._heap[i])).ToArray(); 201 | // Assert.IsTrue(heap._heap.Skip(i).All(item => item <= heap._heap[i])); 202 | // } 203 | // } 204 | // } 205 | // } 206 | //} 207 | -------------------------------------------------------------------------------- /HandyCollectionsTest/Heap/MinMaxMedianHeapTest.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using System.Collections.Generic; 3 | //using System.Linq; 4 | //using System.Text; 5 | //using HandyCollections.Extensions; 6 | //using HandyCollections.Heap; 7 | //using HandyCollections.RandomNumber; 8 | //using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | 10 | //namespace HandyCollectionsTest 11 | //{ 12 | // [TestClass] 13 | // public class MinMaxMedianHeapTest 14 | // { 15 | // private MinMaxMedianHeap_Accessor _intHeap; 16 | 17 | // [TestInitialize] 18 | // public void TestSetup() 19 | // { 20 | // _intHeap = new MinMaxMedianHeap_Accessor(); 21 | // } 22 | 23 | // private List PopulateHeap(int seed, int count) 24 | // { 25 | // List values = new List(); 26 | 27 | // Random r = new Random(seed); 28 | // for (int i = 0; i < count; i++) 29 | // { 30 | // var v = r.Next(); 31 | // _intHeap.Add(v); 32 | // values.Add(v); 33 | // } 34 | 35 | // return values; 36 | // } 37 | 38 | // [TestMethod] 39 | // public void BulkRandomNumbersBuildValidHeap() 40 | // { 41 | // List numbers = new List(); 42 | // Random r = new Random(235246); 43 | // for (int i = 0; i < 1000; i++) 44 | // numbers.Add(r.Next(0, 1000)); 45 | 46 | // _intHeap.AddMany(numbers); 47 | 48 | // Assert.AreEqual(numbers.Max(), _intHeap.Max()); 49 | // Assert.AreEqual(numbers.Min(), _intHeap.Min()); 50 | 51 | // numbers.Sort(); 52 | // Assert.AreEqual(numbers[numbers.Count / 2], _intHeap.HighMedian); 53 | // } 54 | 55 | // [TestMethod] 56 | // public void ManySingleRandomNumbersBuildValidHeap() 57 | // { 58 | // Random r = new Random(12423); 59 | 60 | // List numbers = new List(); 61 | // for (int i = 0; i < 10000; i++) 62 | // { 63 | // var v = r.Next(); 64 | // numbers.Add(v); 65 | // _intHeap.Add(v); 66 | 67 | // Assert.AreEqual(numbers.Max(), _intHeap.Max()); 68 | // Assert.AreEqual(numbers.Min(), _intHeap.Min()); 69 | 70 | // numbers.Sort(); 71 | // Assert.AreEqual(numbers[numbers.Count / 2], _intHeap.HighMedian); 72 | // } 73 | 74 | // Assert.AreEqual(numbers.Count, _intHeap.Count); 75 | // } 76 | 77 | 78 | // [TestMethod] 79 | // public void RemoveMaxSuccessfullyUpdatesMinAndMax() 80 | // { 81 | // var numbers = PopulateHeap(23521, 1000); 82 | // for (int i = 0; i < 100; i++) 83 | // { 84 | // Assert.AreEqual(numbers.Max(), _intHeap.Maximum); 85 | // Assert.AreEqual(numbers.Min(), _intHeap.Minimum); 86 | 87 | // numbers.Remove(_intHeap.Maximum); 88 | // _intHeap.RemoveMax(); 89 | // } 90 | // } 91 | 92 | // [TestMethod] 93 | // public void RemoveMinSuccessfullyUpdatesMinAndMax() 94 | // { 95 | // var numbers = PopulateHeap(3742, 1000); 96 | // for (int i = 0; i < 100; i++) 97 | // { 98 | // Assert.AreEqual(numbers.Max(), _intHeap.Maximum); 99 | // Assert.AreEqual(numbers.Min(), _intHeap.Minimum); 100 | 101 | // numbers.Remove(_intHeap.Minimum); 102 | // _intHeap.RemoveMin(); 103 | // } 104 | // } 105 | 106 | // [TestMethod] 107 | // public void MinMaxMedianHeap() 108 | // { 109 | // MinMaxMedianHeap testHeap = new MinMaxMedianHeap(); 110 | 111 | // testHeap.Add(1); 112 | // CheckValues(testHeap, 1, 1, 1); 113 | 114 | // testHeap.Add(2); 115 | // CheckValues(testHeap, 1, 1, 2); 116 | 117 | // testHeap.Add(3); 118 | // CheckValues(testHeap, 1, 2, 3); 119 | 120 | // testHeap.RemoveMax(); 121 | // CheckValues(testHeap, 1, 1, 2); 122 | 123 | // testHeap.Add(3); 124 | // CheckValues(testHeap, 1, 2, 3); 125 | 126 | // testHeap.RemoveMedian(); 127 | // CheckValues(testHeap, 1, 1, 3); 128 | 129 | // testHeap.Add(2); 130 | // CheckValues(testHeap, 1, 2, 3); 131 | 132 | // testHeap.RemoveMin(); 133 | // CheckValues(testHeap, 2, 2, 3); 134 | 135 | // testHeap.Add(1); 136 | // CheckValues(testHeap, 1, 2, 3); 137 | 138 | // testHeap.Add(4); 139 | // CheckValues(testHeap, 1, 2, 4); 140 | 141 | // testHeap.Add(5); 142 | // CheckValues(testHeap, 1, 3, 5); 143 | 144 | // testHeap.Add(6); 145 | // testHeap.Add(7); 146 | // CheckValues(testHeap, 1, 4, 7); 147 | 148 | // testHeap.Add(6); 149 | // testHeap.Add(7); 150 | // CheckValues(testHeap, 1, 5, 7); 151 | 152 | // for (int i = 0; i < 100; i++) 153 | // { 154 | // int[] values = new int[100]; 155 | // for (int v = 0; v < values.Length; v++) 156 | // { 157 | // values[v] = (int)StaticRandomNumber.Random(0, int.MaxValue); 158 | // } 159 | 160 | // int min = values.Min(); 161 | // int max = values.Max(); 162 | // int median = values[values.OrderSelect(values.Length / 2)]; 163 | 164 | // testHeap.Clear(); 165 | // testHeap.AddMany(values); 166 | // CheckValues(testHeap, min, median, max); 167 | // } 168 | // } 169 | 170 | // private static void CheckValues(MinMaxMedianHeap testHeap, int expectedMin, int expectedMedian, int expectedMax) 171 | // { 172 | // Assert.AreEqual(testHeap.Maximum, expectedMax); 173 | // Assert.AreEqual(testHeap.Minimum, expectedMin); 174 | // Assert.AreEqual(testHeap.Median, expectedMedian); 175 | // } 176 | // } 177 | //} 178 | -------------------------------------------------------------------------------- /HandyCollectionsTest/LinearFeedbackShiftRegisterTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using HandyCollections.RandomNumber; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace HandyCollectionsTest 9 | { 10 | [TestClass] 11 | public class LinearFeedbackShiftRegisterTest 12 | { 13 | [TestMethod] 14 | public static void LinearFeedbackShiftRegister16LoopsAfter2Pow16Values() 15 | { 16 | LinearFeedbackShiftRegister16 r = new LinearFeedbackShiftRegister16(); 17 | 18 | UInt16 first = r.NextRandom(); 19 | UInt16 value; 20 | int period = 0; 21 | 22 | do 23 | { 24 | value = r.NextRandom(); 25 | ++period; 26 | } while (value != first); 27 | 28 | Assert.AreEqual(LinearFeedbackShiftRegister16.Period, period); 29 | } 30 | 31 | [TestMethod] 32 | public void LinearFeedbackShiftRegister16EnumerableReturnsCorrectValues() 33 | { 34 | LinearFeedbackShiftRegister16 a = new LinearFeedbackShiftRegister16(123); 35 | LinearFeedbackShiftRegister16 b = new LinearFeedbackShiftRegister16(123); 36 | 37 | foreach (var value in b) 38 | Assert.AreEqual(a.NextRandom(), value); 39 | } 40 | 41 | [TestMethod] 42 | public static void LinearFeedbackShiftRegister32LoopsAfter2Pow32Values() 43 | { 44 | LinearFeedbackShiftRegister32 r = new LinearFeedbackShiftRegister32(3452); 45 | 46 | HashSet values = new HashSet(); 47 | 48 | for (int i = 0; i < UInt16.MaxValue * 5; i++) 49 | { 50 | Assert.IsTrue(values.Add(r.NextRandom())); 51 | } 52 | } 53 | 54 | [TestMethod] 55 | public void LinearFeedbackShiftRegister32EnumerableReturnsCorrectValues() 56 | { 57 | LinearFeedbackShiftRegister32 a = new LinearFeedbackShiftRegister32(123); 58 | LinearFeedbackShiftRegister32 b = new LinearFeedbackShiftRegister32(123); 59 | 60 | int i = 0; 61 | foreach (var value in b) 62 | { 63 | i++; 64 | if (i > UInt16.MaxValue * 5) 65 | break; 66 | 67 | Assert.AreEqual(a.NextRandom(), value); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /HandyCollectionsTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("HandyCollectionsTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("HandyCollectionsTest")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("2af35500-66b3-4f81-8baa-ca57b575d03a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /HandyCollectionsTest/RecentlyUsedQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace HandyCollectionsTest 8 | { 9 | [TestClass] 10 | public class RecentlyUsedQueue 11 | { 12 | [TestMethod] 13 | public void TestMethod1() 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /HandyCollectionsTest/RingBufferTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using HandyCollections; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace HandyCollectionsTest 7 | { 8 | [TestClass] 9 | public class RingBufferTest 10 | { 11 | [TestMethod] 12 | public void AssertThat_ConstructingRingBuffer_DoesNotThrow() 13 | { 14 | var r = new RingBuffer(5); 15 | 16 | Assert.AreEqual(0, r.Count); 17 | Assert.AreEqual(5, r.Capacity); 18 | } 19 | 20 | [TestMethod] 21 | [ExpectedException(typeof(ArgumentOutOfRangeException))] 22 | public void AssertThat_ConstructingWithNegativeCapacity_Throws() 23 | { 24 | // ReSharper disable once UnusedVariable 25 | var r = new RingBuffer(-5); 26 | } 27 | 28 | [TestMethod] 29 | public void AssertThat_AddingToRingBuffer_AddsItems() 30 | { 31 | var r = new RingBuffer(5) 32 | { 33 | 1, 34 | 2 35 | }; 36 | 37 | 38 | Assert.AreEqual(1, r[0]); 39 | Assert.AreEqual(2, r[1]); 40 | } 41 | 42 | [TestMethod] 43 | public void AssertThat_AddingArrayToRingBuffer_AddsItems() 44 | { 45 | var r = new RingBuffer(5) { new int[] {1, 2} }; 46 | 47 | 48 | Assert.AreEqual(1, r[0]); 49 | Assert.AreEqual(2, r[1]); 50 | } 51 | 52 | [TestMethod] 53 | public void AssertThat_AddingArrayToRingBuffer_Overwrites() 54 | { 55 | var r = new RingBuffer(5) { 56 | 57 | //Add so much data we completely overwrite the entire array 58 | new int[] { 59 | 1, 2, 3, 4, 5, 60 | 6, 7, 8, 9, 10, 61 | 11 62 | } 63 | }; 64 | 65 | Assert.AreEqual(7, r[0]); 66 | Assert.AreEqual(8, r[1]); 67 | } 68 | 69 | [TestMethod] 70 | public void AssertThat_AddingArrayToRingBuffer_Wraps() 71 | { 72 | var r = new RingBuffer(5) { 73 | 74 | //Add some initial data 75 | 1, 76 | 2, 77 | 3, 78 | 79 | //Add some data which will fall off the end 80 | new int[] {4, 5, 6, 7} 81 | }; 82 | 83 | Assert.AreEqual(3, r[0]); 84 | Assert.AreEqual(4, r[1]); 85 | Assert.AreEqual(5, r[2]); 86 | Assert.AreEqual(6, r[3]); 87 | Assert.AreEqual(7, r[4]); 88 | } 89 | 90 | [TestMethod] 91 | [ExpectedException(typeof(IndexOutOfRangeException))] 92 | public void AssertThat_IndexingAfterCount_Throws() 93 | { 94 | var r = new RingBuffer(5) 95 | { 96 | 1, 97 | 2 98 | }; 99 | 100 | 101 | // ReSharper disable once UnusedVariable 102 | var v = r[3]; 103 | } 104 | 105 | [TestMethod] 106 | public void AssertThat_CopyTo_CopiesCompleteBuffer_WhenBufferIsNotFull() 107 | { 108 | var r = new RingBuffer(5) { 1, 2, 3 }; 109 | 110 | var output = r.CopyTo(new ArraySegment(new int[10])); 111 | 112 | Assert.IsNotNull(output.Array); 113 | 114 | Assert.AreEqual(3, output.Count); 115 | 116 | Assert.AreEqual(1, output.Array[0]); 117 | Assert.AreEqual(2, output.Array[1]); 118 | Assert.AreEqual(3, output.Array[2]); 119 | } 120 | 121 | [TestMethod] 122 | public void AssertThat_CopyTo_CopiesCompleteBuffer_WhenBufferIsTorn() 123 | { 124 | //Write enough data to wrap around so the buffer is "torn" 125 | var r = new RingBuffer(5) { 1, 2, 3, 4, 5, 6 }; 126 | 127 | var output = r.CopyTo(new ArraySegment(new int[10])); 128 | 129 | Assert.IsNotNull(output.Array); 130 | 131 | Assert.AreEqual(5, output.Count); 132 | 133 | Assert.AreEqual(2, output.Array[0]); 134 | Assert.AreEqual(3, output.Array[1]); 135 | Assert.AreEqual(4, output.Array[2]); 136 | Assert.AreEqual(5, output.Array[3]); 137 | Assert.AreEqual(6, output.Array[4]); 138 | } 139 | 140 | [TestMethod] 141 | public void AssertThat_AddingMoreThanCapacity_RemovesOldestValue() 142 | { 143 | var r = new RingBuffer(5); 144 | var l = new List(); 145 | 146 | //We'll use the tail of a list to emulate a ringbuffer and check that the actual ringbuffer always contains the same values 147 | 148 | for (var i = 0; i < 1000; i++) 149 | { 150 | //Add to list - last 5 items in list should be items in ringbuffer 151 | l.Add(i); 152 | 153 | //Add to ring buffer 154 | r.Add(i); 155 | 156 | //Ensure two buffers are exactly the same 157 | for (int j = 0; j < r.Count; j++) 158 | { 159 | Assert.AreEqual(l[l.Count - r.Count + j], r[j]); 160 | } 161 | } 162 | } 163 | 164 | [TestMethod] 165 | public void AssertThat_Clear_ClearsAllData() 166 | { 167 | var b = new RingBuffer(4) { 1, 2, 3, 4, 5 }; 168 | 169 | Assert.AreEqual(4, b.Count); 170 | b.Clear(); 171 | Assert.AreEqual(0, b.Count); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /HandyCollectionsTest/Set/OrderedSetTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using HandyCollections.RandomNumber; 5 | using HandyCollections.Set; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace HandyCollectionsTest.Set 9 | { 10 | [TestClass] 11 | public class OrderedSetTest 12 | { 13 | private readonly OrderedSet _set = new OrderedSet(); 14 | 15 | [TestMethod] 16 | public void AssertThat_AfterAddingItems_SetContainsItem() 17 | { 18 | ((ICollection)_set).Add(1); 19 | 20 | Assert.IsTrue(_set.Contains(1)); 21 | } 22 | 23 | [TestMethod] 24 | public void AssertThat_AfterClear_SetDoesNotContainItems() 25 | { 26 | for (var i = 0; i < 100; i++) 27 | _set.Add(i); 28 | 29 | _set.Clear(); 30 | 31 | for (var i = 0; i < 100; i++) 32 | Assert.IsFalse(_set.Contains(i)); 33 | } 34 | 35 | [TestMethod] 36 | public void AssertThat_SetDoesNotContainNonAddedItems() 37 | { 38 | _set.Add(1); 39 | 40 | Assert.IsFalse(_set.Contains(2)); 41 | } 42 | 43 | [TestMethod] 44 | public void AssertThat_SetCount_IsNumberOfItemsAddedMinusRemoved() 45 | { 46 | for (var i = 0; i < 100; i++) 47 | { 48 | _set.Add(i); 49 | Assert.AreEqual(i + 1, _set.Count); 50 | } 51 | 52 | for (var i = 0; i < 50; i++) 53 | { 54 | Assert.IsTrue(_set.Remove(i)); 55 | Assert.AreEqual(100 - i - 1, _set.Count); 56 | } 57 | } 58 | 59 | [TestMethod] 60 | public void AssertThat_ItemCannotBeAddedTwice() 61 | { 62 | Assert.IsTrue(_set.Add(1)); 63 | Assert.IsFalse(_set.Add(1)); 64 | } 65 | 66 | [TestMethod] 67 | public void AssertThat_ExceptWith_RemovesSpecifiedItems() 68 | { 69 | for (var i = 0; i < 10; i++) 70 | _set.Add(i); 71 | 72 | _set.ExceptWith(new int[] { 1, 2, 3 }); 73 | 74 | Assert.IsFalse(_set.Contains(1)); 75 | Assert.IsFalse(_set.Contains(2)); 76 | Assert.IsFalse(_set.Contains(3)); 77 | 78 | Assert.IsTrue(_set.Contains(4)); 79 | Assert.IsTrue(_set.Contains(5)); 80 | Assert.IsTrue(_set.Contains(6)); 81 | Assert.IsTrue(_set.Contains(7)); 82 | Assert.IsTrue(_set.Contains(8)); 83 | Assert.IsTrue(_set.Contains(9)); 84 | } 85 | 86 | [TestMethod] 87 | public void AssertThat_IntersectWith_Intersects() 88 | { 89 | for (var i = 0; i < 10; i++) 90 | _set.Add(i); 91 | 92 | _set.IntersectWith(Enumerable.Range(7, 5)); 93 | 94 | for (var i = 0; i < 6; i++) 95 | Assert.IsFalse(_set.Contains(i)); 96 | for (var i = 7; i < 10; i++) 97 | Assert.IsTrue(_set.Contains(i)); 98 | for (var i = 10; i < 12; i++) 99 | Assert.IsFalse(_set.Contains(i)); 100 | } 101 | 102 | [TestMethod] 103 | public void AssertThat_Enumerable_IsOrderedByInsertionOrder() 104 | { 105 | var r = new LinearFeedbackShiftRegister32(1); 106 | for (var i = 0; i < 100; i++) 107 | _set.Add((int)r.NextRandom()); 108 | 109 | var r2 = new LinearFeedbackShiftRegister32(1); 110 | foreach (var item in _set) 111 | Assert.AreEqual((int)r2.NextRandom(), item); 112 | } 113 | 114 | [TestMethod] 115 | public void AssertThat_CopyTo_IsOrderedByInsertionOrder() 116 | { 117 | var r = new LinearFeedbackShiftRegister32(1); 118 | for (var i = 0; i < 100; i++) 119 | _set.Add((int)r.NextRandom()); 120 | 121 | var items = new int[100]; 122 | _set.CopyTo(items, 0); 123 | 124 | var r2 = new LinearFeedbackShiftRegister32(1); 125 | foreach (var item in items) 126 | Assert.AreEqual((int)r2.NextRandom(), item); 127 | } 128 | 129 | [TestMethod] 130 | public void AssertThat_IsNotReadonly() 131 | { 132 | Assert.IsFalse(_set.IsReadOnly); 133 | } 134 | 135 | [TestMethod] 136 | public void AssertThat_SetIsProperSubSet_OfProperSuperset() 137 | { 138 | for (var i = 0; i < 10; i++) 139 | _set.Add(i); 140 | 141 | Assert.IsTrue(_set.IsProperSubsetOf(Enumerable.Range(0, 100))); 142 | } 143 | 144 | [TestMethod] 145 | public void AssertThat_SetIsNotProperSubSet_OfSameSet() 146 | { 147 | for (var i = 0; i < 10; i++) 148 | _set.Add(i); 149 | 150 | Assert.IsFalse(_set.IsProperSubsetOf(_set)); 151 | } 152 | 153 | [TestMethod] 154 | public void AssertThat_SetIsNotProperSubSet_OfSubSet() 155 | { 156 | for (var i = 0; i < 10; i++) 157 | _set.Add(i); 158 | 159 | Assert.IsFalse(_set.IsProperSubsetOf(Enumerable.Range(0, 5))); 160 | } 161 | 162 | [TestMethod] 163 | public void AssertThat_SetIsSubSet_OfSuperset() 164 | { 165 | for (var i = 0; i < 10; i++) 166 | _set.Add(i); 167 | 168 | Assert.IsTrue(_set.IsSubsetOf(Enumerable.Range(0, 100))); 169 | } 170 | 171 | [TestMethod] 172 | public void AssertThat_SetIsSubSet_OfSameSet() 173 | { 174 | for (var i = 0; i < 10; i++) 175 | _set.Add(i); 176 | 177 | Assert.IsTrue(_set.IsSubsetOf(_set)); 178 | } 179 | 180 | [TestMethod] 181 | public void AssertThat_SetIsNotSubSet_OfSubSet() 182 | { 183 | for (var i = 0; i < 10; i++) 184 | _set.Add(i); 185 | 186 | Assert.IsFalse(_set.IsSubsetOf(Enumerable.Range(0, 5))); 187 | } 188 | 189 | [TestMethod] 190 | public void AssertThat_SetIsSuperSet_OfSubSet() 191 | { 192 | for (var i = 0; i < 10; i++) 193 | _set.Add(i); 194 | 195 | Assert.IsTrue(_set.IsSupersetOf(Enumerable.Range(0, 5))); 196 | } 197 | 198 | [TestMethod] 199 | public void AssertThat_SetIsSuperSet_OfSameSet() 200 | { 201 | for (var i = 0; i < 10; i++) 202 | _set.Add(i); 203 | 204 | Assert.IsTrue(_set.IsSupersetOf(_set)); 205 | } 206 | 207 | [TestMethod] 208 | public void AssertThat_SetIsNotSuperSet_OfSuperSet() 209 | { 210 | for (var i = 0; i < 10; i++) 211 | _set.Add(i); 212 | 213 | Assert.IsFalse(_set.IsSupersetOf(Enumerable.Range(0, 15))); 214 | } 215 | 216 | [TestMethod] 217 | public void AssertThat_SetIsProperSuperSet_OfSubSet() 218 | { 219 | for (var i = 0; i < 10; i++) 220 | _set.Add(i); 221 | 222 | Assert.IsTrue(_set.IsProperSupersetOf(Enumerable.Range(0, 5))); 223 | } 224 | 225 | [TestMethod] 226 | public void AssertThat_SetIsNotProperSuperSet_OfSameSet() 227 | { 228 | for (var i = 0; i < 10; i++) 229 | _set.Add(i); 230 | 231 | Assert.IsFalse(_set.IsProperSupersetOf(_set)); 232 | } 233 | 234 | [TestMethod] 235 | public void AssertThat_SetIsNotProperSuperSet_OfSuperSet() 236 | { 237 | for (var i = 0; i < 10; i++) 238 | _set.Add(i); 239 | 240 | Assert.IsFalse(_set.IsProperSupersetOf(Enumerable.Range(0, 15))); 241 | } 242 | 243 | [TestMethod] 244 | public void AssertThat_SetEquals_EquivalentSet() 245 | { 246 | for (var i = 0; i < 10; i++) 247 | _set.Add(i); 248 | 249 | Assert.IsTrue(_set.SetEquals(Enumerable.Range(0, 10))); 250 | } 251 | 252 | [TestMethod] 253 | public void AssertThat_NotSetEquals_NonEquivalentSet() 254 | { 255 | for (var i = 0; i < 10; i++) 256 | _set.Add(i); 257 | 258 | Assert.IsFalse(_set.SetEquals(Enumerable.Range(0, 11))); 259 | } 260 | 261 | [TestMethod] 262 | public void AssertThat_UnionWith_AddsAllItems() 263 | { 264 | _set.Add(1); 265 | 266 | _set.UnionWith(new[] { 267 | 2, 3, 4 268 | }); 269 | 270 | Assert.IsTrue(_set.Contains(1)); 271 | Assert.IsTrue(_set.Contains(2)); 272 | Assert.IsTrue(_set.Contains(3)); 273 | Assert.IsTrue(_set.Contains(4)); 274 | 275 | Assert.IsFalse(_set.Contains(5)); 276 | } 277 | 278 | [TestMethod] 279 | public void AssertThat_Overlaps_WithOverlappingArray() 280 | { 281 | _set.Add(1); 282 | _set.Add(2); 283 | _set.Add(3); 284 | 285 | Assert.IsTrue(_set.Overlaps(new int[] { 286 | 2 287 | })); 288 | } 289 | 290 | [TestMethod] 291 | public void AssertThat_DoesNotOverlap_WithNonOverlappingArray() 292 | { 293 | _set.Add(1); 294 | _set.Add(2); 295 | _set.Add(3); 296 | 297 | Assert.IsFalse(_set.Overlaps(new int[] { 298 | 4 299 | })); 300 | } 301 | 302 | [TestMethod] 303 | public void AssertThat_SymmetricExceptWith_RemovesItemsInBothSets() 304 | { 305 | _set.Add(1); 306 | _set.Add(2); 307 | 308 | _set.SymmetricExceptWith(new[] { 309 | 2, 3 310 | }); 311 | 312 | Assert.IsFalse(_set.Contains(2)); 313 | Assert.IsTrue(_set.Contains(1)); 314 | } 315 | 316 | [TestMethod] 317 | public void AssertThat_SymmetricExceptWith_AddsItemsInOtherSet() 318 | { 319 | _set.Add(1); 320 | _set.Add(2); 321 | 322 | _set.SymmetricExceptWith(new[] { 323 | 2, 3 324 | }); 325 | 326 | Assert.IsTrue(_set.Contains(3)); 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /HandyCollectionsTest/SplayTreeTest.cs: -------------------------------------------------------------------------------- 1 | using HandyCollections.BinaryTree; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace HandyCollectionsTest 5 | { 6 | [TestClass] 7 | public class SplayTreeTest 8 | { 9 | [TestMethod] 10 | public void Add() 11 | { 12 | SplayTree tree = new SplayTree(); 13 | 14 | var a = tree.Add(10, "a"); 15 | var b = tree.Add(5, "b"); 16 | 17 | Assert.IsTrue(b == tree.Root); 18 | Assert.IsTrue(b.Right == a); 19 | 20 | var c = tree.Add(7, "c"); 21 | 22 | Assert.IsTrue(c == tree.Root); 23 | Assert.IsTrue(c.Left == b); 24 | Assert.IsTrue(c.Right == a); 25 | 26 | var d = tree.Add(3, "d"); 27 | 28 | Assert.IsTrue(d == tree.Root); 29 | Assert.IsTrue(d.Left == null); 30 | Assert.IsTrue(d.Right == b); 31 | Assert.IsTrue(b.Left == null); 32 | Assert.IsTrue(b.Right == c); 33 | Assert.IsTrue(c.Right == a); 34 | Assert.IsTrue(c.Left == null); 35 | } 36 | 37 | [TestMethod] 38 | public void Find() 39 | { 40 | SplayTree tree = new SplayTree(); 41 | 42 | var a = tree.Add(5, "a"); 43 | var b = tree.Add(3, "b"); 44 | var c = tree.Add(1, "c"); 45 | var d = tree.Add(2, "d"); 46 | var e = tree.Add(10, "e"); 47 | 48 | var found = tree.Find(5); 49 | Assert.IsTrue(found == a); 50 | Assert.IsTrue(tree.Root == a); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /HandyCollectionsTest/TypedWeakReference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace HandyCollectionsTest 8 | { 9 | [TestClass] 10 | public class TypedWeakReference 11 | { 12 | [TestMethod] 13 | public void TestMethod1() 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /HandyCollectionsTest/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Local.testsettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | These are default test settings for a local test run. 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /TraceAndTestImpact.testsettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | These are test settings for Trace and Test Impact. 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright 2017 Martin Evans 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 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. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ### HandyCollections 2 | 3 | A convenient set of generic collections for C# 4 | 5 | This project is in active use by myself and I will respond rapidly to bug reports and pull requests. 6 | 7 | #### BinaryTree.BinaryTree 8 | A binary tree for quickly storing and retrieving items in O(Log N) time. Values are stored with a key, the same key can be used to retrieve the item (internally using a binary search on the key). 9 | 10 | #### BinaryTree.SplayTree 11 | A more advanced binary tree which automatically reorders itself to place recently accessed items nearer the root. If you expect your access pattern frequently use the same keys a splay tree should improve your performance (it's still O(Log N), but the constants for accessing near the root are smaller). 12 | 13 | #### BloomFilter.BloomFilter 14 | A set of items which can have items added and supports queries asking if an item has been added to the set. Queries are probabilistic - sometimes it will return false positives (but never false negatives). However, a bloom filter can be many times smaller and faster than a completely accurate set - see [wikipedia](http://en.wikipedia.org/wiki/Bloom_filter) for a more in depth explanation of bloom filters. 15 | 16 | #### BloomFilter.CountingBloomFilter 17 | A bloom filter which supports removing items. 18 | 19 | #### BloomFilter.ScalableBloomFilter 20 | A bloom filter which expands as more items are added to it, ensuring that the probability of a false positive never exceeds some threshold. 21 | 22 | #### Geometry.Octree 23 | An 8-way tree which represents a cuboid of 3 dimensional space. Items can be added with cuboid volume keys and the octree can then answer queries about which items overlap certain volumes. 24 | 25 | #### Geometry.Octree 26 | An 4-way tree which represents a rectangle of 2 dimensional space. Items can be added with rectangle area keys and the quadtree can then answer queries about which items overlap certain areas. 27 | 28 | #### Heap.MinHeap 29 | A collection of items which makes accessing the smallest item in the collection very fast, often used as a priority queue. Inserting and deleting items can be done in O(Log N) time, while finding the smallest is O(1). 30 | 31 | #### Heap.MinMaxHeap 32 | *Currently commented out due to some bugs and no known users!* 33 | 34 | A heap which makes accessing both min and max O(1). Insertion and deletion remain unchanged (except slightly higher constants). 35 | 36 | #### RandomNumber.LinearFeedbackShiftRegister16 37 | A random number generator which shuffles all 16 bit numbers and returns them in sequence. This guarantees no repeats until the sequence begins again (and then repeats itself perfectly). 38 | 39 | #### RandomNumber.LinearFeedbackShiftRegister32 40 | A random number generator which shuffles all 32 bit numbers and returns them in sequence. This guarantees no repeats until the sequence begins again (and then repeats itself perfectly). --------------------------------------------------------------------------------