Repository: mbuchetics/RangeTree Branch: master Commit: acdcae6050b7 Files: 22 Total size: 61.6 KB Directory structure: gitextract_7ydao9jy/ ├── .gitattributes ├── .gitignore ├── CONTRIBUTORS.md ├── IntervalTree/ │ ├── IIntervalTree.cs │ ├── IntervalTree.cs │ ├── IntervalTree.csproj │ ├── IntervalTreeNode.cs │ └── RangeValuePair.cs ├── IntervalTree.sln ├── IntervalTreeExamples/ │ ├── IntervalTreeExamples.csproj │ └── Program.cs ├── IntervalTreeTests/ │ ├── ComparerTests.cs │ ├── IntervalTreeTests.cs │ ├── IntervalTreeTests.csproj │ ├── MultipleComparerTests.cs │ ├── ReadmeExampleTests.cs │ ├── TreeOfDateTimeTests.cs │ ├── TreeOfIntTests.cs │ └── TreeSpecs.cs ├── LICENSE.txt ├── README.md └── rangetree.ruleset ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs !Source/SightPlayer.Omr.Test/x64/*.dll !Source/SightPlayer.Omr.Test/x64/*.exe !Source/SightPlayer.Omr.Test/x86/*.exe !Source/SightPlayer.Omr.Test/x86/*.dll # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studo 2015 cache/options directory .vs/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding addin-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Windows Azure Build Output csx/ *.build.csdef # Windows Store app package directory AppPackages/ # Others *Resource.Designer.cs *.[Cc]ache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Xamarin Components (will be restored like nuget packages) Components ================================================ FILE: CONTRIBUTORS.md ================================================ Special thanks to the contributors - [Steve Hansen](https://github.com/beefo) - [Eric Domke](https://github.com/erdomke) - [Jonas Nyrup](https://github.com/jnyrup) ================================================ FILE: IntervalTree/IIntervalTree.cs ================================================ using System.Collections.Generic; namespace IntervalTree { /// /// The standard interval tree implementation. Keeps a root node and forwards all queries to it. /// Whenever new items are added or items are removed, the tree goes temporarily "out of sync", which means that the /// internal index is not updated immediately, but upon the next query operation. /// /// The type of the range. /// The type of the data items. public interface IIntervalTree : IEnumerable> { /// /// Returns all items contained in the tree. /// IEnumerable Values { get; } /// /// Gets the number of elements contained in the tree. /// int Count { get; } /// /// Performs a point query with a single value. All items with overlapping ranges are returned. /// IEnumerable Query(TKey value); /// /// Performs a range query. All items with overlapping ranges are returned. /// IEnumerable Query(TKey from, TKey to); /// /// Adds the specified item. /// void Add(TKey from, TKey to, TValue value); /// /// Removes the specified item. /// void Remove(TValue item); /// /// Removes the specified items. /// void Remove(IEnumerable items); /// /// Removes all elements from the range tree. /// void Clear(); } } ================================================ FILE: IntervalTree/IntervalTree.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace IntervalTree { public class IntervalTree : IIntervalTree { private IntervalTreeNode root; private List> items; private readonly IComparer comparer; private bool isInSync; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public TKey Max { get { if (!isInSync) Rebuild(); return root.Max; } } public TKey Min { get { if (!isInSync) Rebuild(); return root.Min; } } public IEnumerable Values => items.Select(i => i.Value); public int Count => items.Count; /// /// Initializes an empty tree. /// public IntervalTree() : this(Comparer.Default) { } /// /// Initializes an empty tree. /// public IntervalTree(IComparer comparer) { this.comparer = comparer ?? Comparer.Default; isInSync = true; root = new IntervalTreeNode(this.comparer); items = new List>(); } public IEnumerable Query(TKey value) { if (!isInSync) Rebuild(); return root.Query(value); } public IEnumerable Query(TKey from, TKey to) { if (!isInSync) Rebuild(); return root.Query(from, to); } public void Add(TKey from, TKey to, TValue value) { if (comparer.Compare(from, to) > 0) throw new ArgumentOutOfRangeException($"{nameof(from)} cannot be larger than {nameof(to)}"); isInSync = false; items.Add(new RangeValuePair(from, to, value)); } public void Remove(TValue value) { isInSync = false; items = items.Where(l => !l.Value.Equals(value)).ToList(); } public void Remove(IEnumerable items) { isInSync = false; this.items = this.items.Where(l => !items.Contains(l.Value)).ToList(); } public void Clear() { root = new IntervalTreeNode(comparer); items = new List>(); isInSync = true; } public IEnumerator> GetEnumerator() { if (!isInSync) Rebuild(); return items.GetEnumerator(); } private void Rebuild() { if (isInSync) return; if (items.Count > 0) root = new IntervalTreeNode(items, comparer); else root = new IntervalTreeNode(comparer); isInSync = true; } } } ================================================ FILE: IntervalTree/IntervalTree.csproj ================================================  netstandard1.2;netstandard2.0;net45 3.0.1 IntervalTree A generic implementation of a centered interval tree in C#. In computer science, an interval tree is an ordered tree data structure to hold intervals. Specifically, it allows one to efficiently find all intervals that overlap with any given interval or point. It is often used for windowing queries, for instance, to find all roads on a computerized map inside a rectangular viewport, or to find all visible elements inside a three-dimensional scene. True False Copyright (c) 2020, Matthias Buchetics and Alexander Pacha https://github.com/mbuchetics/RangeTree https://github.com/mbuchetics/RangeTree.git git range, tree, interval 3.0.1 RangeTree Matthias Buchetics, Alexander Pacha and others, see CONTRIBUTORS.md IntervalTree - A generic interval tree implementation in C# This version contains a bug-fix for elements that have overlapping intervals. Thanks to @nordic81. For a full list changes at https://github.com/mbuchetics/RangeTree/releases 3.0.1 3.0.1 Matthias Buchetics, Alexander Pacha true $(SolutionDir)\rangetree.ruleset LICENSE.txt true 3 True ================================================ FILE: IntervalTree/IntervalTreeNode.cs ================================================ using System.Collections.Generic; namespace IntervalTree { /// /// A node of the range tree. Given a list of items, it builds /// its subtree. Also contains methods to query the subtree. /// Basically, all interval tree logic is here. /// internal class IntervalTreeNode : IComparer> { private readonly TKey center; private readonly IComparer comparer; private readonly RangeValuePair[] items; private readonly IntervalTreeNode leftNode; private readonly IntervalTreeNode rightNode; /// /// Initializes an empty node. /// /// The comparer used to compare two items. public IntervalTreeNode(IComparer comparer) { this.comparer = comparer ?? Comparer.Default; center = default; leftNode = null; rightNode = null; items = null; } /// /// Initializes a node with a list of items, builds the sub tree. /// /// The items that should be added to this node /// The comparer used to compare two items. public IntervalTreeNode(IList> items, IComparer comparer) { this.comparer = comparer ?? Comparer.Default; // first, find the median var endPoints = new List(items.Count * 2); foreach (var item in items) { endPoints.Add(item.From); endPoints.Add(item.To); } endPoints.Sort(this.comparer); // the median is used as center value if (endPoints.Count > 0) { Min = endPoints[0]; center = endPoints[endPoints.Count / 2]; Max = endPoints[endPoints.Count - 1]; } var inner = new List>(); var left = new List>(); var right = new List>(); // iterate over all items // if the range of an item is completely left of the center, add it to the left items // if it is on the right of the center, add it to the right items // otherwise (range overlaps the center), add the item to this node's items foreach (var o in items) if (this.comparer.Compare(o.To, center) < 0) left.Add(o); else if (this.comparer.Compare(o.From, center) > 0) right.Add(o); else inner.Add(o); // sort the items, this way the query is faster later on if (inner.Count > 0) { if (inner.Count > 1) inner.Sort(this); this.items = inner.ToArray(); } else { this.items = null; } // create left and right nodes, if there are any items if (left.Count > 0) leftNode = new IntervalTreeNode(left, this.comparer); if (right.Count > 0) rightNode = new IntervalTreeNode(right, this.comparer); } public TKey Max { get; } public TKey Min { get; } /// /// Returns less than 0 if this range's From is less than the other, greater than 0 if greater. /// If both are equal, the comparison of the To values is returned. /// 0 if both ranges are equal. /// /// The first item. /// The other item. /// int IComparer>.Compare(RangeValuePair x, RangeValuePair y) { var fromComp = comparer.Compare(x.From, y.From); if (fromComp == 0) return comparer.Compare(x.To, y.To); return fromComp; } /// /// Performs a point query with a single value. /// All items with overlapping ranges are returned. /// public IEnumerable Query(TKey value) { var results = new List(); // If the node has items, check for leaves containing the value. if (items != null) foreach (var o in items) if (comparer.Compare(o.From, value) > 0) break; else if (comparer.Compare(value, o.From) >= 0 && comparer.Compare(value, o.To) <= 0) results.Add(o.Value); // go to the left or go to the right of the tree, depending // where the query value lies compared to the center var centerComp = comparer.Compare(value, center); if (leftNode != null && centerComp < 0) results.AddRange(leftNode.Query(value)); else if (rightNode != null && centerComp > 0) results.AddRange(rightNode.Query(value)); return results; } /// /// Performs a range query. /// All items with overlapping ranges are returned. /// public IEnumerable Query(TKey from, TKey to) { var results = new List(); // If the node has items, check for leaves intersecting the range. if (items != null) foreach (var o in items) if (comparer.Compare(o.From, to) > 0) break; else if (comparer.Compare(to, o.From) >= 0 && comparer.Compare(from, o.To) <= 0) results.Add(o.Value); // go to the left or go to the right of the tree, depending // where the query value lies compared to the center if (leftNode != null && comparer.Compare(from, center) < 0) results.AddRange(leftNode.Query(from, to)); if (rightNode != null && comparer.Compare(to, center) > 0) results.AddRange(rightNode.Query(from, to)); return results; } } } ================================================ FILE: IntervalTree/RangeValuePair.cs ================================================ using System; using System.Collections.Generic; namespace IntervalTree { /// /// Represents a range of values. /// Both values must be of the same type and comparable. /// /// Type of the values. public readonly struct RangeValuePair : IEquatable> { public TKey From { get; } public TKey To { get; } public TValue Value { get; } /// /// Initializes a new instance. /// public RangeValuePair(TKey from, TKey to, TValue value) : this() { From = from; To = to; Value = value; } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() { return string.Format("[{0} - {1}] {2}", From, To, Value); } public override int GetHashCode() { var hash = 23; if (From != null) hash = hash * 37 + From.GetHashCode(); if (To != null) hash = hash * 37 + To.GetHashCode(); if (Value != null) hash = hash * 37 + Value.GetHashCode(); return hash; } public bool Equals(RangeValuePair other) { return EqualityComparer.Default.Equals(From, other.From) && EqualityComparer.Default.Equals(To, other.To) && EqualityComparer.Default.Equals(Value, other.Value); } public override bool Equals(object obj) { if (!(obj is RangeValuePair)) return false; return Equals((RangeValuePair)obj); } public static bool operator ==(RangeValuePair left, RangeValuePair right) { return left.Equals(right); } public static bool operator !=(RangeValuePair left, RangeValuePair right) { return !(left == right); } } } ================================================ FILE: IntervalTree.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30011.22 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntervalTree", "IntervalTree\IntervalTree.csproj", "{A12CFD40-6EA9-459A-84AD-2DF944E332CE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntervalTreeExamples", "IntervalTreeExamples\IntervalTreeExamples.csproj", "{A2ECC374-8BB1-4B8C-AF67-062595E6FBDB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntervalTreeTests", "IntervalTreeTests\IntervalTreeTests.csproj", "{087BD1DE-623A-4C8B-A41B-E99938EC9296}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {A12CFD40-6EA9-459A-84AD-2DF944E332CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A12CFD40-6EA9-459A-84AD-2DF944E332CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {A12CFD40-6EA9-459A-84AD-2DF944E332CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {A12CFD40-6EA9-459A-84AD-2DF944E332CE}.Release|Any CPU.Build.0 = Release|Any CPU {A2ECC374-8BB1-4B8C-AF67-062595E6FBDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A2ECC374-8BB1-4B8C-AF67-062595E6FBDB}.Debug|Any CPU.Build.0 = Debug|Any CPU {A2ECC374-8BB1-4B8C-AF67-062595E6FBDB}.Release|Any CPU.ActiveCfg = Release|Any CPU {A2ECC374-8BB1-4B8C-AF67-062595E6FBDB}.Release|Any CPU.Build.0 = Release|Any CPU {087BD1DE-623A-4C8B-A41B-E99938EC9296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {087BD1DE-623A-4C8B-A41B-E99938EC9296}.Debug|Any CPU.Build.0 = Debug|Any CPU {087BD1DE-623A-4C8B-A41B-E99938EC9296}.Release|Any CPU.ActiveCfg = Release|Any CPU {087BD1DE-623A-4C8B-A41B-E99938EC9296}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ECFD1131-4EBD-489E-81BE-550DBF8805EF} EndGlobalSection EndGlobal ================================================ FILE: IntervalTreeExamples/IntervalTreeExamples.csproj ================================================  netstandard1.6 all runtime; build; native; contentfiles; analyzers; buildtransitive $(SolutionDir)\rangetree.ruleset Exe bin\Release\netstandard1.3\RangeTree.Examples.xml bin\Debug\netstandard1.3\RangeTree.Examples.xml ================================================ FILE: IntervalTreeExamples/Program.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using IntervalTree; namespace IntervalTreeExamples { class Program { static void Main(string[] args) { TreeExample1(); TreeExample2(); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); } static void TreeExample1() { Console.WriteLine("Example 1"); var tree = new IntervalTree() { { 0, 10, "1" }, { 20, 30, "2" }, { 15, 17, "3" }, { 25, 35, "4" }, }; PrintQueryResult("query 1", tree.Query(5)); PrintQueryResult("query 2", tree.Query(10)); PrintQueryResult("query 3", tree.Query(29)); PrintQueryResult("query 4", tree.Query(5, 15)); Console.WriteLine(); } static void TreeExample2() { Console.WriteLine("Example 2"); var tree = new IntervalTree(); var stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < 100; i++) { for (int j = 0; j < 100; j++) { RandomTreeInsert(tree, 1000); } var resultCount = tree.Query(50, 60).Count(); Console.WriteLine("query: {0} results (tree count: {1})", resultCount, tree.Count); } stopwatch.Stop(); Console.WriteLine("elapsed time: {0}", stopwatch.Elapsed); } static Random random = new Random(); static void RandomTreeInsert(IIntervalTree tree, int limit) { var a = random.Next(limit); var b = random.Next(limit); tree.Add(Math.Min(a, b), Math.Max(a, b), "value"); } static void PrintQueryResult(string queryTitle, IEnumerable result) { Console.WriteLine(queryTitle); foreach (var item in result) { Console.WriteLine(item); } } } } ================================================ FILE: IntervalTreeTests/ComparerTests.cs ================================================ using System; using System.Collections.Generic; using IntervalTree; using NUnit.Framework; namespace IntervalTreeTests { [TestFixture] public class ComparerTests { [Test] public void AddingAnItem_FromIsLargerThanTo_ShouldThrowException() { var comparer = Comparer.Create((x, y) => x - y); var tree = new IntervalTree(comparer); Assert.That(() => tree.Add(2, 0, "FOO"), Throws.InstanceOf()); } [Test] public void CreatingTreeWithNullComparer_AddingAnItem_ShouldNotThrowException() { var tree = new IntervalTree(null); Assert.That(() => tree.Add(0, 1, "FOO"), Throws.Nothing); } } } ================================================ FILE: IntervalTreeTests/IntervalTreeTests.cs ================================================ using NUnit.Framework; using IntervalTree; namespace IntervalTreeTests { [TestFixture] public class IntervalTreeTests { [Test] public void GettingMin_InnerItems() { var tree = new IntervalTree { { 1, 5, -1 }, { 2, 5, -1 }, { 3, 5, -1 }, }; var min = tree.Min; Assert.That(min, Is.EqualTo(1)); } [Test] public void GettingMin_LeftRecurse() { var tree = new IntervalTree { { 1, 2, -1 }, { 3, 4, -1 } }; var min = tree.Min; Assert.That(min, Is.EqualTo(1)); } [Test] public void GettingMax_InnerItems() { var tree = new IntervalTree { { 1, 2, -1 }, { 1, 3, -1 }, { 1, 4, -1 }, }; var max = tree.Max; Assert.That(max, Is.EqualTo(4)); } [Test] public void GettingMax_RightRecurse() { var tree = new IntervalTree { { 1, 2, -1 }, { 3, 4, -1 }, { 5, 6, -1 } }; var max = tree.Max; Assert.That(max, Is.EqualTo(6)); } [Test] public void GettingMin_Mixed() { var tree = new IntervalTree { { 2, 3, -1 }, { 8, 9, -1 }, { 1, 10, -1 }, }; var min = tree.Min; Assert.That(min, Is.EqualTo(1)); } [Test] public void GettingMax_Mixed() { var tree = new IntervalTree { { 1, 10, -1 }, { 2, 3, -1 }, { 4, 5, -1 }, { 8, 9, -1 }, }; var max = tree.Max; Assert.That(max, Is.EqualTo(10)); } } } ================================================ FILE: IntervalTreeTests/IntervalTreeTests.csproj ================================================  netcoreapp2.1 all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive $(SolutionDir)\rangetree.ruleset bin\Release\netcoreapp1.1\.xml 3 bin\Debug\netcoreapp1.1\.xml 3 ================================================ FILE: IntervalTreeTests/MultipleComparerTests.cs ================================================ using System; using System.Linq; using NUnit.Framework; using IntervalTree; namespace IntervalTreeTests { [TestFixture] public class MultipleComparerTests { [Test] public void CreateTwoTrees_ProvideDifferentComparers_ExpectBothToHaveTheComparersFromConstruction() { var tree = new IntervalTree(StringComparer.Ordinal) { { "a", "e", "value1" }, { "B", "D", "value2" }, }; var results = tree.Query("c").ToArray(); Assert.That(results.Length, Is.EqualTo(1)); Assert.That(results[0], Is.EqualTo("value1")); tree = new IntervalTree(StringComparer.OrdinalIgnoreCase) { { "a", "e", "value1" }, { "B", "D", "value2" }, }; results = tree.Query("c").ToArray(); Assert.That(results.Length, Is.EqualTo(2)); Assert.That(results[0], Is.EqualTo("value1")); Assert.That(results[1], Is.EqualTo("value2")); } } } ================================================ FILE: IntervalTreeTests/ReadmeExampleTests.cs ================================================ using System.Linq; using NUnit.Framework; using IntervalTree; namespace IntervalTreeTests { [TestFixture] public class ReadmeExampleTests { [Test] public void Query_CreateTreeAndExecuteQuery_ExpectCorrectElementsToBeReturned() { var tree = new IntervalTree() { { 0, 10, "1" }, { 20, 30, "2" }, { 15, 17, "3" }, { 25, 35, "4" }, }; var results1 = tree.Query(5).ToArray(); Assert.That(results1.Count, Is.EqualTo(1)); Assert.That(results1[0], Is.EqualTo("1")); var results2 = tree.Query(10).ToArray(); Assert.That(results2.Count, Is.EqualTo(1)); Assert.That(results2[0], Is.EqualTo("1")); var results3 = tree.Query(29).ToArray(); Assert.That(results3.Count, Is.EqualTo(2)); Assert.That(results3[0], Is.EqualTo("2")); Assert.That(results3[1], Is.EqualTo("4")); var results4 = tree.Query(5, 15).ToArray(); Assert.That(results4.Count, Is.EqualTo(2)); Assert.That(results4[0], Is.EqualTo("3")); Assert.That(results4[1], Is.EqualTo("1")); } } } ================================================ FILE: IntervalTreeTests/TreeOfDateTimeTests.cs ================================================ using System; using System.Linq; using NUnit.Framework; using IntervalTree; namespace IntervalTreeTests { [TestFixture] internal class TreeOfDateTimeTests { private static readonly DateTime ZERO = new DateTime(2001, 01, 01, 10, 00, 00); [Test] public void BuildEmptyIntervalTree() { var emptyTree = new IntervalTree(); Assert.Pass(); } [Test] public void CreateEmptyIntervalTree() { var emptyTree = new IntervalTree(); Assert.That(emptyTree, Is.Not.Null); } [Test] public void GetIntervalByExactEndTime() { var tree = new IntervalTree(); tree.Add(ZERO, ZERO.AddHours(1), 100); var result = tree.Query(ZERO.AddHours(1)).ToList(); Assert.That(result.Count, Is.EqualTo(1)); } [Test] public void GetIntervalByExactStartTime() { var tree = new IntervalTree(); tree.Add(ZERO, ZERO.AddHours(1), 100); var result = tree.Query(ZERO).ToList(); Assert.That(result.Count, Is.EqualTo(1)); } /// /// 0-----5-----10------15--------20 /// |=====100====| /// |==200=| /// |====300==========| /// [Test] public void OverlapOnExactEndAndStart_AssertCount() { var tree = new IntervalTree(); tree.Add(ZERO, ZERO.AddHours(10), 100); tree.Add(ZERO.AddHours(10), ZERO.AddHours(15), 200); tree.Add(ZERO.AddHours(10), ZERO.AddHours(20), 200); var result = tree.Query(ZERO.AddHours(10)).ToList(); Assert.That(result.Count, Is.EqualTo(3)); } [Test] public void TestSeparateIntervals() { var tree = new IntervalTree(); tree.Add(ZERO, ZERO.AddHours(10), 100); tree.Add(ZERO.AddHours(20), ZERO.AddHours(30), 200); var result = tree.Query(ZERO.AddHours(5)).ToList(); Assert.That(result.Count, Is.EqualTo(1)); Assert.That(result[0], Is.EqualTo(100)); } [Test] public void TwoIntersectingIntervals() { var tree = new IntervalTree(); tree.Add(ZERO, ZERO.AddHours(10), 100); tree.Add(ZERO.AddHours(3), ZERO.AddHours(30), 200); var result = tree.Query(ZERO.AddHours(5)).ToList(); Assert.That(result.Count, Is.EqualTo(2)); Assert.That(result[0], Is.EqualTo(100)); Assert.That(result[1], Is.EqualTo(200)); } } } ================================================ FILE: IntervalTreeTests/TreeOfIntTests.cs ================================================ using System.Linq; using NUnit.Framework; using IntervalTree; namespace IntervalTreeTests { [TestFixture] internal class TreeOfIntTests { [Test] public void BuildEmptyIntervalTree() { var emptyTree = new IntervalTree(); Assert.Pass(); } [Test] public void CreateEmptyIntervalTree() { var emptyTree = new IntervalTree(); Assert.That(emptyTree, Is.Not.Null); } [Test] public void TestSeparateIntervals() { var tree = new IntervalTree(); tree.Add(0, 10, 100); tree.Add(20, 30, 200); var result = tree.Query(5).ToList(); Assert.That(result.Count, Is.EqualTo(1)); Assert.That(result[0], Is.EqualTo(100)); } [Test] public void TwoIntersectingIntervals() { var tree = new IntervalTree(); tree.Add(0, 10, 100); tree.Add(3, 30, 200); var result = tree.Query(5).ToList(); Assert.That(result.Count, Is.EqualTo(2)); Assert.That(result[0], Is.EqualTo(100)); Assert.That(result[1], Is.EqualTo(200)); } [Test] public void QueryOutOfSyncTree_ExpectObsoleteResults() { var tree = new IntervalTree(); tree.Add(0, 10, 100); var result = tree.Query(5).ToList(); Assert.That(result.Count, Is.EqualTo(1)); tree.Add(3, 30, 200); result = tree.Query(5).ToList(); Assert.That(result.Count, Is.EqualTo(2)); } } } ================================================ FILE: IntervalTreeTests/TreeSpecs.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using NUnit.Framework; using IntervalTree; namespace IntervalTreeTests { [TestFixture] public class If_the_user_searches_for_overlapping_entries_in_an_interval_tree : Spec { private static IEnumerable> TestEntries() { yield return Tuple.Create(1400, 1500); yield return Tuple.Create(0100, 0130); yield return Tuple.Create(1700, 1800); yield return Tuple.Create(0230, 0240); yield return Tuple.Create(0530, 0540); yield return Tuple.Create(2330, 2400); yield return Tuple.Create(0700, 0800); yield return Tuple.Create(0900, 1000); yield return Tuple.Create(0000, 0100); yield return Tuple.Create(0540, 0700); yield return Tuple.Create(1800, 2130); yield return Tuple.Create(2130, 2131); yield return Tuple.Create(0200, 0230); } private static IEnumerable TestCases { get { yield return new TestCaseData(Tuple.Create(2000, 2300)).Returns(2); yield return new TestCaseData(Tuple.Create(0000, 0100)).Returns(2); yield return new TestCaseData(Tuple.Create(0000, 0000)).Returns(1); yield return new TestCaseData(Tuple.Create(0100, 0100)).Returns(2); yield return new TestCaseData(Tuple.Create(1000, 1100)).Returns(1); yield return new TestCaseData(Tuple.Create(1030, 1400)).Returns(1); yield return new TestCaseData(Tuple.Create(0150, 0155)).Returns(0); yield return new TestCaseData(Tuple.Create(2132, 2133)).Returns(0); yield return new TestCaseData(Tuple.Create(1030, 1350)).Returns(0); yield return new TestCaseData(Tuple.Create(0000, 2359)).Returns(13); } } [Test] [TestCaseSource("TestCases")] public int CorrectQuery_BuiltInOrder(Tuple value) { var tree = CreateTree(TestEntries().OrderBy(interval => interval.Item1)); return tree .Query(value.Item1, value.Item2) .Count(); } [Test] [TestCaseSource("TestCases")] public int CorrectQuery_BuiltInReverseOrder(Tuple value) { var tree = CreateTree(TestEntries().OrderBy(interval => interval.Item1).Reverse()); return tree .Query(value.Item1, value.Item2) .Count(); } [Test] [TestCaseSource("TestCases")] public int CorrectQuery_BuiltRandomly(Tuple value) { var tree = CreateTree(TestEntries()); return tree .Query(value.Item1, value.Item2) .Count(); } private static IIntervalTree CreateTree(IEnumerable> entries) { var tree = new IntervalTree(); foreach (var interval in entries) { tree.Add(interval.Item1, interval.Item2, "value"); } return tree; } } /// /// Abstract helper class to make nunit tests more readable. /// [DebuggerStepThrough] [DebuggerNonUserCode] public class Spec { [DebuggerStepThrough] [OneTimeSetUp] public void SetUp() { EstablishContext(); BecauseOf(); } [DebuggerStepThrough] [OneTimeTearDown] public void TearDown() { Cleanup(); } /// /// Test setup. Place your initialization code here. /// [DebuggerStepThrough] protected virtual void EstablishContext() { } /// /// Test run. Place the tested method / action here. /// [DebuggerStepThrough] protected virtual void BecauseOf() { } /// /// Test clean. Close/delete files, close database connections .. /// [DebuggerStepThrough] protected virtual void Cleanup() { } /// /// Creates an Action delegate. /// /// Method the shall be created as delegate. /// A delegate of type protected Action Invoking(Action func) { return func; } } } ================================================ FILE: LICENSE.txt ================================================ If not noted otherwise in the file header, the project uses the MIT license. Copyright (c) 2020, Matthias Buchetics and Alexander Pacha. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # IntervalTree # [![Build status](https://ci.appveyor.com/api/projects/status/t8xvh5oquuvk17ks?svg=true)](https://ci.appveyor.com/project/apacha/rangetree) [![NuGet version](https://img.shields.io/nuget/v/RangeTree.svg?style=flat-square)](https://www.nuget.org/packages/RangeTree) ## A generic interval tree ## A generic implementation of a centered interval tree in C#. From [Wikipedia](http://en.wikipedia.org/wiki/Interval_tree): > In computer science, an interval tree is an ordered tree data structure to hold intervals. Specifically, it allows one to efficiently find all intervals that overlap with any given interval or point. It is often used for windowing queries, for instance, to find all roads on a computerized map inside a rectangular viewport, or to find all visible elements inside a three-dimensional scene. Based on the Java implementation found here: http://www.sanfoundry.com/java-program-implement-interval-tree/ Queries require `O(log n + m)` time, with `n` being the total number of intervals and `m` being the number of reported results. Construction requires `O(n log n)` time, and storage requires `O(n)` space. ### Requirements ### - Consuming this NuGet package requires .NET Framework >= 4.5 or .NET Standard >= 1.2 - Developing this project requires Visual Studio 2017 with .NET Framework >= 4.5 and .NET Standard >= 2.0. ## Simple Interface ### ```csharp public interface IIntervalTree : IEnumerable> { IEnumerable Values { get; } int Count { get; } IEnumerable Query(TKey value); IEnumerable Query(TKey from, TKey to); void Add(TKey from, TKey to, TValue value); void Remove(TValue item); void Remove(IEnumerable items); void Clear(); } ``` ## Usage ### ```csharp var tree = new IntervalTree() { { 0, 10, "1" }, { 20, 30, "2" }, { 15, 17, "3" }, { 25, 35, "4" }, }; // Alternatively, use the Add method, for example: // tree.Add(0, 10, "1"); var results1 = tree.Query(5); // 1 item: [0 - 10] var results2 = tree.Query(10); // 1 item: [0 - 10] var results3 = tree.Query(29); // 2 items: [20 - 30], [25 - 35] var results4 = tree.Query(5, 15); // 2 items: [0 - 10], [15 - 17] ``` The solution file contains more examples and tests, that show how to use IntervalTree with other data types. ## Implementation Details ## In this implementation, whenever you add or remove items from the tree, the tree goes "out of sync" internally, which means that the items are stored, but the tree-index is not updated yet. Upon the next query, the tree structure is automatically rebuild. Subsequent queries will use the cached index and be much faster. The creation of the tree-index requires `O(n log n)` time. Therefore, it is best suited for trees that do not change often or small trees, where the creation time is negligible. ## RangeTree vs. IntervalTree ## This project contains an IntervalTree (see [Issue #24](https://github.com/mbuchetics/RangeTree/issues/24)), but was incorrectly named RangeTree at the beginning. It was mostly renamed to IntervalTree in version 3.0.0. However, given that a large number of users are using this project, renaming the NuGet package and repository was not possible without breaking too much, so we settled with (just) renaming all occurences in the source code and documentation. ================================================ FILE: rangetree.ruleset ================================================