├── .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