From 21d50956b169cccba2faf1309ae672d9810ea4c5 Mon Sep 17 00:00:00 2001 From: Kibnet Philosoff Date: Tue, 27 May 2025 11:14:26 +0300 Subject: [PATCH 1/6] test: Add mutant killers --- src/IntSet.Tests/StrykerTests.cs | 133 +++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/src/IntSet.Tests/StrykerTests.cs b/src/IntSet.Tests/StrykerTests.cs index 4b9ea12..edd9bc7 100644 --- a/src/IntSet.Tests/StrykerTests.cs +++ b/src/IntSet.Tests/StrykerTests.cs @@ -195,6 +195,87 @@ public void SetEquals_DifferentOrderButSameElements_ReturnsTrue() Assert.True(a.SetEquals(b)); } + [Fact] + public void CheckUniqueAndUnfoundElements_EmptySet_ReturnsCorrectCounts() + { + var set = new IntSet(); + var result = set.IsSubsetOf(new[] { 1, 2 }); + Assert.Equal(true, result); + } + + [Fact] + public void CheckUniqueAndUnfoundElements_MixedElements_ReturnsCorrectCounts() + { + var set = new IntSet(new[] { 1, 2, 3 }); + var result = set.IsSubsetOf(new[] { 2, 3, 4, 5 }); + Assert.Equal(false, result); + } + + [Fact] + public void ContainsAllElements_EmptyEnumerable_ReturnsTrue() + { + var set = new IntSet(new[] { 1, 2, 3 }); + Assert.True(set.IsSupersetOf(Enumerable.Empty())); + } + + [Fact] + public void ContainsAllElements_MixedElements_ReturnsFalse() + { + var set = new IntSet(new[] { 1, 2, 3 }); + Assert.False(set.IsSupersetOf(new[] { 2, 3, 4 })); + } + + [Fact] + public void IntersectWithIntSet_EmptySet_DoesNotChange() + { + var set = new IntSet(new[] { 1, 2, 3 }); + var other = new IntSet(); + set.IntersectWith(other); + Assert.Empty(set); + } + + [Fact] + public void IntersectWithIntSet_PartialIntersection_RetainsCommonElements() + { + var set = new IntSet(new[] { 1, 2, 3 }); + var other = new IntSet(new[] { 2, 3, 4 }); + set.IntersectWith(other); + Assert.Equal(new[] { 2, 3 }, set.OrderBy(x => x)); + } + + [Fact] + public void IntersectWithEnumerable_EmptyEnumerable_DoesNotChange() + { + var set = new IntSet(new[] { 1, 2, 3 }); + set.IntersectWith(Enumerable.Empty()); + Assert.Equal(Enumerable.Empty(), set.OrderBy(x => x)); + } + + [Fact] + public void IntersectWithEnumerable_PartialIntersection_RetainsCommonElements() + { + var set = new IntSet(new[] { 1, 2, 3 }); + var other = new[] { 2, 3, 4 }; + set.IntersectWith(other); + Assert.Equal(new[] { 2, 3 }, set.OrderBy(x => x)); + } + + [Fact] + public void Constructor_FullSet_CreatesFullSet() + { + var set = new IntSet(false, true); + Assert.Equal(uint.MaxValue, set.LongCount); + Assert.True(set.root.Full); + Assert.Null(set.root.Cards); + } + + [Fact] + public void Constructor_EmptyCollection_DoesNotThrow() + { + var set = new IntSet(new int[0]); + Assert.Empty(set); + } + [Fact] public void SetEquals_DifferentCounts_ReturnsFalse() { @@ -328,6 +409,58 @@ public void CheckFull_NonFull_DoesNotClearBytes() Assert.NotNull(set.root.Cards[0].Cards[0].Cards[0].Cards[0].Bytes); } + [Fact] + public void Remove_CascadeRemoval_RemovesAllLevels() + { + var set = new IntSet(); + set.Add(1000000); // This will create a deep card structure + Assert.True(set.Contains(1000000)); + Assert.True(set.Remove(1000000)); + Assert.False(set.Contains(1000000)); + Assert.Equal(0, set.Count); + } + + [Fact] + public void LargeSetOperations_ReturnsCorrectCounts() + { + var set = new IntSet(); + for (int i = 0; i < 1000; i++) + { + set.Add(i * 2); // Add even numbers + } + + var otherSet = new IntSet(Enumerable.Range(0, 1000)); + Assert.Equal(1000, set.Count); // 1000 even numbers + Assert.Equal(1000, otherSet.Count); // 1000 numbers + + set.IntersectWith(otherSet); + Assert.Equal(500, set.Count); // 500 even numbers + } + + [Fact] + public void BoundaryValuesOperations_WorkCorrectly() + { + var set = new IntSet(); + + // Test with max int value + set.Add(int.MaxValue); + Assert.True(set.Contains(int.MaxValue)); + Assert.True(set.Remove(int.MaxValue)); + Assert.False(set.Contains(int.MaxValue)); + + // Test with min int value + set.Add(int.MinValue); + Assert.True(set.Contains(int.MinValue)); + Assert.True(set.Remove(int.MinValue)); + Assert.False(set.Contains(int.MinValue)); + + // Test with positive boundary + set.Add(16383); // Last index in first card + Assert.True(set.Contains(16383)); + Assert.True(set.Remove(16383)); + Assert.False(set.Contains(16383)); + } + [Fact] public void UnionWith_AddsAllElements() { From b8b87e0131febfeb19766415fa22a34fb80c3e4b Mon Sep 17 00:00:00 2001 From: Kibnet Philosoff Date: Tue, 27 May 2025 11:14:46 +0300 Subject: [PATCH 2/6] ci: Bump dotnet version in CI --- .github/workflows/dotnetcore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 5990827..584bfd4 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -17,7 +17,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 3.1.202 + dotnet-version: 9.0.5 - name: Install dependencies run: dotnet restore - name: Pack solution From 26e803c15dedb6638f0ade33321f48951d0bcace Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 17:35:40 +0000 Subject: [PATCH 3/6] ci: Add step to run unit tests in workflow Modifies the GitHub Actions workflow (`.github/workflows/dotnetcore.yml`) to include a step for running unit tests using `dotnet test`. This ensures that tests are executed automatically as part of the CI process after dependencies are restored and before the solution is packed and published. The tests are run with the Release configuration. refactor: Separate CI tests and manual NuGet publication workflows I've revised the GitHub Actions CI/CD setup for you: 1. Automated Testing (`ci-tests.yml`): - I renamed this from `dotnetcore.yml`. - It now triggers on pushes and pull requests to all branches (`**`). - It performs checkout, .NET setup, dependency restore, and runs unit tests. - I've removed the packaging and publishing steps from this workflow. 2. Manual NuGet Publication (`manual-publish.yml`): - This is a new workflow file. - You can trigger it manually via `workflow_dispatch` from the GitHub Actions UI. - It contains steps for checkout, .NET setup, restore, packing the solution, and publishing to both nuget.org and GitHub Packages. This change allows for continuous testing on all development branches and provides you with explicit control over the release and publication of NuGet packages. --- .github/workflows/ci-tests.yml | 24 +++++++++++++++++++ .../{dotnetcore.yml => manual-publish.yml} | 11 +++------ IntSet.sln | 10 ++++++-- 3 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/ci-tests.yml rename .github/workflows/{dotnetcore.yml => manual-publish.yml} (87%) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml new file mode 100644 index 0000000..bc82098 --- /dev/null +++ b/.github/workflows/ci-tests.yml @@ -0,0 +1,24 @@ +name: CI Tests + +on: + push: + branches: ['**'] # Test on push to all branches + pull_request: + branches: ['**'] # Test on PR to all branches (can remove type filter or adjust as needed) + +jobs: + test: # Renamed job for clarity + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 9.0.5 # Consider updating if your project supports newer .NET versions + - name: Install dependencies + run: dotnet restore + - name: Run tests + run: dotnet test --configuration Release --no-restore + # Removed Pack and Push steps diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/manual-publish.yml similarity index 87% rename from .github/workflows/dotnetcore.yml rename to .github/workflows/manual-publish.yml index 584bfd4..cf09c8e 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/manual-publish.yml @@ -1,15 +1,10 @@ -name: NuGet Generation +name: Manual NuGet Publication on: - push: - branches: [ master ] - pull_request: - types: [closed] - branches: [ master ] + workflow_dispatch: # Allows manual triggering from the GitHub Actions UI jobs: - build: - + publish: runs-on: ubuntu-latest steps: diff --git a/IntSet.sln b/IntSet.sln index 84e0eff..fb24024 100644 --- a/IntSet.sln +++ b/IntSet.sln @@ -1,12 +1,18 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29613.14 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36109.1 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntSet", "src\IntSet\IntSet.csproj", "{AE63B664-F383-48F8-8EEE-70FCB2169AF7}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntSet.Tests", "src\IntSet.Tests\IntSet.Tests.csproj", "{175DFCC8-9221-4D95-81D3-42C7252227D0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Элементы решения", "Элементы решения", "{754FC069-D67B-A9D7-50A1-8D1CA196D8F1}" + ProjectSection(SolutionItems) = preProject + .github\workflows\ci-tests.yml = .github\workflows\ci-tests.yml + .github\workflows\manual-publish.yml = .github\workflows\manual-publish.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU From de9eac293a65654a4030580bf154b988fdc73e71 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 17:10:59 +0000 Subject: [PATCH 4/6] feat: Implement GetElementsInRange for IntSet Adds a new public method `GetElementsInRange(int firstElement, int lastElement)` to the `IntSet` class. This method allows you to enumerate integers from the set that fall within the specified range `[min(first, last), max(first, last)]`. The enumeration direction (ascending or descending) is determined by the order of `firstElement` and `lastElement`. Key changes include: - New private helper methods for bidirectional traversal of the internal Card/byte structure (`TraverseRootCardsDirectional`, `TraverseCardLevelDirectional`, `TraverseByteLevelDirectional`, `ProcessBytesDirectional`). - The existing `GetEnumerator()` was updated to use the new ascending directional traversal logic, ensuring consistency. - Efficient pruning logic is implemented at each level of traversal within `GetElementsInRange` to skip blocks of numbers that are outside the target range, using a new `GetBlockRange` helper. - Extensive unit tests have been added to `IntSetTests.cs` to cover various scenarios, including empty sets, ascending/descending ranges, edge cases, gaps in data, negative numbers, and int.MinValue/MaxValue. --- src/IntSet.Tests/IntSetTests.cs | 155 +++++++++++++ src/IntSet/IntSet.cs | 389 ++++++++++++++++++++++++++++++-- 2 files changed, 528 insertions(+), 16 deletions(-) diff --git a/src/IntSet.Tests/IntSetTests.cs b/src/IntSet.Tests/IntSetTests.cs index 4c159a5..28062b1 100644 --- a/src/IntSet.Tests/IntSetTests.cs +++ b/src/IntSet.Tests/IntSetTests.cs @@ -1,6 +1,7 @@  using System; using System.Collections; +using System.Collections.Generic; using System.Linq; using Xunit; @@ -450,5 +451,159 @@ public void CopyTo_MatchExactSpace_CopiesAll() set.CopyTo(dst, 2); Assert.Equal(new[] { 0, 0, 1, 2, 3 }, dst); } + + #region GetElementsInRange Tests + + [Fact] + public void GetElementsInRange_EmptySet() + { + var set = new Kibnet.IntSet(); + Assert.Equal(new List(), set.GetElementsInRange(1, 10).ToList()); + Assert.Equal(new List(), set.GetElementsInRange(10, 1).ToList()); + } + + [Fact] + public void GetElementsInRange_PopulatedSet_Ascending() + { + var set = new Kibnet.IntSet(new[] { 1, 5, 10, 15, 20, 25 }); + Assert.Equal(new List { 1, 5, 10, 15, 20, 25 }, set.GetElementsInRange(1, 25).ToList()); + Assert.Equal(new List { 10, 15, 20 }, set.GetElementsInRange(10, 20).ToList()); + Assert.Equal(new List { 1, 5 }, set.GetElementsInRange(0, 5).ToList()); + Assert.Equal(new List { 20, 25 }, set.GetElementsInRange(20, 30).ToList()); + Assert.Equal(new List(), set.GetElementsInRange(11, 14).ToList()); + Assert.Equal(new List { 1, 5, 10, 15, 20, 25 }, set.GetElementsInRange(0, 30).ToList()); + Assert.Equal(new List { 5, 10, 15 }, set.GetElementsInRange(5, 15).ToList()); + } + + [Fact] + public void GetElementsInRange_PopulatedSet_Descending() + { + var set = new Kibnet.IntSet(new[] { 1, 5, 10, 15, 20, 25 }); + Assert.Equal(new List { 25, 20, 15, 10, 5, 1 }, set.GetElementsInRange(25, 1).ToList()); + Assert.Equal(new List { 20, 15, 10 }, set.GetElementsInRange(20, 10).ToList()); + Assert.Equal(new List { 25, 20 }, set.GetElementsInRange(30, 20).ToList()); + Assert.Equal(new List { 5, 1 }, set.GetElementsInRange(5, 0).ToList()); + Assert.Equal(new List(), set.GetElementsInRange(14, 11).ToList()); + Assert.Equal(new List { 25, 20, 15, 10, 5, 1 }, set.GetElementsInRange(30, 0).ToList()); + Assert.Equal(new List { 15, 10, 5 }, set.GetElementsInRange(15, 5).ToList()); + } + + [Fact] + public void GetElementsInRange_PopulatedSet_SingleElementRange() + { + var set = new Kibnet.IntSet(new[] { 1, 5, 10, 15, 20 }); + Assert.Equal(new List { 10 }, set.GetElementsInRange(10, 10).ToList()); + Assert.Equal(new List(), set.GetElementsInRange(11, 11).ToList()); + Assert.Equal(new List { 1 }, set.GetElementsInRange(1, 1).ToList()); + Assert.Equal(new List { 20 }, set.GetElementsInRange(20, 20).ToList()); + } + + [Fact] + public void GetElementsInRange_ComplexScenarios_Gaps() + { + var set = new Kibnet.IntSet(new[] { 1, 2, 3, 10, 11, 12, 20, 21, 22 }); + Assert.Equal(new List { 3, 10, 11 }, set.GetElementsInRange(3, 11).ToList()); + Assert.Equal(new List { 11, 10, 3 }, set.GetElementsInRange(11, 3).ToList()); + Assert.Equal(new List { 3, 10 }, set.GetElementsInRange(3, 10).ToList()); // Test case name was 'Asc across gap' + Assert.Equal(new List { 10, 3 }, set.GetElementsInRange(10, 3).ToList()); // Test case name was 'Desc across gap' + } + + [Fact] + public void GetElementsInRange_ComplexScenarios_NegativeNumbers() + { + var negSet = new Kibnet.IntSet(new[] { -10, -5, 0, 5, 10 }); + Assert.Equal(new List { -5, 0, 5 }, negSet.GetElementsInRange(-5, 5).ToList()); + Assert.Equal(new List { 5, 0, -5 }, negSet.GetElementsInRange(5, -5).ToList()); + Assert.Equal(new List { -10, -5 }, negSet.GetElementsInRange(-15, -5).ToList()); + Assert.Equal(new List { -5, -10 }, negSet.GetElementsInRange(-5, -15).ToList()); + Assert.Equal(new List { -10 }, negSet.GetElementsInRange(-10, -10).ToList()); + Assert.Equal(new List { 0 }, negSet.GetElementsInRange(0, 0).ToList()); + Assert.Equal(new List { 10 }, negSet.GetElementsInRange(10, 10).ToList()); + } + + [Fact] + public void GetElementsInRange_FullRange_MinMaxInt() + { + var set = new Kibnet.IntSet(new[] { int.MinValue, 0, int.MaxValue }); + Assert.Equal(new List { int.MinValue, 0, int.MaxValue }, set.GetElementsInRange(int.MinValue, int.MaxValue).ToList()); + Assert.Equal(new List { int.MaxValue, 0, int.MinValue }, set.GetElementsInRange(int.MaxValue, int.MinValue).ToList()); + } + + [Fact] + public void GetElementsInRange_LargeNumbers() + { + var set = new Kibnet.IntSet(new[] { 1000000, 2000000, 3000000, int.MaxValue - 5, int.MaxValue }); + Assert.Equal(new List { 1000000, 2000000 }, set.GetElementsInRange(1000000, 2500000).ToList()); + Assert.Equal(new List { 2000000, 1000000 }, set.GetElementsInRange(2500000, 1000000).ToList()); + Assert.Equal(new List { int.MaxValue - 5, int.MaxValue }, set.GetElementsInRange(int.MaxValue - 10, int.MaxValue).ToList()); + Assert.Equal(new List { int.MaxValue, int.MaxValue -5 }, set.GetElementsInRange(int.MaxValue, int.MaxValue - 10).ToList()); + } + + [Fact] + public void GetElementsInRange_RangeOutsidePopulatedData() + { + var set = new Kibnet.IntSet(new[] { 10, 20, 30 }); + Assert.Equal(new List(), set.GetElementsInRange(1, 5).ToList()); // Range before + Assert.Equal(new List(), set.GetElementsInRange(5, 1).ToList()); + Assert.Equal(new List(), set.GetElementsInRange(35, 40).ToList()); // Range after + Assert.Equal(new List(), set.GetElementsInRange(40, 35).ToList()); + Assert.Equal(new List(), set.GetElementsInRange(22, 28).ToList()); // Range between elements + Assert.Equal(new List(), set.GetElementsInRange(28, 22).ToList()); + } + + [Fact] + public void GetElementsInRange_SingleElementInSet_MatchingRange() + { + var set = new Kibnet.IntSet(new[] { 42 }); + Assert.Equal(new List { 42 }, set.GetElementsInRange(42, 42).ToList()); + Assert.Equal(new List { 42 }, set.GetElementsInRange(40, 45).ToList()); + Assert.Equal(new List { 42 }, set.GetElementsInRange(45, 40).ToList()); + } + + [Fact] + public void GetElementsInRange_SingleElementInSet_NonMatchingRange() + { + var set = new Kibnet.IntSet(new[] { 42 }); + Assert.Equal(new List(), set.GetElementsInRange(10, 20).ToList()); + Assert.Equal(new List(), set.GetElementsInRange(50, 60).ToList()); + } + + // Test with a set that forces traversal through multiple levels of cards + [Fact] + public void GetElementsInRange_DeepTraversal() + { + // These numbers are chosen to likely span different i0, i1, i2, i3 blocks + var data = new List { 0, 1, 2, + (1 << 8) + 5, (1 << 8) + 10, // Different i3 + (1 << 14) + 7, (1 << 14) + 15, // Different i2 + (1 << 20) + 3, (1 << 20) + 9, // Different i1 + (1 << 26) + 100, (1 << 26) + 200, // Different i0 + int.MaxValue - 10, int.MaxValue -1, int.MaxValue}; + var set = new Kibnet.IntSet(data); + + var expectedAsc = data.Where(x => x >= ((1 << 8) + 5) && x <= ((1 << 26) + 100)).OrderBy(x => x).ToList(); + Assert.Equal(expectedAsc, set.GetElementsInRange((1 << 8) + 5, (1 << 26) + 100).ToList()); + + var expectedDesc = data.Where(x => x >= ((1 << 14) + 7) && x <= ((1 << 20) + 9)).OrderByDescending(x => x).ToList(); + Assert.Equal(expectedDesc, set.GetElementsInRange((1 << 20) + 9, (1 << 14) + 7).ToList()); + + // Range including int.MaxValue + Assert.Equal(new List { (1 << 26) + 200, int.MaxValue - 10, int.MaxValue -1, int.MaxValue }, set.GetElementsInRange((1 << 26) + 150, int.MaxValue).ToList()); + Assert.Equal(new List { int.MaxValue, int.MaxValue -1, int.MaxValue - 10, (1 << 26) + 200 }, set.GetElementsInRange(int.MaxValue, (1 << 26) + 150).ToList()); + + // Range including int.MinValue (if 0 is considered MinValue for positive range) + // If IntSet can store negative numbers, this test would be more relevant with actual int.MinValue + var dataWithNeg = new List(data); + dataWithNeg.Add(int.MinValue); + dataWithNeg.Add(int.MinValue+1); + var setWithNeg = new Kibnet.IntSet(dataWithNeg); + dataWithNeg.Sort(); // For expected list + + Assert.Equal(dataWithNeg.Where(x => x >= int.MinValue && x <= 2).ToList(), setWithNeg.GetElementsInRange(int.MinValue, 2).ToList()); + Assert.Equal(dataWithNeg.Where(x => x >= int.MinValue && x <= 2).OrderByDescending(x=>x).ToList(), setWithNeg.GetElementsInRange(2, int.MinValue).ToList()); + + } + + #endregion } } diff --git a/src/IntSet/IntSet.cs b/src/IntSet/IntSet.cs index 462669f..a98a6ba 100644 --- a/src/IntSet/IntSet.cs +++ b/src/IntSet/IntSet.cs @@ -9,6 +9,12 @@ namespace Kibnet { + internal enum TraversalDirection + { + Ascending, + Descending + } + [Serializable] public class IntSet : ISet, IReadOnlyCollection { @@ -516,15 +522,24 @@ public static void GetMask(Span span, Card arg) public IEnumerator GetEnumerator() { - foreach (var i0 in TraverseRootCards(root)) + foreach (var i0 in TraverseRootCardsDirectional(root, TraversalDirection.Ascending)) { - foreach (var i1 in TraverseCards(root, i0)) + var card0 = root.Full ? root : root.Cards?[i0]; + if (card0 == null) continue; + + foreach (var i1 in TraverseCardLevelDirectional(card0, TraversalDirection.Ascending)) { - foreach (var i2 in TraverseCards(root, i0, i1)) + var card1 = card0.Full ? card0 : card0.Cards?[i1]; + if (card1 == null) continue; + + foreach (var i2 in TraverseCardLevelDirectional(card1, TraversalDirection.Ascending)) { - foreach (var i3 in TraverseCards(root, i0, i1, i2)) + var card2 = card1.Full ? card1 : card1.Cards?[i2]; + if (card2 == null) continue; + + foreach (var i3 in TraverseCardLevelDirectional(card2, TraversalDirection.Ascending)) { - foreach (var value in ProcessBytes(root, i0, i1, i2, i3)) + foreach (var value in ProcessBytesDirectional(root, i0, i1, i2, i3, TraversalDirection.Ascending)) { yield return value; } @@ -538,14 +553,14 @@ private IEnumerable TraverseRootCards(Card root) { for (int i0 = 32; i0 < 64; i0++) { - if (root.Full || root.Cards[i0] != null) + if (root.Full || (root.Cards != null && root.Cards[i0] != null)) { yield return i0; } } for (int i0 = 0; i0 < 32; i0++) { - if (root.Full || root.Cards[i0] != null) + if (root.Full || (root.Cards != null && root.Cards[i0] != null)) { yield return i0; } @@ -554,26 +569,368 @@ private IEnumerable TraverseRootCards(Card root) private IEnumerable TraverseCards(Card node, params int[] indices) { - var current = indices.Aggregate(node, (n, index) => n.Full ? n : n.Cards[index]); - return current != null ? (IEnumerable)current : []; + var current = node; + foreach (var index in indices) + { + if (current.Full) break; + if (current.Cards == null || current.Cards[index] == null) return Enumerable.Empty(); + current = current.Cards[index]; + } + // At this point, 'current' is the card at the level specified by indices. + // This method, as used by the original GetEnumerator, expects to return the *indices* of the next level. + // Or, if it's the byte level, it returns the bytes themselves. + + if (current.Full) // If the target card is full, all its sub-indices/bytes are present. + { + if (indices.Length == 3) // This means 'current' is a level 3 card, next is byte level + { + return Enumerable.Range(0, 32).Select(i => (int)byte.MaxValue); // Represent full bytes + } + else // Next is another card level + { + return Enumerable.Range(0, 64); + } + } + else if (indices.Length == 3) // Target is byte level, and not full + { + if (current.Bytes != null) + { + return current.Bytes.Select(b => (int)b); + } + return Enumerable.Empty(); + } + else // Target is another card level, and not full + { + if (current.Cards != null) + { + List availableIndices = new List(); + for (int i = 0; i < 64; i++) + { + if (current.Cards[i] != null) + { + availableIndices.Add(i); + } + } + return availableIndices; + } + return Enumerable.Empty(); + } } private IEnumerable ProcessBytes(Card root, int i0, int i1, int i2, int i3) { - var bytes = TraverseCards(root, i0, i1, i2, i3); - int byteCount = 0; + // Navigate to the leaf card (level 4 card) + Card leafCard = root; + int[] path = { i0, i1, i2, i3 }; + foreach (var index in path) + { + if (leafCard.Full) break; // All sub-paths exist + if (leafCard.Cards == null || leafCard.Cards[index] == null) yield break; // Path does not exist + leafCard = leafCard.Cards[index]; + } + + IEnumerable bytesToProcess; + if (leafCard.Full) + { + bytesToProcess = Enumerable.Repeat(byte.MaxValue, 32); + } + else if (leafCard.Bytes != null) + { + bytesToProcess = leafCard.Bytes; + } + else + { + yield break; // No bytes to process + } + + int byteIndexInArray = 0; + foreach (var byteValue in bytesToProcess) + { + for (int bitIdx = 0; bitIdx < 8; bitIdx++) + { + if ((byteValue & (1 << bitIdx)) != 0) + { + yield return (i0 << 26) | (i1 << 20) | (i2 << 14) | (i3 << 8) | (byteIndexInArray << 3) | bitIdx; + } + } + byteIndexInArray++; + } + } + + // New Bidirectional Traversal Methods + + private IEnumerable TraverseRootCardsDirectional(Card rootNode, TraversalDirection direction) + { + if (direction == TraversalDirection.Ascending) + { + for (int i0 = 32; i0 < 64; i0++) + { + if (rootNode.Full || (rootNode.Cards != null && rootNode.Cards[i0] != null)) + { + yield return i0; + } + } + for (int i0 = 0; i0 < 32; i0++) + { + if (rootNode.Full || (rootNode.Cards != null && rootNode.Cards[i0] != null)) + { + yield return i0; + } + } + } + else // Descending + { + for (int i0 = 31; i0 >= 0; i0--) + { + if (rootNode.Full || (rootNode.Cards != null && rootNode.Cards[i0] != null)) + { + yield return i0; + } + } + for (int i0 = 63; i0 >= 32; i0--) + { + if (rootNode.Full || (rootNode.Cards != null && rootNode.Cards[i0] != null)) + { + yield return i0; + } + } + } + } - foreach (var i4 in bytes.Cast()) + private IEnumerable TraverseCardLevelDirectional(Card currentCard, TraversalDirection direction) + { + if (direction == TraversalDirection.Ascending) { - for (int j = 0; j < 8; j++) + if (currentCard.Cards != null) { - if ((i4 & (1 << j)) != 0) + for (int idx = 0; idx < 64; idx++) { - yield return (i0 << 26) | (i1 << 20) | (i2 << 14) | (i3 << 8) | (byteCount << 3) | j; + if (currentCard.Cards[idx] != null) + { + yield return idx; + } + } + } + else if (currentCard.Full) + { + for (int idx = 0; idx < 64; idx++) + { + yield return idx; } } - byteCount++; } + else // Descending + { + if (currentCard.Cards != null) + { + for (int idx = 63; idx >= 0; idx--) + { + if (currentCard.Cards[idx] != null) + { + yield return idx; + } + } + } + else if (currentCard.Full) + { + for (int idx = 63; idx >= 0; idx--) + { + yield return idx; + } + } + } + } + + private IEnumerable TraverseByteLevelDirectional(Card leafCard, TraversalDirection direction) + { + if (direction == TraversalDirection.Ascending) + { + if (leafCard.Bytes != null) + { + for (int byteIdx = 0; byteIdx < 32; byteIdx++) + { + yield return leafCard.Bytes[byteIdx]; + } + } + else if (leafCard.Full) + { + for (int byteIdx = 0; byteIdx < 32; byteIdx++) + { + yield return byte.MaxValue; + } + } + } + else // Descending + { + if (leafCard.Bytes != null) + { + for (int byteIdx = 31; byteIdx >= 0; byteIdx--) + { + yield return leafCard.Bytes[byteIdx]; + } + } + else if (leafCard.Full) + { + for (int byteIdx = 31; byteIdx >= 0; byteIdx--) + { + yield return byte.MaxValue; + } + } + } + } + + private IEnumerable ProcessBytesDirectional(Card rootNode, int i0, int i1, int i2, int i3, TraversalDirection direction) + { + var current = rootNode; + int[] indices = { i0, i1, i2, i3 }; + foreach (var index in indices) + { + if (current.Full) break; + if (current.Cards == null || current.Cards[index] == null) yield break; + current = current.Cards[index]; + } + Card leafCard = current; + + if (direction == TraversalDirection.Ascending) + { + int byteIndexInArray = 0; + foreach (var byteValue in TraverseByteLevelDirectional(leafCard, TraversalDirection.Ascending)) + { + for (int bitIdx = 0; bitIdx < 8; bitIdx++) + { + if ((byteValue & (1 << bitIdx)) != 0) + { + yield return (i0 << 26) | (i1 << 20) | (i2 << 14) | (i3 << 8) | (byteIndexInArray << 3) | bitIdx; + } + } + byteIndexInArray++; + } + } + else // Descending + { + int byteIndexInArray = 31; + foreach (var byteValue in TraverseByteLevelDirectional(leafCard, TraversalDirection.Descending)) + { + for (int bitIdx = 7; bitIdx >= 0; bitIdx--) + { + if ((byteValue & (1 << bitIdx)) != 0) + { + yield return (i0 << 26) | (i1 << 20) | (i2 << 14) | (i3 << 8) | (byteIndexInArray << 3) | bitIdx; + } + } + byteIndexInArray--; + } + } + } + + #endregion + + #region Public API + + public IEnumerable GetElementsInRange(int firstElement, int lastElement) + { + int minValue = Math.Min(firstElement, lastElement); + int maxValue = Math.Max(firstElement, lastElement); + TraversalDirection direction = (firstElement <= lastElement) ? TraversalDirection.Ascending : TraversalDirection.Descending; + + Card c0Node, c1Node, c2Node; + + foreach (int i0 in TraverseRootCardsDirectional(root, direction)) + { + var (blockMin_i0, blockMax_i0) = GetBlockRange(i0); + if (direction == TraversalDirection.Ascending) { + if (blockMin_i0 > maxValue) break; + if (blockMax_i0 < minValue) continue; + } else { // Descending + if (blockMax_i0 < minValue) break; + if (blockMin_i0 > maxValue) continue; + } + + c0Node = (root.Full || root.Cards == null) ? root : root.Cards[i0]; + if (c0Node == null) continue; + + foreach (int i1 in TraverseCardLevelDirectional(c0Node, direction)) + { + var (blockMin_i1, blockMax_i1) = GetBlockRange(i0, i1); + if (direction == TraversalDirection.Ascending) { + if (blockMin_i1 > maxValue) break; + if (blockMax_i1 < minValue) continue; + } else { // Descending + if (blockMax_i1 < minValue) break; + if (blockMin_i1 > maxValue) continue; + } + + c1Node = (c0Node.Full || c0Node.Cards == null) ? c0Node : c0Node.Cards[i1]; + if (c1Node == null) continue; + + foreach (int i2 in TraverseCardLevelDirectional(c1Node, direction)) + { + var (blockMin_i2, blockMax_i2) = GetBlockRange(i0, i1, i2); + if (direction == TraversalDirection.Ascending) { + if (blockMin_i2 > maxValue) break; + if (blockMax_i2 < minValue) continue; + } else { // Descending + if (blockMax_i2 < minValue) break; + if (blockMin_i2 > maxValue) continue; + } + + c2Node = (c1Node.Full || c1Node.Cards == null) ? c1Node : c1Node.Cards[i2]; + if (c2Node == null) continue; + + foreach (int i3 in TraverseCardLevelDirectional(c2Node, direction)) + { + var (blockMin_i3, blockMax_i3) = GetBlockRange(i0, i1, i2, i3); + if (direction == TraversalDirection.Ascending) { + if (blockMin_i3 > maxValue) break; + if (blockMax_i3 < minValue) continue; + } else { // Descending + if (blockMax_i3 < minValue) break; + if (blockMin_i3 > maxValue) continue; + } + + foreach (int value in ProcessBytesDirectional(root, i0, i1, i2, i3, direction)) + { + if (value >= minValue && value <= maxValue) + { + yield return value; + } + // Optimization: if ProcessBytesDirectional guarantees sorted output for its block: + if (direction == TraversalDirection.Ascending && value > maxValue && value >= blockMin_i3) break; + if (direction == TraversalDirection.Descending && value < minValue && value <= blockMax_i3) break; + } + } + } + } + } + } + + private static (int Min, int Max) GetBlockRange(int i0 = -1, int i1 = -1, int i2 = -1, int i3 = -1) + { + int minBase = 0; + int maxBase = 0; + uint currentMaxMask = 0xFFFFFFFF; + + if (i0 != -1) { + minBase |= (i0 << 26); + maxBase |= (i0 << 26); + currentMaxMask = 0x03FFFFFF; + } + if (i1 != -1) { + minBase |= (i1 << 20); + maxBase |= (i1 << 20); + currentMaxMask = 0x000FFFFF; + } + if (i2 != -1) { + minBase |= (i2 << 14); + maxBase |= (i2 << 14); + currentMaxMask = 0x00003FFF; + } + if (i3 != -1) { + minBase |= (i3 << 8); + maxBase |= (i3 << 8); + currentMaxMask = 0x000000FF; + } + return (minBase, maxBase | (int)currentMaxMask); } #endregion From 744ec669461cc2d305ba14b1b83412588106a72c Mon Sep 17 00:00:00 2001 From: Kibnet Philosoff Date: Tue, 27 May 2025 12:04:32 +0300 Subject: [PATCH 5/6] ci: fix script --- .github/workflows/ci-tests.yml | 17 +++++++++-------- .github/workflows/manual-publish.yml | 10 +++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index bc82098..a618bfd 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -12,13 +12,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.5 # Consider updating if your project supports newer .NET versions - - name: Install dependencies + dotnet-version: 9.0.x + - name: Restore dependencies run: dotnet restore - - name: Run tests - run: dotnet test --configuration Release --no-restore - # Removed Pack and Push steps + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index cf09c8e..8e41d69 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -8,12 +8,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.5 - - name: Install dependencies + dotnet-version: 9.0.x + - name: Restore dependencies run: dotnet restore - name: Pack solution run: dotnet pack --configuration Release -o out --no-restore From 98ecc84937f3bb1351802b4497f8c82ea6f51536 Mon Sep 17 00:00:00 2001 From: Kibnet Philosoff Date: Tue, 27 May 2025 13:13:16 +0300 Subject: [PATCH 6/6] doc: Add summary for few members --- src/IntSet/IntSet.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/IntSet/IntSet.cs b/src/IntSet/IntSet.cs index a98a6ba..e228efc 100644 --- a/src/IntSet/IntSet.cs +++ b/src/IntSet/IntSet.cs @@ -46,8 +46,14 @@ public IntSet(bool isFastest, bool isFull) protected internal long _count; + /// + /// The number of elements contained in the set + /// public long LongCount => _count; + /// + /// The flag is a setting indicating that actions need to be performed faster, sacrificing memory release + /// public bool IsFastest { get => _isFastest; @@ -827,6 +833,12 @@ private IEnumerable ProcessBytesDirectional(Card rootNode, int i0, int i1, #region Public API + /// + /// Get the elements in a given range sorted from first to last + /// + /// First element of range + /// Last element of range + /// public IEnumerable GetElementsInRange(int firstElement, int lastElement) { int minValue = Math.Min(firstElement, lastElement);