From f2a04e24c8db256f26747894d6ab8c9fbdd9d301 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Sun, 21 Apr 2024 23:52:31 +0300 Subject: [PATCH 1/7] Started working over an implementation of the segment tree data structure. Prepared the initial interface(s) structure and the implementation for the Qery method --- .../Interfaces/ISegmentTree.cs | 13 +++ .../Interfaces/ISegmentTreeEngine.cs | 11 +++ .../RecursiveSegmentTree.cs | 88 +++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTree.cs create mode 100644 TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeEngine.cs create mode 100644 TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs diff --git a/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTree.cs b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTree.cs new file mode 100644 index 0000000..6bfdc84 --- /dev/null +++ b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTree.cs @@ -0,0 +1,13 @@ +namespace TryAtSoftware.Extensions.Collections.Interfaces; + +public interface ISegmentTree +{ + TOutput Query(int index); + TOutput Query(int start, int end); + + void Update(int index, TChange change); + void Update(int start, int end, TChange change); + + void LazyUpdate(int index, TChange change); + void LazyUpdate(int start, int end, TChange change); +} \ No newline at end of file diff --git a/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeEngine.cs b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeEngine.cs new file mode 100644 index 0000000..02949a9 --- /dev/null +++ b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeEngine.cs @@ -0,0 +1,11 @@ +namespace TryAtSoftware.Extensions.Collections.Interfaces; + +public interface ISegmentTreeEngine +{ + TValue CreateDefaultValue(); + TChange Combine(TChange pendingChange, TChange newChange); + TValue ApplyChange(TValue currentValue, TChange change); + + TOutput Merge(TOutput left, TOutput right); + TOutput ProduceResult(TValue value); +} \ No newline at end of file diff --git a/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs b/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs new file mode 100644 index 0000000..6b7ae7d --- /dev/null +++ b/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs @@ -0,0 +1,88 @@ +namespace TryAtSoftware.Extensions.Collections; + +using System; +using TryAtSoftware.Extensions.Collections.Interfaces; + +public class RecursiveSegmentTree : ISegmentTree +{ + private readonly RecursiveSegmentTreeNode _root; + private readonly int _n; + + public RecursiveSegmentTree(ISegmentTreeEngine engine, int n) + { + if (engine is null) throw new ArgumentNullException(nameof(engine)); + if (n < 0) throw new ArgumentException("The count of base elements for the segment tree must not be negative."); + + this._root = new RecursiveSegmentTreeNode(engine, 0, n - 1); + this._n = n; + } + + public TOutput Query(int index) => this.Query(index, index); + + public TOutput Query(int start, int end) + { + if (start < 0) throw new ArgumentException("The query start index must not be negative.", nameof(start)); + if (end >= this._n) throw new ArgumentException("The query end index must not exceed the original bounds.", nameof(end)); + if (start > end) throw new InvalidOperationException("The query start index must be less than or equal to the query end index"); + + return this._root.Query(start, end); + } + + public void Update(int index, TChange change) => this.Update(index, index, change); + + public void Update(int start, int end, TChange change) => throw new System.NotImplementedException(); + + public void LazyUpdate(int index, TChange change) => this.LazyUpdate(index, index, change); + + public void LazyUpdate(int start, int end, TChange change) => throw new System.NotImplementedException(); + + private class Node + { + } +} + +internal class RecursiveSegmentTreeNode +{ + private readonly ISegmentTreeEngine _engine; + private readonly int _start, _end, _m1, _m2; + private readonly bool _isLeaf; + + private RecursiveSegmentTreeNode? _left, _right; + private TValue? _value; + + private RecursiveSegmentTreeNode Left => this._left ??= new RecursiveSegmentTreeNode(this._engine, this._start, this._m1); + private RecursiveSegmentTreeNode Right => this._right ??= new RecursiveSegmentTreeNode(this._engine, this._m2, this._end); + private TValue Value => this._value!; + + public RecursiveSegmentTreeNode(ISegmentTreeEngine engine, int start, int end) + { + this._engine = engine; + this._start = start; + this._end = end; + this._isLeaf = start == end; + + if (end - start < 2) + { + this._m1 = start; + this._m2 = end; + } + else + { + this._m1 = start + (end - start) / 2; + this._m2 = this._m1 + 1; + } + + if (this._isLeaf) this._value = engine.CreateDefaultValue(); + } + + // It is guaranteed that `this._start <= queryStart <= queryEnd <= this._end`. + public TOutput Query(int queryStart, int queryEnd) + { + if (this._isLeaf) return this._engine.ProduceResult(this.Value); + + if (queryEnd <= this._m1) return this.Left.Query(queryStart, queryEnd); + if (queryStart >= this._m2) return this.Right.Query(queryStart, queryEnd); + + return this._engine.Merge(this.Left.Query(queryStart, this._m1), this.Right.Query(this._m2, queryEnd)); + } +} \ No newline at end of file From d6091c30a02d32491ee6479b28e64a2d93a91dad Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Thu, 2 May 2024 21:27:53 +0300 Subject: [PATCH 2/7] Implemented Update functionalities --- .../RecursiveSegmentTree.cs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs b/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs index 6b7ae7d..8123ee2 100644 --- a/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs +++ b/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs @@ -30,7 +30,14 @@ public TOutput Query(int start, int end) public void Update(int index, TChange change) => this.Update(index, index, change); - public void Update(int start, int end, TChange change) => throw new System.NotImplementedException(); + public void Update(int start, int end, TChange change) + { + if (start < 0) throw new ArgumentException("The query start index must not be negative.", nameof(start)); + if (end >= this._n) throw new ArgumentException("The query end index must not exceed the original bounds.", nameof(end)); + if (start > end) throw new InvalidOperationException("The query start index must be less than or equal to the query end index"); + + this._root.Update(start, end, change); + } public void LazyUpdate(int index, TChange change) => this.LazyUpdate(index, index, change); @@ -75,7 +82,7 @@ public RecursiveSegmentTreeNode(ISegmentTreeEngine eng if (this._isLeaf) this._value = engine.CreateDefaultValue(); } - // It is guaranteed that `this._start <= queryStart <= queryEnd <= this._end`. + // For all the methods below is guaranteed that `this._start <= queryStart <= queryEnd <= this._end`. public TOutput Query(int queryStart, int queryEnd) { if (this._isLeaf) return this._engine.ProduceResult(this.Value); @@ -85,4 +92,14 @@ public TOutput Query(int queryStart, int queryEnd) return this._engine.Merge(this.Left.Query(queryStart, this._m1), this.Right.Query(this._m2, queryEnd)); } + + public void Update(int queryStart, int queryEnd, TChange change) + { + if (this._isLeaf) this._value = this._engine.ApplyChange(this.Value, change); + else + { + if (queryStart <= this._m1) this.Left.Update(queryStart, Math.Min(this._m1, queryEnd), change); + if (queryEnd >= this._m2) this.Right.Update(Math.Max(this._m2, queryStart), queryEnd, change); + } + } } \ No newline at end of file From 3542cf13b77a7ed2ab0b93c8cc95c14e94ed22b9 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Thu, 2 May 2024 21:38:49 +0300 Subject: [PATCH 3/7] Implemented a standard sum engine and wrote simple tests --- .../SegmentTreeTests.cs | 35 +++++++++++++++++++ .../StandardSegmentTreeSumEngine.cs | 22 ++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs create mode 100644 TryAtSoftware.Extensions.Collections/StandardSegmentTreeSumEngine.cs diff --git a/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs b/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs new file mode 100644 index 0000000..04c6d96 --- /dev/null +++ b/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs @@ -0,0 +1,35 @@ +namespace TryAtSoftware.Extensions.Collections.Tests; + +using TryAtSoftware.Randomizer.Core.Helpers; +using Xunit; + +#if NET7_0_OR_GREATER + +public static class SegmentTreeTests +{ + [Fact] + public static void SegmentTreeShouldBeUpdatedSuccessfully() + { + var n = RandomizationHelper.RandomInteger(0, 100); + + var engine = new StandardSegmentTreeSumEngine(); + var segmentTree = new RecursiveSegmentTree(engine, n); + + var numbers = new int[n]; + for (var i = 0; i < n; i++) + { + numbers[i] = RandomizationHelper.RandomInteger(0, 100); + segmentTree.Update(i, numbers[i]); + } + + for (var i = 0; i < n; i++) Assert.Equal(numbers[i], segmentTree.Query(i)); + + var prefixSum = new int[n + 1]; + for (var i = 0; i < n; i++) prefixSum[i + 1] = prefixSum[i] + numbers[i]; + + for (var i = 0; i < n; i++) + for (var j = i; j < n; j++) Assert.Equal(prefixSum[j + 1] - prefixSum[i], segmentTree.Query(i, j)); + } +} + +#endif \ No newline at end of file diff --git a/TryAtSoftware.Extensions.Collections/StandardSegmentTreeSumEngine.cs b/TryAtSoftware.Extensions.Collections/StandardSegmentTreeSumEngine.cs new file mode 100644 index 0000000..23db137 --- /dev/null +++ b/TryAtSoftware.Extensions.Collections/StandardSegmentTreeSumEngine.cs @@ -0,0 +1,22 @@ +namespace TryAtSoftware.Extensions.Collections; + +#if NET7_0_OR_GREATER + +using System.Numerics; +using TryAtSoftware.Extensions.Collections.Interfaces; + +public class StandardSegmentTreeSumEngine : ISegmentTreeEngine + where T : struct, INumber +{ + public T CreateDefaultValue() => default; + + public T Combine(T pendingChange, T newChange) => newChange; + + public T ApplyChange(T currentValue, T change) => change; + + public T Merge(T left, T right) => left + right; + + public T ProduceResult(T value) => value; +} + +#endif \ No newline at end of file From e7db3883b632d82de1e46121c46129e73846ce3b Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Thu, 2 May 2024 22:21:25 +0300 Subject: [PATCH 4/7] Implemented lazy update --- .../SegmentTreeTests.cs | 16 ++++- .../RecursiveSegmentTree.cs | 58 +++++++++++++++---- .../StandardSegmentTreeSumEngine.cs | 22 ------- 3 files changed, 63 insertions(+), 33 deletions(-) delete mode 100644 TryAtSoftware.Extensions.Collections/StandardSegmentTreeSumEngine.cs diff --git a/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs b/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs index 04c6d96..b6db194 100644 --- a/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs +++ b/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs @@ -1,5 +1,6 @@ namespace TryAtSoftware.Extensions.Collections.Tests; +using TryAtSoftware.Extensions.Collections.Interfaces; using TryAtSoftware.Randomizer.Core.Helpers; using Xunit; @@ -12,7 +13,7 @@ public static void SegmentTreeShouldBeUpdatedSuccessfully() { var n = RandomizationHelper.RandomInteger(0, 100); - var engine = new StandardSegmentTreeSumEngine(); + var engine = new StandardSegmentTreeSumEngine(); var segmentTree = new RecursiveSegmentTree(engine, n); var numbers = new int[n]; @@ -32,4 +33,17 @@ public static void SegmentTreeShouldBeUpdatedSuccessfully() } } +public class StandardSegmentTreeSumEngine : ISegmentTreeEngine +{ + public int CreateDefaultValue() => default; + + public int Combine(int pendingChange, int newChange) => newChange; + + public int ApplyChange(int currentValue, int change) => change; + + public int Merge(int left, int right) => left + right; + + public int ProduceResult(int value) => value; +} + #endif \ No newline at end of file diff --git a/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs b/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs index 8123ee2..9d8991e 100644 --- a/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs +++ b/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs @@ -21,10 +21,7 @@ public RecursiveSegmentTree(ISegmentTreeEngine engine, public TOutput Query(int start, int end) { - if (start < 0) throw new ArgumentException("The query start index must not be negative.", nameof(start)); - if (end >= this._n) throw new ArgumentException("The query end index must not exceed the original bounds.", nameof(end)); - if (start > end) throw new InvalidOperationException("The query start index must be less than or equal to the query end index"); - + this.ValidateBounds(start, end); return this._root.Query(start, end); } @@ -32,19 +29,23 @@ public TOutput Query(int start, int end) public void Update(int start, int end, TChange change) { - if (start < 0) throw new ArgumentException("The query start index must not be negative.", nameof(start)); - if (end >= this._n) throw new ArgumentException("The query end index must not exceed the original bounds.", nameof(end)); - if (start > end) throw new InvalidOperationException("The query start index must be less than or equal to the query end index"); - + this.ValidateBounds(start, end); this._root.Update(start, end, change); } public void LazyUpdate(int index, TChange change) => this.LazyUpdate(index, index, change); - public void LazyUpdate(int start, int end, TChange change) => throw new System.NotImplementedException(); + public void LazyUpdate(int start, int end, TChange change) + { + this.ValidateBounds(start, end); + this._root.LazyUpdate(start, end, change); + } - private class Node + private void ValidateBounds(int start, int end) { + if (start < 0) throw new ArgumentException("The query start index must not be negative.", nameof(start)); + if (end >= this._n) throw new ArgumentException("The query end index must not exceed the original bounds.", nameof(end)); + if (start > end) throw new InvalidOperationException("The query start index must be less than or equal to the query end index"); } } @@ -56,6 +57,7 @@ internal class RecursiveSegmentTreeNode private RecursiveSegmentTreeNode? _left, _right; private TValue? _value; + private LazyUpdateDefinition? _lazyUpdateDefinition; private RecursiveSegmentTreeNode Left => this._left ??= new RecursiveSegmentTreeNode(this._engine, this._start, this._m1); private RecursiveSegmentTreeNode Right => this._right ??= new RecursiveSegmentTreeNode(this._engine, this._m2, this._end); @@ -87,6 +89,7 @@ public TOutput Query(int queryStart, int queryEnd) { if (this._isLeaf) return this._engine.ProduceResult(this.Value); + this.PopulateLazyUpdate(); if (queryEnd <= this._m1) return this.Left.Query(queryStart, queryEnd); if (queryStart >= this._m2) return this.Right.Query(queryStart, queryEnd); @@ -98,8 +101,43 @@ public void Update(int queryStart, int queryEnd, TChange change) if (this._isLeaf) this._value = this._engine.ApplyChange(this.Value, change); else { + this.PopulateLazyUpdate(); if (queryStart <= this._m1) this.Left.Update(queryStart, Math.Min(this._m1, queryEnd), change); if (queryEnd >= this._m2) this.Right.Update(Math.Max(this._m2, queryStart), queryEnd, change); } } + + public void LazyUpdate(int queryStart, int queryEnd, TChange change) + { + if (this._isLeaf) this._value = this._engine.ApplyChange(this.Value, change); + else + { + TChange nextChange; + if (this._lazyUpdateDefinition is not null && this._lazyUpdateDefinition.Start == queryStart && this._lazyUpdateDefinition.End == queryEnd) + nextChange = this._engine.Combine(this._lazyUpdateDefinition.Change, change); + else + { + this.PopulateLazyUpdate(); + nextChange = change; + } + + this._lazyUpdateDefinition = new LazyUpdateDefinition(queryStart, queryEnd, nextChange); + } + } + + private void PopulateLazyUpdate() + { + if (this._lazyUpdateDefinition is null) return; + + if (this._lazyUpdateDefinition.Start <= this._m1) this.Left.LazyUpdate(this._lazyUpdateDefinition.Start, Math.Min(this._m1, this._lazyUpdateDefinition.End), this._lazyUpdateDefinition.Change); + if (this._lazyUpdateDefinition.End >= this._m2) this.Right.LazyUpdate(Math.Max(this._m2, this._lazyUpdateDefinition.Start), this._lazyUpdateDefinition.End, this._lazyUpdateDefinition.Change); + this._lazyUpdateDefinition = null; + } +} + +internal class LazyUpdateDefinition(int start, int end, TChange change) +{ + public int Start { get; } = start; + public int End { get; } = end; + public TChange Change { get; } = change; } \ No newline at end of file diff --git a/TryAtSoftware.Extensions.Collections/StandardSegmentTreeSumEngine.cs b/TryAtSoftware.Extensions.Collections/StandardSegmentTreeSumEngine.cs deleted file mode 100644 index 23db137..0000000 --- a/TryAtSoftware.Extensions.Collections/StandardSegmentTreeSumEngine.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace TryAtSoftware.Extensions.Collections; - -#if NET7_0_OR_GREATER - -using System.Numerics; -using TryAtSoftware.Extensions.Collections.Interfaces; - -public class StandardSegmentTreeSumEngine : ISegmentTreeEngine - where T : struct, INumber -{ - public T CreateDefaultValue() => default; - - public T Combine(T pendingChange, T newChange) => newChange; - - public T ApplyChange(T currentValue, T change) => change; - - public T Merge(T left, T right) => left + right; - - public T ProduceResult(T value) => value; -} - -#endif \ No newline at end of file From 9644759eb46b34d079b5e57b3c4cfa36154538b1 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Thu, 2 May 2024 22:27:49 +0300 Subject: [PATCH 5/7] Removed pre-processors in tests --- .../SegmentTreeTests.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs b/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs index b6db194..64d590d 100644 --- a/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs +++ b/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs @@ -4,8 +4,6 @@ using TryAtSoftware.Randomizer.Core.Helpers; using Xunit; -#if NET7_0_OR_GREATER - public static class SegmentTreeTests { [Fact] @@ -44,6 +42,4 @@ public class StandardSegmentTreeSumEngine : ISegmentTreeEngine public int Merge(int left, int right) => left + right; public int ProduceResult(int value) => value; -} - -#endif \ No newline at end of file +} \ No newline at end of file From 02b7a27f3c6cc9bba3d808bde7892a86468a6b20 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Sun, 5 May 2024 22:37:30 +0300 Subject: [PATCH 6/7] Simplifying the structure of all segment-tree-related components. --- .../SegmentTreeTests.cs | 25 +++--- .../Interfaces/ISegmentTree.cs | 14 +-- .../Interfaces/ISegmentTreeChangeOperator.cs | 6 ++ .../Interfaces/ISegmentTreeEngine.cs | 11 --- .../ISegmentTreeInitializationEngine.cs | 6 ++ .../Interfaces/ISegmentTreeQueryOperator.cs | 7 ++ .../RecursiveSegmentTree.cs | 88 +++++++++---------- 7 files changed, 81 insertions(+), 76 deletions(-) create mode 100644 TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeChangeOperator.cs delete mode 100644 TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeEngine.cs create mode 100644 TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeInitializationEngine.cs create mode 100644 TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeQueryOperator.cs diff --git a/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs b/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs index 64d590d..9d563e4 100644 --- a/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs +++ b/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs @@ -11,34 +11,39 @@ public static void SegmentTreeShouldBeUpdatedSuccessfully() { var n = RandomizationHelper.RandomInteger(0, 100); - var engine = new StandardSegmentTreeSumEngine(); - var segmentTree = new RecursiveSegmentTree(engine, n); + var initializationEngine = new StandardSegmentTreeInitializationEngine(0); + var segmentTree = new RecursiveSegmentTree(n, initializationEngine); var numbers = new int[n]; for (var i = 0; i < n; i++) { numbers[i] = RandomizationHelper.RandomInteger(0, 100); - segmentTree.Update(i, numbers[i]); + segmentTree.Update(i, new StandardSegmentTreeChangeOperator(numbers[i])); } - for (var i = 0; i < n; i++) Assert.Equal(numbers[i], segmentTree.Query(i)); + var queryEngine = new SumSegmentTreeQueryOperator(); + for (var i = 0; i < n; i++) Assert.Equal(numbers[i], segmentTree.Query(i, queryEngine)); var prefixSum = new int[n + 1]; for (var i = 0; i < n; i++) prefixSum[i + 1] = prefixSum[i] + numbers[i]; for (var i = 0; i < n; i++) - for (var j = i; j < n; j++) Assert.Equal(prefixSum[j + 1] - prefixSum[i], segmentTree.Query(i, j)); + for (var j = i; j < n; j++) Assert.Equal(prefixSum[j + 1] - prefixSum[i], segmentTree.Query(i, j, queryEngine)); } } -public class StandardSegmentTreeSumEngine : ISegmentTreeEngine +public class StandardSegmentTreeInitializationEngine(TValue defaultValue) : ISegmentTreeInitializationEngine { - public int CreateDefaultValue() => default; - - public int Combine(int pendingChange, int newChange) => newChange; + public TValue CreateInitialValue(int index) => defaultValue; +} - public int ApplyChange(int currentValue, int change) => change; +public class StandardSegmentTreeChangeOperator(TValue newValue) : ISegmentTreeChangeOperator +{ + public TValue ApplyChange(TValue currentValue) => newValue; +} +public class SumSegmentTreeQueryOperator : ISegmentTreeQueryOperator +{ public int Merge(int left, int right) => left + right; public int ProduceResult(int value) => value; diff --git a/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTree.cs b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTree.cs index 6bfdc84..e9936ef 100644 --- a/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTree.cs +++ b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTree.cs @@ -1,13 +1,13 @@ namespace TryAtSoftware.Extensions.Collections.Interfaces; -public interface ISegmentTree +public interface ISegmentTree { - TOutput Query(int index); - TOutput Query(int start, int end); + TOutput Query(int index, ISegmentTreeQueryOperator queryOperator); + TOutput Query(int start, int end, ISegmentTreeQueryOperator queryOperator); - void Update(int index, TChange change); - void Update(int start, int end, TChange change); + void Update(int index, ISegmentTreeChangeOperator changeOperator); + void Update(int start, int end, ISegmentTreeChangeOperator changeOperator); - void LazyUpdate(int index, TChange change); - void LazyUpdate(int start, int end, TChange change); + void LazyUpdate(int index, ISegmentTreeChangeOperator changeOperator); + void LazyUpdate(int start, int end, ISegmentTreeChangeOperator changeOperator); } \ No newline at end of file diff --git a/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeChangeOperator.cs b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeChangeOperator.cs new file mode 100644 index 0000000..d1f41a6 --- /dev/null +++ b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeChangeOperator.cs @@ -0,0 +1,6 @@ +namespace TryAtSoftware.Extensions.Collections.Interfaces; + +public interface ISegmentTreeChangeOperator +{ + TValue ApplyChange(TValue currentValue); +} \ No newline at end of file diff --git a/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeEngine.cs b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeEngine.cs deleted file mode 100644 index 02949a9..0000000 --- a/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeEngine.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace TryAtSoftware.Extensions.Collections.Interfaces; - -public interface ISegmentTreeEngine -{ - TValue CreateDefaultValue(); - TChange Combine(TChange pendingChange, TChange newChange); - TValue ApplyChange(TValue currentValue, TChange change); - - TOutput Merge(TOutput left, TOutput right); - TOutput ProduceResult(TValue value); -} \ No newline at end of file diff --git a/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeInitializationEngine.cs b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeInitializationEngine.cs new file mode 100644 index 0000000..a437783 --- /dev/null +++ b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeInitializationEngine.cs @@ -0,0 +1,6 @@ +namespace TryAtSoftware.Extensions.Collections.Interfaces; + +public interface ISegmentTreeInitializationEngine +{ + TValue CreateInitialValue(int index); +} \ No newline at end of file diff --git a/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeQueryOperator.cs b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeQueryOperator.cs new file mode 100644 index 0000000..8bbc656 --- /dev/null +++ b/TryAtSoftware.Extensions.Collections/Interfaces/ISegmentTreeQueryOperator.cs @@ -0,0 +1,7 @@ +namespace TryAtSoftware.Extensions.Collections.Interfaces; + +public interface ISegmentTreeQueryOperator +{ + TOutput Merge(TOutput left, TOutput right); + TOutput ProduceResult(TValue value); +} \ No newline at end of file diff --git a/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs b/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs index 9d8991e..f4b2ad1 100644 --- a/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs +++ b/TryAtSoftware.Extensions.Collections/RecursiveSegmentTree.cs @@ -3,42 +3,42 @@ using System; using TryAtSoftware.Extensions.Collections.Interfaces; -public class RecursiveSegmentTree : ISegmentTree +public class RecursiveSegmentTree : ISegmentTree { - private readonly RecursiveSegmentTreeNode _root; + private readonly RecursiveSegmentTreeNode _root; private readonly int _n; - public RecursiveSegmentTree(ISegmentTreeEngine engine, int n) + public RecursiveSegmentTree(int n, ISegmentTreeInitializationEngine initializationEngine) { - if (engine is null) throw new ArgumentNullException(nameof(engine)); + if (initializationEngine is null) throw new ArgumentNullException(nameof(initializationEngine)); if (n < 0) throw new ArgumentException("The count of base elements for the segment tree must not be negative."); - this._root = new RecursiveSegmentTreeNode(engine, 0, n - 1); + this._root = new RecursiveSegmentTreeNode(initializationEngine, 0, n - 1); this._n = n; } - public TOutput Query(int index) => this.Query(index, index); + public TOutput Query(int index, ISegmentTreeQueryOperator queryOperator) => this.Query(index, index, queryOperator); - public TOutput Query(int start, int end) + public TOutput Query(int start, int end, ISegmentTreeQueryOperator queryOperator) { this.ValidateBounds(start, end); - return this._root.Query(start, end); + return this._root.Query(start, end, queryOperator); } - public void Update(int index, TChange change) => this.Update(index, index, change); + public void Update(int index, ISegmentTreeChangeOperator changeOperator) => this.Update(index, index, changeOperator); - public void Update(int start, int end, TChange change) + public void Update(int start, int end, ISegmentTreeChangeOperator changeOperator) { this.ValidateBounds(start, end); - this._root.Update(start, end, change); + this._root.Update(start, end, changeOperator); } - public void LazyUpdate(int index, TChange change) => this.LazyUpdate(index, index, change); + public void LazyUpdate(int index, ISegmentTreeChangeOperator changeOperator) => this.LazyUpdate(index, index, changeOperator); - public void LazyUpdate(int start, int end, TChange change) + public void LazyUpdate(int start, int end, ISegmentTreeChangeOperator changeOperator) { this.ValidateBounds(start, end); - this._root.LazyUpdate(start, end, change); + this._root.LazyUpdate(start, end, changeOperator); } private void ValidateBounds(int start, int end) @@ -49,23 +49,23 @@ private void ValidateBounds(int start, int end) } } -internal class RecursiveSegmentTreeNode +internal class RecursiveSegmentTreeNode { - private readonly ISegmentTreeEngine _engine; + private readonly ISegmentTreeInitializationEngine _initializationEngine; private readonly int _start, _end, _m1, _m2; private readonly bool _isLeaf; - private RecursiveSegmentTreeNode? _left, _right; + private RecursiveSegmentTreeNode? _left, _right; private TValue? _value; - private LazyUpdateDefinition? _lazyUpdateDefinition; + private LazyUpdateDefinition? _lazyUpdateDefinition; - private RecursiveSegmentTreeNode Left => this._left ??= new RecursiveSegmentTreeNode(this._engine, this._start, this._m1); - private RecursiveSegmentTreeNode Right => this._right ??= new RecursiveSegmentTreeNode(this._engine, this._m2, this._end); + private RecursiveSegmentTreeNode Left => this._left ??= new RecursiveSegmentTreeNode(this._initializationEngine, this._start, this._m1); + private RecursiveSegmentTreeNode Right => this._right ??= new RecursiveSegmentTreeNode(this._initializationEngine, this._m2, this._end); private TValue Value => this._value!; - public RecursiveSegmentTreeNode(ISegmentTreeEngine engine, int start, int end) + public RecursiveSegmentTreeNode(ISegmentTreeInitializationEngine initializationEngine, int start, int end) { - this._engine = engine; + this._initializationEngine = initializationEngine; this._start = start; this._end = end; this._isLeaf = start == end; @@ -81,47 +81,39 @@ public RecursiveSegmentTreeNode(ISegmentTreeEngine eng this._m2 = this._m1 + 1; } - if (this._isLeaf) this._value = engine.CreateDefaultValue(); + if (this._isLeaf) this._value = this._initializationEngine.CreateInitialValue(this._start); } // For all the methods below is guaranteed that `this._start <= queryStart <= queryEnd <= this._end`. - public TOutput Query(int queryStart, int queryEnd) + public TOutput Query(int queryStart, int queryEnd, ISegmentTreeQueryOperator queryOperator) { - if (this._isLeaf) return this._engine.ProduceResult(this.Value); + if (this._isLeaf) return queryOperator.ProduceResult(this.Value); this.PopulateLazyUpdate(); - if (queryEnd <= this._m1) return this.Left.Query(queryStart, queryEnd); - if (queryStart >= this._m2) return this.Right.Query(queryStart, queryEnd); + if (queryEnd <= this._m1) return this.Left.Query(queryStart, queryEnd, queryOperator); + if (queryStart >= this._m2) return this.Right.Query(queryStart, queryEnd, queryOperator); - return this._engine.Merge(this.Left.Query(queryStart, this._m1), this.Right.Query(this._m2, queryEnd)); + return queryOperator.Merge(this.Left.Query(queryStart, this._m1, queryOperator), this.Right.Query(this._m2, queryEnd, queryOperator)); } - public void Update(int queryStart, int queryEnd, TChange change) + public void Update(int queryStart, int queryEnd, ISegmentTreeChangeOperator changeOperator) { - if (this._isLeaf) this._value = this._engine.ApplyChange(this.Value, change); + if (this._isLeaf) this._value = changeOperator.ApplyChange(this.Value); else { this.PopulateLazyUpdate(); - if (queryStart <= this._m1) this.Left.Update(queryStart, Math.Min(this._m1, queryEnd), change); - if (queryEnd >= this._m2) this.Right.Update(Math.Max(this._m2, queryStart), queryEnd, change); + if (queryStart <= this._m1) this.Left.Update(queryStart, Math.Min(this._m1, queryEnd), changeOperator); + if (queryEnd >= this._m2) this.Right.Update(Math.Max(this._m2, queryStart), queryEnd, changeOperator); } } - public void LazyUpdate(int queryStart, int queryEnd, TChange change) + public void LazyUpdate(int queryStart, int queryEnd, ISegmentTreeChangeOperator changeOperator) { - if (this._isLeaf) this._value = this._engine.ApplyChange(this.Value, change); + if (this._isLeaf) this._value = changeOperator.ApplyChange(this.Value); else { - TChange nextChange; - if (this._lazyUpdateDefinition is not null && this._lazyUpdateDefinition.Start == queryStart && this._lazyUpdateDefinition.End == queryEnd) - nextChange = this._engine.Combine(this._lazyUpdateDefinition.Change, change); - else - { - this.PopulateLazyUpdate(); - nextChange = change; - } - - this._lazyUpdateDefinition = new LazyUpdateDefinition(queryStart, queryEnd, nextChange); + this.PopulateLazyUpdate(); + this._lazyUpdateDefinition = new LazyUpdateDefinition(queryStart, queryEnd, changeOperator); } } @@ -129,15 +121,15 @@ private void PopulateLazyUpdate() { if (this._lazyUpdateDefinition is null) return; - if (this._lazyUpdateDefinition.Start <= this._m1) this.Left.LazyUpdate(this._lazyUpdateDefinition.Start, Math.Min(this._m1, this._lazyUpdateDefinition.End), this._lazyUpdateDefinition.Change); - if (this._lazyUpdateDefinition.End >= this._m2) this.Right.LazyUpdate(Math.Max(this._m2, this._lazyUpdateDefinition.Start), this._lazyUpdateDefinition.End, this._lazyUpdateDefinition.Change); + if (this._lazyUpdateDefinition.Start <= this._m1) this.Left.LazyUpdate(this._lazyUpdateDefinition.Start, Math.Min(this._m1, this._lazyUpdateDefinition.End), this._lazyUpdateDefinition.ChangeOperator); + if (this._lazyUpdateDefinition.End >= this._m2) this.Right.LazyUpdate(Math.Max(this._m2, this._lazyUpdateDefinition.Start), this._lazyUpdateDefinition.End, this._lazyUpdateDefinition.ChangeOperator); this._lazyUpdateDefinition = null; } } -internal class LazyUpdateDefinition(int start, int end, TChange change) +internal class LazyUpdateDefinition(int start, int end, ISegmentTreeChangeOperator changeOperator) { public int Start { get; } = start; public int End { get; } = end; - public TChange Change { get; } = change; + public ISegmentTreeChangeOperator ChangeOperator { get; } = changeOperator; } \ No newline at end of file From 133fd7e7f47859c7e640365a05de39ad79ae00c5 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Sun, 5 May 2024 22:41:14 +0300 Subject: [PATCH 7/7] Added tests covering basic lazy update scenarios --- .../SegmentTreeTests.cs | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs b/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs index 9d563e4..2b60754 100644 --- a/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs +++ b/TryAtSoftware.Extensions.Collections.Tests/SegmentTreeTests.cs @@ -21,14 +21,37 @@ public static void SegmentTreeShouldBeUpdatedSuccessfully() segmentTree.Update(i, new StandardSegmentTreeChangeOperator(numbers[i])); } + ValidateQueries(numbers, segmentTree); + } + + [Fact] + public static void SegmentTreeShouldBeLazyUpdatedSuccessfully() + { + var n = RandomizationHelper.RandomInteger(0, 100); + + var initializationEngine = new StandardSegmentTreeInitializationEngine(0); + var segmentTree = new RecursiveSegmentTree(n, initializationEngine); + + var numbers = new int[n]; + for (var i = 0; i < n; i++) + { + numbers[i] = RandomizationHelper.RandomInteger(0, 100); + segmentTree.LazyUpdate(i, new StandardSegmentTreeChangeOperator(numbers[i])); + } + + ValidateQueries(numbers, segmentTree); + } + + private static void ValidateQueries(int[] numbers, RecursiveSegmentTree segmentTree) + { var queryEngine = new SumSegmentTreeQueryOperator(); - for (var i = 0; i < n; i++) Assert.Equal(numbers[i], segmentTree.Query(i, queryEngine)); + for (var i = 0; i < numbers.Length; i++) Assert.Equal(numbers[i], segmentTree.Query(i, queryEngine)); - var prefixSum = new int[n + 1]; - for (var i = 0; i < n; i++) prefixSum[i + 1] = prefixSum[i] + numbers[i]; + var prefixSum = new int[numbers.Length + 1]; + for (var i = 0; i < numbers.Length; i++) prefixSum[i + 1] = prefixSum[i] + numbers[i]; - for (var i = 0; i < n; i++) - for (var j = i; j < n; j++) Assert.Equal(prefixSum[j + 1] - prefixSum[i], segmentTree.Query(i, j, queryEngine)); + for (var i = 0; i < numbers.Length; i++) + for (var j = i; j < numbers.Length; j++) Assert.Equal(prefixSum[j + 1] - prefixSum[i], segmentTree.Query(i, j, queryEngine)); } }