├── CONTRIBUTORS.md ├── IntervalTreeTests ├── ComparerTests.cs ├── MultipleComparerTests.cs ├── ReadmeExampleTests.cs ├── IntervalTreeTests.csproj ├── TreeOfIntTests.cs ├── IntervalTreeTests.cs ├── TreeOfDateTimeTests.cs └── TreeSpecs.cs ├── LICENSE.txt ├── IntervalTreeExamples ├── IntervalTreeExamples.csproj └── Program.cs ├── IntervalTree ├── IIntervalTree.cs ├── RangeValuePair.cs ├── IntervalTree.csproj ├── IntervalTree.cs └── IntervalTreeNode.cs ├── IntervalTree.sln ├── .gitattributes ├── README.md ├── .gitignore └── rangetree.ruleset /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Special thanks to the contributors 2 | 3 | - [Steve Hansen](https://github.com/beefo) 4 | - [Eric Domke](https://github.com/erdomke) 5 | - [Jonas Nyrup](https://github.com/jnyrup) -------------------------------------------------------------------------------- /IntervalTreeTests/ComparerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using IntervalTree; 4 | using NUnit.Framework; 5 | 6 | namespace IntervalTreeTests 7 | { 8 | [TestFixture] 9 | public class ComparerTests 10 | { 11 | [Test] 12 | public void AddingAnItem_FromIsLargerThanTo_ShouldThrowException() 13 | { 14 | var comparer = Comparer.Create((x, y) => x - y); 15 | var tree = new IntervalTree(comparer); 16 | 17 | Assert.That(() => tree.Add(2, 0, "FOO"), Throws.InstanceOf()); 18 | } 19 | 20 | [Test] 21 | public void CreatingTreeWithNullComparer_AddingAnItem_ShouldNotThrowException() 22 | { 23 | var tree = new IntervalTree(null); 24 | 25 | Assert.That(() => tree.Add(0, 1, "FOO"), Throws.Nothing); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | If not noted otherwise in the file header, the project uses the MIT license. 2 | 3 | Copyright (c) 2020, Matthias Buchetics and Alexander Pacha. 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /IntervalTreeExamples/IntervalTreeExamples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.6 5 | 6 | 7 | 8 | 9 | all 10 | runtime; build; native; contentfiles; analyzers; buildtransitive 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | $(SolutionDir)\rangetree.ruleset 20 | 21 | Exe 22 | 23 | 24 | 25 | 26 | bin\Release\netstandard1.3\RangeTree.Examples.xml 27 | 28 | 29 | 30 | bin\Debug\netstandard1.3\RangeTree.Examples.xml 31 | 32 | 33 | -------------------------------------------------------------------------------- /IntervalTreeTests/MultipleComparerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using IntervalTree; 5 | 6 | namespace IntervalTreeTests 7 | { 8 | [TestFixture] 9 | public class MultipleComparerTests 10 | { 11 | [Test] 12 | public void CreateTwoTrees_ProvideDifferentComparers_ExpectBothToHaveTheComparersFromConstruction() 13 | { 14 | var tree = new IntervalTree(StringComparer.Ordinal) 15 | { 16 | { "a", "e", "value1" }, 17 | { "B", "D", "value2" }, 18 | }; 19 | var results = tree.Query("c").ToArray(); 20 | Assert.That(results.Length, Is.EqualTo(1)); 21 | Assert.That(results[0], Is.EqualTo("value1")); 22 | 23 | tree = new IntervalTree(StringComparer.OrdinalIgnoreCase) 24 | { 25 | { "a", "e", "value1" }, 26 | { "B", "D", "value2" }, 27 | }; 28 | results = tree.Query("c").ToArray(); 29 | Assert.That(results.Length, Is.EqualTo(2)); 30 | Assert.That(results[0], Is.EqualTo("value1")); 31 | Assert.That(results[1], Is.EqualTo("value2")); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /IntervalTreeTests/ReadmeExampleTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using NUnit.Framework; 3 | using IntervalTree; 4 | 5 | namespace IntervalTreeTests 6 | { 7 | [TestFixture] 8 | public class ReadmeExampleTests 9 | { 10 | [Test] 11 | public void Query_CreateTreeAndExecuteQuery_ExpectCorrectElementsToBeReturned() 12 | { 13 | var tree = new IntervalTree() 14 | { 15 | { 0, 10, "1" }, 16 | { 20, 30, "2" }, 17 | { 15, 17, "3" }, 18 | { 25, 35, "4" }, 19 | }; 20 | 21 | var results1 = tree.Query(5).ToArray(); 22 | Assert.That(results1.Count, Is.EqualTo(1)); 23 | Assert.That(results1[0], Is.EqualTo("1")); 24 | 25 | var results2 = tree.Query(10).ToArray(); 26 | Assert.That(results2.Count, Is.EqualTo(1)); 27 | Assert.That(results2[0], Is.EqualTo("1")); 28 | 29 | var results3 = tree.Query(29).ToArray(); 30 | Assert.That(results3.Count, Is.EqualTo(2)); 31 | Assert.That(results3[0], Is.EqualTo("2")); 32 | Assert.That(results3[1], Is.EqualTo("4")); 33 | 34 | var results4 = tree.Query(5, 15).ToArray(); 35 | Assert.That(results4.Count, Is.EqualTo(2)); 36 | Assert.That(results4[0], Is.EqualTo("3")); 37 | Assert.That(results4[1], Is.EqualTo("1")); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /IntervalTreeTests/IntervalTreeTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | all 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | 31 | 32 | $(SolutionDir)\rangetree.ruleset 33 | 34 | 35 | 36 | bin\Release\netcoreapp1.1\.xml 37 | 3 38 | 39 | 40 | 41 | bin\Debug\netcoreapp1.1\.xml 42 | 3 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /IntervalTreeTests/TreeOfIntTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using NUnit.Framework; 3 | using IntervalTree; 4 | 5 | namespace IntervalTreeTests 6 | { 7 | [TestFixture] 8 | internal class TreeOfIntTests 9 | { 10 | [Test] 11 | public void BuildEmptyIntervalTree() 12 | { 13 | var emptyTree = new IntervalTree(); 14 | Assert.Pass(); 15 | } 16 | 17 | [Test] 18 | public void CreateEmptyIntervalTree() 19 | { 20 | var emptyTree = new IntervalTree(); 21 | Assert.That(emptyTree, Is.Not.Null); 22 | } 23 | 24 | [Test] 25 | public void TestSeparateIntervals() 26 | { 27 | var tree = new IntervalTree(); 28 | tree.Add(0, 10, 100); 29 | tree.Add(20, 30, 200); 30 | 31 | var result = tree.Query(5).ToList(); 32 | Assert.That(result.Count, Is.EqualTo(1)); 33 | Assert.That(result[0], Is.EqualTo(100)); 34 | } 35 | 36 | [Test] 37 | public void TwoIntersectingIntervals() 38 | { 39 | var tree = new IntervalTree(); 40 | tree.Add(0, 10, 100); 41 | tree.Add(3, 30, 200); 42 | 43 | var result = tree.Query(5).ToList(); 44 | Assert.That(result.Count, Is.EqualTo(2)); 45 | Assert.That(result[0], Is.EqualTo(100)); 46 | Assert.That(result[1], Is.EqualTo(200)); 47 | } 48 | 49 | [Test] 50 | public void QueryOutOfSyncTree_ExpectObsoleteResults() 51 | { 52 | var tree = new IntervalTree(); 53 | tree.Add(0, 10, 100); 54 | 55 | var result = tree.Query(5).ToList(); 56 | Assert.That(result.Count, Is.EqualTo(1)); 57 | 58 | tree.Add(3, 30, 200); 59 | 60 | result = tree.Query(5).ToList(); 61 | Assert.That(result.Count, Is.EqualTo(2)); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /IntervalTree/IIntervalTree.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IntervalTree 4 | { 5 | /// 6 | /// The standard interval tree implementation. Keeps a root node and forwards all queries to it. 7 | /// Whenever new items are added or items are removed, the tree goes temporarily "out of sync", which means that the 8 | /// internal index is not updated immediately, but upon the next query operation. 9 | /// 10 | /// The type of the range. 11 | /// The type of the data items. 12 | public interface IIntervalTree : IEnumerable> 13 | { 14 | /// 15 | /// Returns all items contained in the tree. 16 | /// 17 | IEnumerable Values { get; } 18 | 19 | /// 20 | /// Gets the number of elements contained in the tree. 21 | /// 22 | int Count { get; } 23 | 24 | /// 25 | /// Performs a point query with a single value. All items with overlapping ranges are returned. 26 | /// 27 | IEnumerable Query(TKey value); 28 | 29 | /// 30 | /// Performs a range query. All items with overlapping ranges are returned. 31 | /// 32 | IEnumerable Query(TKey from, TKey to); 33 | 34 | /// 35 | /// Adds the specified item. 36 | /// 37 | void Add(TKey from, TKey to, TValue value); 38 | 39 | /// 40 | /// Removes the specified item. 41 | /// 42 | void Remove(TValue item); 43 | 44 | /// 45 | /// Removes the specified items. 46 | /// 47 | void Remove(IEnumerable items); 48 | 49 | /// 50 | /// Removes all elements from the range tree. 51 | /// 52 | void Clear(); 53 | } 54 | } -------------------------------------------------------------------------------- /IntervalTree.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30011.22 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntervalTree", "IntervalTree\IntervalTree.csproj", "{A12CFD40-6EA9-459A-84AD-2DF944E332CE}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntervalTreeExamples", "IntervalTreeExamples\IntervalTreeExamples.csproj", "{A2ECC374-8BB1-4B8C-AF67-062595E6FBDB}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntervalTreeTests", "IntervalTreeTests\IntervalTreeTests.csproj", "{087BD1DE-623A-4C8B-A41B-E99938EC9296}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {A12CFD40-6EA9-459A-84AD-2DF944E332CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {A12CFD40-6EA9-459A-84AD-2DF944E332CE}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {A12CFD40-6EA9-459A-84AD-2DF944E332CE}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {A12CFD40-6EA9-459A-84AD-2DF944E332CE}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {A2ECC374-8BB1-4B8C-AF67-062595E6FBDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {A2ECC374-8BB1-4B8C-AF67-062595E6FBDB}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {A2ECC374-8BB1-4B8C-AF67-062595E6FBDB}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {A2ECC374-8BB1-4B8C-AF67-062595E6FBDB}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {087BD1DE-623A-4C8B-A41B-E99938EC9296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {087BD1DE-623A-4C8B-A41B-E99938EC9296}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {087BD1DE-623A-4C8B-A41B-E99938EC9296}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {087BD1DE-623A-4C8B-A41B-E99938EC9296}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {ECFD1131-4EBD-489E-81BE-550DBF8805EF} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /IntervalTreeExamples/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using IntervalTree; 6 | 7 | namespace IntervalTreeExamples 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | TreeExample1(); 14 | TreeExample2(); 15 | Console.WriteLine("Press any key to continue..."); 16 | Console.ReadKey(); 17 | } 18 | 19 | static void TreeExample1() 20 | { 21 | Console.WriteLine("Example 1"); 22 | 23 | var tree = new IntervalTree() 24 | { 25 | { 0, 10, "1" }, 26 | { 20, 30, "2" }, 27 | { 15, 17, "3" }, 28 | { 25, 35, "4" }, 29 | }; 30 | 31 | PrintQueryResult("query 1", tree.Query(5)); 32 | PrintQueryResult("query 2", tree.Query(10)); 33 | PrintQueryResult("query 3", tree.Query(29)); 34 | PrintQueryResult("query 4", tree.Query(5, 15)); 35 | 36 | Console.WriteLine(); 37 | } 38 | 39 | static void TreeExample2() 40 | { 41 | Console.WriteLine("Example 2"); 42 | 43 | var tree = new IntervalTree(); 44 | var stopwatch = new Stopwatch(); 45 | stopwatch.Start(); 46 | 47 | for (int i = 0; i < 100; i++) 48 | { 49 | for (int j = 0; j < 100; j++) 50 | { 51 | RandomTreeInsert(tree, 1000); 52 | } 53 | 54 | var resultCount = tree.Query(50, 60).Count(); 55 | Console.WriteLine("query: {0} results (tree count: {1})", resultCount, tree.Count); 56 | } 57 | 58 | stopwatch.Stop(); 59 | Console.WriteLine("elapsed time: {0}", stopwatch.Elapsed); 60 | } 61 | 62 | static Random random = new Random(); 63 | 64 | static void RandomTreeInsert(IIntervalTree tree, int limit) 65 | { 66 | var a = random.Next(limit); 67 | var b = random.Next(limit); 68 | 69 | tree.Add(Math.Min(a, b), Math.Max(a, b), "value"); 70 | } 71 | 72 | static void PrintQueryResult(string queryTitle, IEnumerable result) 73 | { 74 | Console.WriteLine(queryTitle); 75 | foreach (var item in result) 76 | { 77 | Console.WriteLine(item); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /IntervalTreeTests/IntervalTreeTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using IntervalTree; 3 | 4 | namespace IntervalTreeTests 5 | { 6 | [TestFixture] 7 | public class IntervalTreeTests 8 | { 9 | [Test] 10 | public void GettingMin_InnerItems() 11 | { 12 | var tree = new IntervalTree 13 | { 14 | { 1, 5, -1 }, 15 | { 2, 5, -1 }, 16 | { 3, 5, -1 }, 17 | }; 18 | 19 | var min = tree.Min; 20 | 21 | Assert.That(min, Is.EqualTo(1)); 22 | } 23 | 24 | [Test] 25 | public void GettingMin_LeftRecurse() 26 | { 27 | var tree = new IntervalTree 28 | { 29 | { 1, 2, -1 }, 30 | { 3, 4, -1 } 31 | }; 32 | 33 | var min = tree.Min; 34 | 35 | Assert.That(min, Is.EqualTo(1)); 36 | } 37 | 38 | [Test] 39 | public void GettingMax_InnerItems() 40 | { 41 | var tree = new IntervalTree 42 | { 43 | { 1, 2, -1 }, 44 | { 1, 3, -1 }, 45 | { 1, 4, -1 }, 46 | }; 47 | 48 | var max = tree.Max; 49 | 50 | Assert.That(max, Is.EqualTo(4)); 51 | } 52 | 53 | [Test] 54 | public void GettingMax_RightRecurse() 55 | { 56 | var tree = new IntervalTree 57 | { 58 | { 1, 2, -1 }, 59 | { 3, 4, -1 }, 60 | { 5, 6, -1 } 61 | }; 62 | 63 | var max = tree.Max; 64 | 65 | Assert.That(max, Is.EqualTo(6)); 66 | } 67 | 68 | [Test] 69 | public void GettingMin_Mixed() 70 | { 71 | var tree = new IntervalTree 72 | { 73 | { 2, 3, -1 }, 74 | { 8, 9, -1 }, 75 | { 1, 10, -1 }, 76 | }; 77 | 78 | var min = tree.Min; 79 | 80 | Assert.That(min, Is.EqualTo(1)); 81 | } 82 | 83 | [Test] 84 | public void GettingMax_Mixed() 85 | { 86 | var tree = new IntervalTree 87 | { 88 | { 1, 10, -1 }, 89 | { 2, 3, -1 }, 90 | { 4, 5, -1 }, 91 | { 8, 9, -1 }, 92 | }; 93 | 94 | var max = tree.Max; 95 | 96 | Assert.That(max, Is.EqualTo(10)); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /IntervalTree/RangeValuePair.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace IntervalTree 5 | { 6 | /// 7 | /// Represents a range of values. 8 | /// Both values must be of the same type and comparable. 9 | /// 10 | /// Type of the values. 11 | public readonly struct RangeValuePair : IEquatable> 12 | { 13 | public TKey From { get; } 14 | public TKey To { get; } 15 | public TValue Value { get; } 16 | 17 | /// 18 | /// Initializes a new instance. 19 | /// 20 | public RangeValuePair(TKey from, TKey to, TValue value) : this() 21 | { 22 | From = from; 23 | To = to; 24 | Value = value; 25 | } 26 | 27 | /// 28 | /// Returns a that represents this instance. 29 | /// 30 | /// 31 | /// A that represents this instance. 32 | /// 33 | public override string ToString() 34 | { 35 | return string.Format("[{0} - {1}] {2}", From, To, Value); 36 | } 37 | 38 | public override int GetHashCode() 39 | { 40 | var hash = 23; 41 | if (From != null) 42 | hash = hash * 37 + From.GetHashCode(); 43 | if (To != null) 44 | hash = hash * 37 + To.GetHashCode(); 45 | if (Value != null) 46 | hash = hash * 37 + Value.GetHashCode(); 47 | return hash; 48 | } 49 | 50 | public bool Equals(RangeValuePair other) 51 | { 52 | return EqualityComparer.Default.Equals(From, other.From) 53 | && EqualityComparer.Default.Equals(To, other.To) 54 | && EqualityComparer.Default.Equals(Value, other.Value); 55 | } 56 | 57 | public override bool Equals(object obj) 58 | { 59 | if (!(obj is RangeValuePair)) 60 | return false; 61 | 62 | return Equals((RangeValuePair)obj); 63 | } 64 | 65 | public static bool operator ==(RangeValuePair left, RangeValuePair right) 66 | { 67 | return left.Equals(right); 68 | } 69 | 70 | public static bool operator !=(RangeValuePair left, RangeValuePair right) 71 | { 72 | return !(left == right); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /IntervalTree/IntervalTree.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.2;netstandard2.0;net45 5 | 3.0.1 6 | 7 | 8 | 9 | IntervalTree 10 | A generic implementation of a centered interval tree in C#. 11 | 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. 12 | True 13 | False 14 | Copyright (c) 2020, Matthias Buchetics and Alexander Pacha 15 | 16 | https://github.com/mbuchetics/RangeTree 17 | https://github.com/mbuchetics/RangeTree.git 18 | git 19 | range, tree, interval 20 | 3.0.1 21 | RangeTree 22 | Matthias Buchetics, Alexander Pacha and others, see CONTRIBUTORS.md 23 | IntervalTree - A generic interval tree implementation in C# 24 | This version contains a bug-fix for elements that have overlapping intervals. 25 | Thanks to @nordic81. 26 | 27 | For a full list changes at https://github.com/mbuchetics/RangeTree/releases 28 | 3.0.1 29 | 3.0.1 30 | Matthias Buchetics, Alexander Pacha 31 | 32 | 33 | 34 | true 35 | 36 | 37 | 38 | $(SolutionDir)\rangetree.ruleset 39 | LICENSE.txt 40 | 41 | 42 | 43 | true 44 | 45 | 46 | 47 | 3 48 | 49 | 50 | 51 | 52 | True 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /IntervalTreeTests/TreeOfDateTimeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using IntervalTree; 5 | 6 | namespace IntervalTreeTests 7 | { 8 | [TestFixture] 9 | internal class TreeOfDateTimeTests 10 | { 11 | private static readonly DateTime ZERO = new DateTime(2001, 01, 01, 10, 00, 00); 12 | 13 | [Test] 14 | public void BuildEmptyIntervalTree() 15 | { 16 | var emptyTree = new IntervalTree(); 17 | Assert.Pass(); 18 | } 19 | 20 | [Test] 21 | public void CreateEmptyIntervalTree() 22 | { 23 | var emptyTree = new IntervalTree(); 24 | Assert.That(emptyTree, Is.Not.Null); 25 | } 26 | 27 | [Test] 28 | public void GetIntervalByExactEndTime() 29 | { 30 | var tree = new IntervalTree(); 31 | tree.Add(ZERO, ZERO.AddHours(1), 100); 32 | 33 | var result = tree.Query(ZERO.AddHours(1)).ToList(); 34 | Assert.That(result.Count, Is.EqualTo(1)); 35 | } 36 | 37 | [Test] 38 | public void GetIntervalByExactStartTime() 39 | { 40 | var tree = new IntervalTree(); 41 | tree.Add(ZERO, ZERO.AddHours(1), 100); 42 | 43 | var result = tree.Query(ZERO).ToList(); 44 | Assert.That(result.Count, Is.EqualTo(1)); 45 | } 46 | 47 | /// 48 | /// 0-----5-----10------15--------20 49 | /// |=====100====| 50 | /// |==200=| 51 | /// |====300==========| 52 | /// 53 | [Test] 54 | public void OverlapOnExactEndAndStart_AssertCount() 55 | { 56 | var tree = new IntervalTree(); 57 | tree.Add(ZERO, ZERO.AddHours(10), 100); 58 | tree.Add(ZERO.AddHours(10), ZERO.AddHours(15), 200); 59 | tree.Add(ZERO.AddHours(10), ZERO.AddHours(20), 200); 60 | 61 | var result = tree.Query(ZERO.AddHours(10)).ToList(); 62 | Assert.That(result.Count, Is.EqualTo(3)); 63 | } 64 | 65 | [Test] 66 | public void TestSeparateIntervals() 67 | { 68 | var tree = new IntervalTree(); 69 | tree.Add(ZERO, ZERO.AddHours(10), 100); 70 | tree.Add(ZERO.AddHours(20), ZERO.AddHours(30), 200); 71 | 72 | var result = tree.Query(ZERO.AddHours(5)).ToList(); 73 | Assert.That(result.Count, Is.EqualTo(1)); 74 | Assert.That(result[0], Is.EqualTo(100)); 75 | } 76 | 77 | [Test] 78 | public void TwoIntersectingIntervals() 79 | { 80 | var tree = new IntervalTree(); 81 | tree.Add(ZERO, ZERO.AddHours(10), 100); 82 | tree.Add(ZERO.AddHours(3), ZERO.AddHours(30), 200); 83 | 84 | var result = tree.Query(ZERO.AddHours(5)).ToList(); 85 | Assert.That(result.Count, Is.EqualTo(2)); 86 | Assert.That(result[0], Is.EqualTo(100)); 87 | Assert.That(result[1], Is.EqualTo(200)); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IntervalTree # 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/t8xvh5oquuvk17ks?svg=true)](https://ci.appveyor.com/project/apacha/rangetree) 4 | [![NuGet version](https://img.shields.io/nuget/v/RangeTree.svg?style=flat-square)](https://www.nuget.org/packages/RangeTree) 5 | 6 | ## A generic interval tree ## 7 | 8 | A generic implementation of a centered interval tree in C#. 9 | 10 | From [Wikipedia](http://en.wikipedia.org/wiki/Interval_tree): 11 | > 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. 12 | 13 | Based on the Java implementation found here: http://www.sanfoundry.com/java-program-implement-interval-tree/ 14 | 15 | 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. 16 | 17 | ### Requirements ### 18 | - Consuming this NuGet package requires .NET Framework >= 4.5 or .NET Standard >= 1.2 19 | - Developing this project requires Visual Studio 2017 with .NET Framework >= 4.5 and .NET Standard >= 2.0. 20 | 21 | ## Simple Interface ### 22 | 23 | ```csharp 24 | public interface IIntervalTree 25 | : IEnumerable> 26 | { 27 | IEnumerable Values { get; } 28 | int Count { get; } 29 | 30 | IEnumerable Query(TKey value); 31 | IEnumerable Query(TKey from, TKey to); 32 | 33 | void Add(TKey from, TKey to, TValue value); 34 | void Remove(TValue item); 35 | void Remove(IEnumerable items); 36 | void Clear(); 37 | } 38 | ``` 39 | 40 | ## Usage ### 41 | 42 | ```csharp 43 | var tree = new IntervalTree() 44 | { 45 | { 0, 10, "1" }, 46 | { 20, 30, "2" }, 47 | { 15, 17, "3" }, 48 | { 25, 35, "4" }, 49 | }; 50 | 51 | // Alternatively, use the Add method, for example: 52 | // tree.Add(0, 10, "1"); 53 | 54 | var results1 = tree.Query(5); // 1 item: [0 - 10] 55 | var results2 = tree.Query(10); // 1 item: [0 - 10] 56 | var results3 = tree.Query(29); // 2 items: [20 - 30], [25 - 35] 57 | var results4 = tree.Query(5, 15); // 2 items: [0 - 10], [15 - 17] 58 | ``` 59 | 60 | The solution file contains more examples and tests, that show how to use IntervalTree with other data types. 61 | 62 | ## Implementation Details ## 63 | 64 | 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. 65 | 66 | ## RangeTree vs. IntervalTree ## 67 | 68 | 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. -------------------------------------------------------------------------------- /IntervalTree/IntervalTree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace IntervalTree 7 | { 8 | public class IntervalTree : IIntervalTree 9 | { 10 | private IntervalTreeNode root; 11 | private List> items; 12 | private readonly IComparer comparer; 13 | private bool isInSync; 14 | 15 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 16 | 17 | public TKey Max 18 | { 19 | get 20 | { 21 | if (!isInSync) 22 | Rebuild(); 23 | 24 | return root.Max; 25 | } 26 | } 27 | 28 | public TKey Min 29 | { 30 | get 31 | { 32 | if (!isInSync) 33 | Rebuild(); 34 | 35 | return root.Min; 36 | } 37 | } 38 | 39 | public IEnumerable Values => items.Select(i => i.Value); 40 | 41 | public int Count => items.Count; 42 | 43 | /// 44 | /// Initializes an empty tree. 45 | /// 46 | public IntervalTree() : this(Comparer.Default) { } 47 | 48 | /// 49 | /// Initializes an empty tree. 50 | /// 51 | public IntervalTree(IComparer comparer) 52 | { 53 | this.comparer = comparer ?? Comparer.Default; 54 | isInSync = true; 55 | root = new IntervalTreeNode(this.comparer); 56 | items = new List>(); 57 | } 58 | 59 | public IEnumerable Query(TKey value) 60 | { 61 | if (!isInSync) 62 | Rebuild(); 63 | 64 | return root.Query(value); 65 | } 66 | 67 | public IEnumerable Query(TKey from, TKey to) 68 | { 69 | if (!isInSync) 70 | Rebuild(); 71 | 72 | return root.Query(from, to); 73 | } 74 | 75 | public void Add(TKey from, TKey to, TValue value) 76 | { 77 | if (comparer.Compare(from, to) > 0) 78 | throw new ArgumentOutOfRangeException($"{nameof(from)} cannot be larger than {nameof(to)}"); 79 | 80 | isInSync = false; 81 | items.Add(new RangeValuePair(from, to, value)); 82 | } 83 | 84 | public void Remove(TValue value) 85 | { 86 | isInSync = false; 87 | items = items.Where(l => !l.Value.Equals(value)).ToList(); 88 | } 89 | 90 | public void Remove(IEnumerable items) 91 | { 92 | isInSync = false; 93 | this.items = this.items.Where(l => !items.Contains(l.Value)).ToList(); 94 | } 95 | 96 | public void Clear() 97 | { 98 | root = new IntervalTreeNode(comparer); 99 | items = new List>(); 100 | isInSync = true; 101 | } 102 | 103 | public IEnumerator> GetEnumerator() 104 | { 105 | if (!isInSync) 106 | Rebuild(); 107 | 108 | return items.GetEnumerator(); 109 | } 110 | 111 | private void Rebuild() 112 | { 113 | if (isInSync) 114 | return; 115 | 116 | if (items.Count > 0) 117 | root = new IntervalTreeNode(items, comparer); 118 | else 119 | root = new IntervalTreeNode(comparer); 120 | isInSync = true; 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | !Source/SightPlayer.Omr.Test/x64/*.dll 14 | !Source/SightPlayer.Omr.Test/x64/*.exe 15 | !Source/SightPlayer.Omr.Test/x86/*.exe 16 | !Source/SightPlayer.Omr.Test/x86/*.dll 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Dd]ebugPublic/ 21 | [Rr]elease/ 22 | [Rr]eleases/ 23 | x64/ 24 | x86/ 25 | build/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | 30 | # Visual Studo 2015 cache/options directory 31 | .vs/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opensdf 79 | *.sdf 80 | *.cachefile 81 | 82 | # Visual Studio profiler 83 | *.psess 84 | *.vsp 85 | *.vspx 86 | 87 | # TFS 2012 Local Workspace 88 | $tf/ 89 | 90 | # Guidance Automation Toolkit 91 | *.gpState 92 | 93 | # ReSharper is a .NET coding add-in 94 | _ReSharper*/ 95 | *.[Rr]e[Ss]harper 96 | *.DotSettings.user 97 | 98 | # JustCode is a .NET coding addin-in 99 | .JustCode 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | _NCrunch_* 109 | .*crunch*.local.xml 110 | 111 | # MightyMoose 112 | *.mm.* 113 | AutoTest.Net/ 114 | 115 | # Web workbench (sass) 116 | .sass-cache/ 117 | 118 | # Installshield output folder 119 | [Ee]xpress/ 120 | 121 | # DocProject is a documentation generator add-in 122 | DocProject/buildhelp/ 123 | DocProject/Help/*.HxT 124 | DocProject/Help/*.HxC 125 | DocProject/Help/*.hhc 126 | DocProject/Help/*.hhk 127 | DocProject/Help/*.hhp 128 | DocProject/Help/Html2 129 | DocProject/Help/html 130 | 131 | # Click-Once directory 132 | publish/ 133 | 134 | # Publish Web Output 135 | *.[Pp]ublish.xml 136 | *.azurePubxml 137 | # TODO: Comment the next line if you want to checkin your web deploy settings 138 | # but database connection strings (with potential passwords) will be unencrypted 139 | *.pubxml 140 | *.publishproj 141 | 142 | # NuGet Packages 143 | *.nupkg 144 | # The packages folder can be ignored because of Package Restore 145 | **/packages/* 146 | # except build/, which is used as an MSBuild target. 147 | !**/packages/build/ 148 | # Uncomment if necessary however generally it will be regenerated when needed 149 | #!**/packages/repositories.config 150 | 151 | # Windows Azure Build Output 152 | csx/ 153 | *.build.csdef 154 | 155 | # Windows Store app package directory 156 | AppPackages/ 157 | 158 | # Others 159 | *Resource.Designer.cs 160 | *.[Cc]ache 161 | ClientBin/ 162 | [Ss]tyle[Cc]op.* 163 | ~$* 164 | *~ 165 | *.dbmdl 166 | *.dbproj.schemaview 167 | *.pfx 168 | *.publishsettings 169 | node_modules/ 170 | bower_components/ 171 | 172 | # RIA/Silverlight projects 173 | Generated_Code/ 174 | 175 | # Backup & report files from converting an old project file 176 | # to a newer Visual Studio version. Backup files are not needed, 177 | # because we have git ;-) 178 | _UpgradeReport_Files/ 179 | Backup*/ 180 | UpgradeLog*.XML 181 | UpgradeLog*.htm 182 | 183 | # SQL Server files 184 | *.mdf 185 | *.ldf 186 | 187 | # Business Intelligence projects 188 | *.rdl.data 189 | *.bim.layout 190 | *.bim_*.settings 191 | 192 | # Microsoft Fakes 193 | FakesAssemblies/ 194 | 195 | # Node.js Tools for Visual Studio 196 | .ntvs_analysis.dat 197 | 198 | # Visual Studio 6 build log 199 | *.plg 200 | 201 | # Visual Studio 6 workspace options file 202 | *.opt 203 | 204 | # Xamarin Components (will be restored like nuget packages) 205 | Components -------------------------------------------------------------------------------- /IntervalTreeTests/TreeSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using NUnit.Framework; 7 | using IntervalTree; 8 | 9 | namespace IntervalTreeTests 10 | { 11 | [TestFixture] 12 | public class If_the_user_searches_for_overlapping_entries_in_an_interval_tree : Spec 13 | { 14 | private static IEnumerable> TestEntries() 15 | { 16 | yield return Tuple.Create(1400, 1500); 17 | yield return Tuple.Create(0100, 0130); 18 | yield return Tuple.Create(1700, 1800); 19 | yield return Tuple.Create(0230, 0240); 20 | yield return Tuple.Create(0530, 0540); 21 | yield return Tuple.Create(2330, 2400); 22 | yield return Tuple.Create(0700, 0800); 23 | yield return Tuple.Create(0900, 1000); 24 | yield return Tuple.Create(0000, 0100); 25 | yield return Tuple.Create(0540, 0700); 26 | yield return Tuple.Create(1800, 2130); 27 | yield return Tuple.Create(2130, 2131); 28 | yield return Tuple.Create(0200, 0230); 29 | } 30 | 31 | private static IEnumerable TestCases 32 | { 33 | get 34 | { 35 | yield return new TestCaseData(Tuple.Create(2000, 2300)).Returns(2); 36 | yield return new TestCaseData(Tuple.Create(0000, 0100)).Returns(2); 37 | yield return new TestCaseData(Tuple.Create(0000, 0000)).Returns(1); 38 | yield return new TestCaseData(Tuple.Create(0100, 0100)).Returns(2); 39 | yield return new TestCaseData(Tuple.Create(1000, 1100)).Returns(1); 40 | yield return new TestCaseData(Tuple.Create(1030, 1400)).Returns(1); 41 | yield return new TestCaseData(Tuple.Create(0150, 0155)).Returns(0); 42 | yield return new TestCaseData(Tuple.Create(2132, 2133)).Returns(0); 43 | yield return new TestCaseData(Tuple.Create(1030, 1350)).Returns(0); 44 | yield return new TestCaseData(Tuple.Create(0000, 2359)).Returns(13); 45 | } 46 | } 47 | 48 | [Test] 49 | [TestCaseSource("TestCases")] 50 | public int CorrectQuery_BuiltInOrder(Tuple value) 51 | { 52 | var tree = CreateTree(TestEntries().OrderBy(interval => interval.Item1)); 53 | return tree 54 | .Query(value.Item1, value.Item2) 55 | .Count(); 56 | } 57 | 58 | [Test] 59 | [TestCaseSource("TestCases")] 60 | public int CorrectQuery_BuiltInReverseOrder(Tuple value) 61 | { 62 | var tree = CreateTree(TestEntries().OrderBy(interval => interval.Item1).Reverse()); 63 | return tree 64 | .Query(value.Item1, value.Item2) 65 | .Count(); 66 | } 67 | 68 | [Test] 69 | [TestCaseSource("TestCases")] 70 | public int CorrectQuery_BuiltRandomly(Tuple value) 71 | { 72 | var tree = CreateTree(TestEntries()); 73 | return tree 74 | .Query(value.Item1, value.Item2) 75 | .Count(); 76 | } 77 | 78 | private static IIntervalTree CreateTree(IEnumerable> entries) 79 | { 80 | var tree = new IntervalTree(); 81 | 82 | foreach (var interval in entries) 83 | { 84 | tree.Add(interval.Item1, interval.Item2, "value"); 85 | } 86 | 87 | return tree; 88 | } 89 | } 90 | 91 | /// 92 | /// Abstract helper class to make nunit tests more readable. 93 | /// 94 | [DebuggerStepThrough] 95 | [DebuggerNonUserCode] 96 | public class Spec 97 | { 98 | [DebuggerStepThrough] 99 | [OneTimeSetUp] 100 | public void SetUp() 101 | { 102 | EstablishContext(); 103 | BecauseOf(); 104 | } 105 | 106 | [DebuggerStepThrough] 107 | [OneTimeTearDown] 108 | public void TearDown() 109 | { 110 | Cleanup(); 111 | } 112 | 113 | /// 114 | /// Test setup. Place your initialization code here. 115 | /// 116 | [DebuggerStepThrough] 117 | protected virtual void EstablishContext() { } 118 | 119 | /// 120 | /// Test run. Place the tested method / action here. 121 | /// 122 | [DebuggerStepThrough] 123 | protected virtual void BecauseOf() { } 124 | 125 | /// 126 | /// Test clean. Close/delete files, close database connections .. 127 | /// 128 | [DebuggerStepThrough] 129 | protected virtual void Cleanup() { } 130 | 131 | /// 132 | /// Creates an Action delegate. 133 | /// 134 | /// Method the shall be created as delegate. 135 | /// A delegate of type 136 | protected Action Invoking(Action func) 137 | { 138 | return func; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /IntervalTree/IntervalTreeNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IntervalTree 4 | { 5 | /// 6 | /// A node of the range tree. Given a list of items, it builds 7 | /// its subtree. Also contains methods to query the subtree. 8 | /// Basically, all interval tree logic is here. 9 | /// 10 | internal class IntervalTreeNode : IComparer> 11 | { 12 | private readonly TKey center; 13 | 14 | private readonly IComparer comparer; 15 | private readonly RangeValuePair[] items; 16 | private readonly IntervalTreeNode leftNode; 17 | private readonly IntervalTreeNode rightNode; 18 | 19 | /// 20 | /// Initializes an empty node. 21 | /// 22 | /// The comparer used to compare two items. 23 | public IntervalTreeNode(IComparer comparer) 24 | { 25 | this.comparer = comparer ?? Comparer.Default; 26 | 27 | center = default; 28 | leftNode = null; 29 | rightNode = null; 30 | items = null; 31 | } 32 | 33 | /// 34 | /// Initializes a node with a list of items, builds the sub tree. 35 | /// 36 | /// The items that should be added to this node 37 | /// The comparer used to compare two items. 38 | public IntervalTreeNode(IList> items, IComparer comparer) 39 | { 40 | this.comparer = comparer ?? Comparer.Default; 41 | 42 | // first, find the median 43 | var endPoints = new List(items.Count * 2); 44 | foreach (var item in items) 45 | { 46 | endPoints.Add(item.From); 47 | endPoints.Add(item.To); 48 | } 49 | 50 | endPoints.Sort(this.comparer); 51 | 52 | // the median is used as center value 53 | if (endPoints.Count > 0) 54 | { 55 | Min = endPoints[0]; 56 | center = endPoints[endPoints.Count / 2]; 57 | Max = endPoints[endPoints.Count - 1]; 58 | } 59 | 60 | var inner = new List>(); 61 | var left = new List>(); 62 | var right = new List>(); 63 | 64 | // iterate over all items 65 | // if the range of an item is completely left of the center, add it to the left items 66 | // if it is on the right of the center, add it to the right items 67 | // otherwise (range overlaps the center), add the item to this node's items 68 | foreach (var o in items) 69 | if (this.comparer.Compare(o.To, center) < 0) 70 | left.Add(o); 71 | else if (this.comparer.Compare(o.From, center) > 0) 72 | right.Add(o); 73 | else 74 | inner.Add(o); 75 | 76 | // sort the items, this way the query is faster later on 77 | if (inner.Count > 0) 78 | { 79 | if (inner.Count > 1) 80 | inner.Sort(this); 81 | this.items = inner.ToArray(); 82 | } 83 | else 84 | { 85 | this.items = null; 86 | } 87 | 88 | // create left and right nodes, if there are any items 89 | if (left.Count > 0) 90 | leftNode = new IntervalTreeNode(left, this.comparer); 91 | if (right.Count > 0) 92 | rightNode = new IntervalTreeNode(right, this.comparer); 93 | } 94 | 95 | public TKey Max { get; } 96 | 97 | public TKey Min { get; } 98 | 99 | /// 100 | /// Returns less than 0 if this range's From is less than the other, greater than 0 if greater. 101 | /// If both are equal, the comparison of the To values is returned. 102 | /// 0 if both ranges are equal. 103 | /// 104 | /// The first item. 105 | /// The other item. 106 | /// 107 | int IComparer>.Compare(RangeValuePair x, 108 | RangeValuePair y) 109 | { 110 | var fromComp = comparer.Compare(x.From, y.From); 111 | if (fromComp == 0) 112 | return comparer.Compare(x.To, y.To); 113 | return fromComp; 114 | } 115 | 116 | /// 117 | /// Performs a point query with a single value. 118 | /// All items with overlapping ranges are returned. 119 | /// 120 | public IEnumerable Query(TKey value) 121 | { 122 | var results = new List(); 123 | 124 | // If the node has items, check for leaves containing the value. 125 | if (items != null) 126 | foreach (var o in items) 127 | if (comparer.Compare(o.From, value) > 0) 128 | break; 129 | else if (comparer.Compare(value, o.From) >= 0 && comparer.Compare(value, o.To) <= 0) 130 | results.Add(o.Value); 131 | 132 | // go to the left or go to the right of the tree, depending 133 | // where the query value lies compared to the center 134 | var centerComp = comparer.Compare(value, center); 135 | if (leftNode != null && centerComp < 0) 136 | results.AddRange(leftNode.Query(value)); 137 | else if (rightNode != null && centerComp > 0) 138 | results.AddRange(rightNode.Query(value)); 139 | 140 | return results; 141 | } 142 | 143 | /// 144 | /// Performs a range query. 145 | /// All items with overlapping ranges are returned. 146 | /// 147 | public IEnumerable Query(TKey from, TKey to) 148 | { 149 | var results = new List(); 150 | 151 | // If the node has items, check for leaves intersecting the range. 152 | if (items != null) 153 | foreach (var o in items) 154 | if (comparer.Compare(o.From, to) > 0) 155 | break; 156 | else if (comparer.Compare(to, o.From) >= 0 && comparer.Compare(from, o.To) <= 0) 157 | results.Add(o.Value); 158 | 159 | // go to the left or go to the right of the tree, depending 160 | // where the query value lies compared to the center 161 | if (leftNode != null && comparer.Compare(from, center) < 0) 162 | results.AddRange(leftNode.Query(from, to)); 163 | if (rightNode != null && comparer.Compare(to, center) > 0) 164 | results.AddRange(rightNode.Query(from, to)); 165 | 166 | return results; 167 | } 168 | } 169 | } -------------------------------------------------------------------------------- /rangetree.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | --------------------------------------------------------------------------------