using System; using System.Collections.Generic; namespace Amphora.UnitTests { public class FenwickTreeTests : UnitTestSharp.TestFixture { public struct XorOperations : IFenwickOperations { public uint Add(uint left, uint right) { return left ^ right; } public uint Subtract(uint left, uint right) { return left ^ right; } public bool IsAtMost(uint left, uint right) { return left <= right; } } public struct DoubleSum : IFenwickOperations { public double Add(double left, double right) { return left + right; } public double Subtract(double left, double right) { return left - right; } public bool IsAtMost(double left, double right) { return left <= right; } } public static List Pattern(int count) { var returnMe = new List(); for (int i = 0; i < count; ++i) { returnMe.Add((i * 7 + 3) % 11); } return returnMe; } public static List NegativePattern(int count) { var returnMe = new List(); for (int i = 0; i < count; ++i) { returnMe.Add((i * 5 + 2) % 9 - 4); } return returnMe; } public static FenwickTree TreeOf(List items) { var returnMe = new FenwickTree(); foreach (int item in items) { returnMe.Append(item); } return returnMe; } public static int NaiveFind(List items, int target, out int remainder) { int position = 0; remainder = target; while (position < items.Count && items[position] <= remainder) { remainder -= items[position]; ++position; } return position; } public static List Spliced(List items, int index, int removeCount, int[] inserted) { var returnMe = new List(items); returnMe.RemoveRange(index, removeCount); returnMe.InsertRange(index, inserted); return returnMe; } public static int[] Inserted(int count) { var returnMe = new int[count]; for (int i = 0; i < count; ++i) { returnMe[i] = (i * 3 + 1) % 7; } return returnMe; } /// /// Returns what first differs between the tree and the list it should hold, or an empty string. /// public static string FirstDifference(FenwickTree tree, List model, bool searchToo) { if (tree.Count != model.Count) { return $"count {tree.Count} instead of {model.Count}"; } int sum = 0; for (int i = 0; i <= model.Count; ++i) { if (tree.PrefixSum(i) != sum) { return $"prefix {i} was {tree.PrefixSum(i)} instead of {sum}"; } if (i == model.Count) { break; } if (tree.ValueAt(i) != model[i]) { return $"item {i} was {tree.ValueAt(i)} instead of {model[i]}"; } sum += model[i]; } if (tree.Total != sum) { return $"total {tree.Total} instead of {sum}"; } for (int target = -1; searchToo && target <= sum + 1; ++target) { int expected = NaiveFind(model, target, out int expectedRemainder); int found = tree.Find(target, out int remainder); if (found != expected || remainder != expectedRemainder) { return $"find {target} was {found} with {remainder} left, not {expected} with " + $"{expectedRemainder} left"; } } return ""; } public class CtorTests : UnitTestSharp.TestFixture { public void StartsEmpty() { var tree = new FenwickTree(); CheckEqual(0, tree.Count); CheckEqual(0, tree.Total); } public void ByDefault_ReservesNothing() { CheckEqual(0, new FenwickTree().Capacity); } public void ReservesTheCapacityAskedFor() { CheckEqual(100, new FenwickTree(100).Capacity); } public void NegativeCapacity_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new FenwickTree(-1); } public void CapacityPastTheMost_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new FenwickTree(FenwickTree.MaxCount + 1); } } public class EnsureCapacityTests : UnitTestSharp.TestFixture { public void GrowsToHoldTheCapacity() { var tree = new FenwickTree(); tree.EnsureCapacity(50); Check(tree.Capacity >= 50); } public void GrowsAtLeastTwofold() { var tree = new FenwickTree(40); tree.EnsureCapacity(41); Check(tree.Capacity >= 80); } public void ShortOfTheCapacityAlreadyHeld_ChangesNothing() { var tree = new FenwickTree(40); tree.EnsureCapacity(10); CheckEqual(40, tree.Capacity); } public void KeepsTheItems() { FenwickTree tree = TreeOf(Pattern(20)); tree.EnsureCapacity(500); CheckEqual("", FirstDifference(tree, Pattern(20), searchToo: true)); } public void Negative_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new FenwickTree().EnsureCapacity(-1); } public void PastTheMost_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new FenwickTree().EnsureCapacity(FenwickTree.MaxCount + 1); } } public class AppendTests : UnitTestSharp.TestFixture { public void AnyNumberOfItems_MatchesTheList() { for (int count = 0; count <= 140; ++count) { CheckEqual("", FirstDifference(TreeOf(Pattern(count)), Pattern(count), searchToo: count < 40)); } } public void NegativeItems_MatchTheList() { for (int count = 0; count <= 70; ++count) { CheckEqual("", FirstDifference(TreeOf(NegativePattern(count)), NegativePattern(count), searchToo: false)); } } public void GrowsPastItsCapacity() { var tree = new FenwickTree(1); for (int i = 0; i < 100; ++i) { tree.Append(i); } CheckEqual(100, tree.Count); CheckEqual(4950, tree.Total); Check(tree.Capacity >= 100); } public void AfterTruncating_IgnoresWhatWasThere() { FenwickTree tree = TreeOf(Pattern(8)); tree.TruncateTo(3); tree.Append(100); tree.Append(200); tree.Append(300); var model = Pattern(3); model.AddRange(new[] { 100, 200, 300 }); CheckEqual("", FirstDifference(tree, model, searchToo: true)); } public void ATotalThatOverflows_ThrowsAndChangesNothing() { FenwickTree tree = TreeOf(new List { int.MaxValue }); CheckThrow(typeof(OverflowException)); tree.Append(1); CheckEqual(1, tree.Count); CheckEqual(int.MaxValue, tree.Total); } } public class PrefixSumTests : UnitTestSharp.TestFixture { public void OfNoItems_IsZero() { CheckEqual(0, TreeOf(Pattern(5)).PrefixSum(0)); } public void OfEveryItem_IsTheTotal() { FenwickTree tree = TreeOf(Pattern(37)); CheckEqual(tree.Total, tree.PrefixSum(37)); } public void Negative_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).PrefixSum(-1); } public void PastTheCount_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).PrefixSum(6); } } public class RangeSumTests : UnitTestSharp.TestFixture { public void EveryRange_MatchesTheList() { var model = NegativePattern(21); FenwickTree tree = TreeOf(model); for (int from = 0; from <= 21; ++from) { int expected = 0; for (int to = from; to <= 21; ++to) { CheckEqual(expected, tree.RangeSum(from, to)); if (to < 21) { expected += model[to]; } } } } public void OfNothing_IsZero() { CheckEqual(0, TreeOf(Pattern(9)).RangeSum(4, 4)); } public void FromPastTheCount_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).RangeSum(6, 6); } public void FromBelowZero_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).RangeSum(-1, 2); } public void ToBeforeFrom_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).RangeSum(3, 2); } public void ToPastTheCount_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).RangeSum(2, 6); } } public class ValueAtTests : UnitTestSharp.TestFixture { public void EveryItem_MatchesTheList() { for (int count = 1; count <= 70; ++count) { FenwickTree tree = TreeOf(NegativePattern(count)); for (int i = 0; i < count; ++i) { CheckEqual(NegativePattern(count)[i], tree.ValueAt(i)); } } } public void Negative_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).ValueAt(-1); } public void AtTheCount_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).ValueAt(5); } } public class AddTests : UnitTestSharp.TestFixture { public void ToEveryItem_MatchesTheList() { for (int count = 1; count <= 40; ++count) { for (int index = 0; index < count; ++index) { var model = Pattern(count); FenwickTree tree = TreeOf(model); tree.Add(index, 13); model[index] += 13; CheckEqual("", FirstDifference(tree, model, searchToo: count < 12)); } } } public void ANegativeDelta_MatchesTheList() { var model = Pattern(33); FenwickTree tree = TreeOf(model); tree.Add(20, -3); model[20] -= 3; CheckEqual("", FirstDifference(tree, model, searchToo: true)); } public void Negative_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).Add(-1, 1); } public void AtTheCount_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).Add(5, 1); } public void ATotalThatOverflows_Throws() { FenwickTree tree = TreeOf(new List { int.MaxValue }); CheckThrow(typeof(OverflowException)); tree.Add(0, 1); } } public class SetTests : UnitTestSharp.TestFixture { public void ReplacesTheItem() { for (int index = 0; index < 20; ++index) { var model = Pattern(20); FenwickTree tree = TreeOf(model); tree.Set(index, 40); model[index] = 40; CheckEqual("", FirstDifference(tree, model, searchToo: true)); } } public void OutOfRange_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).Set(5, 1); } } public class TruncateToTests : UnitTestSharp.TestFixture { public void ToEveryCount_MatchesTheList() { for (int count = 0; count <= 40; ++count) { for (int newCount = 0; newCount <= count; ++newCount) { FenwickTree tree = TreeOf(Pattern(count)); tree.TruncateTo(newCount); CheckEqual("", FirstDifference(tree, Pattern(newCount), searchToo: true)); } } } public void ThenSplicingBigger_MatchesTheList() { FenwickTree tree = TreeOf(Pattern(40)); tree.TruncateTo(5); tree.Splice(5, 0, Inserted(30), 30); var model = Pattern(5); model.AddRange(Inserted(30)); CheckEqual("", FirstDifference(tree, model, searchToo: true)); } public void PastTheCount_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).TruncateTo(6); } public void BelowZero_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).TruncateTo(-1); } } public class ClearTests : UnitTestSharp.TestFixture { public void DropsEveryItem() { FenwickTree tree = TreeOf(Pattern(20)); tree.Clear(); CheckEqual("", FirstDifference(tree, new List(), searchToo: true)); } public void KeepsTheStorage() { FenwickTree tree = TreeOf(Pattern(20)); int capacity = tree.Capacity; tree.Clear(); CheckEqual(capacity, tree.Capacity); } public void ThenAppending_MatchesTheList() { FenwickTree tree = TreeOf(Pattern(20)); tree.Clear(); tree.Append(4); tree.Append(9); CheckEqual("", FirstDifference(tree, new List { 4, 9 }, searchToo: true)); } } public class FindTests : UnitTestSharp.TestFixture { public void AnyTarget_MatchesTheList() { for (int count = 0; count <= 45; ++count) { CheckEqual("", FirstDifference(TreeOf(Pattern(count)), Pattern(count), searchToo: true)); } } public void InNoItems_FindsNothingAndLeavesTheTarget() { int found = new FenwickTree().Find(7, out int remainder); CheckEqual(0, found); CheckEqual(7, remainder); } public void ATargetPastTheTotal_FindsEveryItem() { FenwickTree tree = TreeOf(Pattern(20)); int found = tree.Find(tree.Total + 100, out int remainder); CheckEqual(20, found); CheckEqual(100, remainder); } public void ATargetEqualToAPrefix_IncludesItsLastItem() { FenwickTree tree = TreeOf(new List { 2, 3, 4 }); CheckEqual(2, tree.Find(5)); } public void ZeroItems_AreIncluded() { FenwickTree tree = TreeOf(new List { 2, 0, 0, 3 }); CheckEqual(3, tree.Find(2)); } public void ANegativeTarget_FindsNothing() { int found = TreeOf(Pattern(9)).Find(-5, out int remainder); CheckEqual(0, found); CheckEqual(-5, remainder); } public void WithoutTheRemainder_FindsTheSameCount() { FenwickTree tree = TreeOf(Pattern(30)); for (int target = 0; target < 60; ++target) { CheckEqual(tree.Find(target, out _), tree.Find(target)); } } } public class SpliceTests : UnitTestSharp.TestFixture { public void EveryWayOnSmallTrees_MatchesTheList() { for (int count = 0; count <= 17; ++count) { for (int index = 0; index <= count; ++index) { for (int removeCount = 0; removeCount <= count - index; ++removeCount) { for (int insertedCount = 0; insertedCount <= 3; ++insertedCount) { FenwickTree tree = TreeOf(Pattern(count)); int[] inserted = Inserted(insertedCount); tree.Splice(index, removeCount, inserted, insertedCount); CheckEqual("", FirstDifference(tree, Spliced(Pattern(count), index, removeCount, inserted), searchToo: true)); } } } } } public void AroundPowersOfTwo_MatchesTheList() { foreach (int count in new[] { 31, 32, 33, 64, 65, 100 }) { foreach (int index in new[] { 0, 1, count / 2, count - 1, count }) { foreach (int removeCount in new[] { 0, Math.Min(1, count - index), count - index }) { foreach (int insertedCount in new[] { 0, 1, 5, 40 }) { FenwickTree tree = TreeOf(Pattern(count)); int[] inserted = Inserted(insertedCount); tree.Splice(index, removeCount, inserted, insertedCount); CheckEqual("", FirstDifference(tree, Spliced(Pattern(count), index, removeCount, inserted), searchToo: false)); } } } } } public void NegativeItems_MatchTheList() { var model = NegativePattern(50); FenwickTree tree = TreeOf(model); tree.Splice(10, 20, new[] { -9, 8, -7 }, 3); CheckEqual("", FirstDifference(tree, Spliced(model, 10, 20, new[] { -9, 8, -7 }), searchToo: false)); } public void OnlyTheFirstItemsGiven_AreInserted() { FenwickTree tree = TreeOf(Pattern(10)); tree.Splice(4, 0, new[] { 1, 2, 3, 4, 5 }, 2); CheckEqual("", FirstDifference(tree, Spliced(Pattern(10), 4, 0, new[] { 1, 2 }), searchToo: true)); } public void GrowsTheStorageWhenItHasTo() { var tree = new FenwickTree(2); tree.Append(5); tree.Append(6); tree.Splice(1, 0, Inserted(200), 200); var model = new List { 5, 6 }; CheckEqual("", FirstDifference(tree, Spliced(model, 1, 0, Inserted(200)), searchToo: true)); } public void RemovingOrInsertingNothing_ChangesNothing() { FenwickTree tree = TreeOf(Pattern(20)); tree.Splice(7, 0, null, 0); CheckEqual("", FirstDifference(tree, Pattern(20), searchToo: true)); } public void ATotalThatOverflows_ThrowsAndChangesNothing() { var model = new List { 1, 2, int.MaxValue - 3 }; FenwickTree tree = TreeOf(model); CheckThrow(typeof(OverflowException)); tree.Splice(0, 1, new[] { 10 }, 1); CheckEqual("", FirstDifference(tree, model, searchToo: true)); } public void IndexPastTheCount_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).Splice(6, 0, null, 0); } public void IndexBelowZero_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).Splice(-1, 0, null, 0); } public void RemovingPastTheEnd_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).Splice(3, 3, null, 0); } public void RemovingANegativeCount_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).Splice(3, -1, null, 0); } public void InsertingANegativeCount_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).Splice(3, 0, new[] { 1 }, -1); } public void InsertingMoreThanWasGiven_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).Splice(3, 0, new[] { 1, 2 }, 3); } public void InsertingPastTheMostItems_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).Splice(3, 0, new[] { 1 }, FenwickTree.MaxCount); } public void InsertingFromNothing_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); TreeOf(Pattern(5)).Splice(3, 0, null, 1); } } public class ManyItemsTests : UnitTestSharp.TestFixture { public void AHundredThousandItems_AnswerLikeTheList() { var tree = new LongFenwickTree(); long sum = 0; var sums = new long[100001]; for (int i = 0; i < 100000; ++i) { tree.Append(i % 7); sum += i % 7; sums[i + 1] = sum; } CheckEqual(sum, tree.Total); foreach (int length in new[] { 0, 1, 2, 1023, 1024, 1025, 65535, 65536, 65537, 99999, 100000 }) { CheckEqual(sums[length], tree.PrefixSum(length)); } foreach (long target in new[] { 0L, 1L, sums[1000], sums[65536], sum, sum + 5 }) { int expected = 0; while (expected < 100000 && sums[expected + 1] <= target) { ++expected; } CheckEqual(expected, tree.Find(target)); } } } public class LongFenwickTreeTests : UnitTestSharp.TestFixture { public void HoldsTotalsPastAnInt() { var tree = new LongFenwickTree(); tree.Append(int.MaxValue); tree.Append(int.MaxValue); tree.Append(5); CheckEqual(2L * int.MaxValue + 5, tree.Total); CheckEqual(2L * int.MaxValue, tree.PrefixSum(2)); CheckEqual(int.MaxValue, tree.ValueAt(1)); } public void FindsByLongTargets() { var tree = new LongFenwickTree(); tree.Append(3000000000L); tree.Append(3000000000L); int found = tree.Find(4000000000L, out long remainder); CheckEqual(1, found); CheckEqual(1000000000L, remainder); } public void ATotalThatOverflows_Throws() { var tree = new LongFenwickTree(); tree.Append(long.MaxValue); CheckThrow(typeof(OverflowException)); tree.Append(1); } } public class OperationsTests : UnitTestSharp.TestFixture { public void XorSums_AreCombinedByTheOperations() { var tree = new FenwickTree(); uint[] items = { 5, 9, 12, 3, 3, 40, 7, 1, 0, 255, 8 }; foreach (uint item in items) { tree.Append(item); } uint running = 0; for (int i = 0; i <= items.Length; ++i) { CheckEqual(running, tree.PrefixSum(i)); if (i < items.Length) { CheckEqual(items[i], tree.ValueAt(i)); running ^= items[i]; } } CheckEqual(running, tree.Total); } public void XorSums_SurviveASplice() { var tree = new FenwickTree(); for (uint item = 1; item <= 20; ++item) { tree.Append(item * 37); } tree.Splice(5, 6, new uint[] { 1000, 2000 }, 2); uint running = 0; var model = new List(); for (uint item = 1; item <= 20; ++item) { model.Add(item * 37); } model.RemoveRange(5, 6); model.InsertRange(5, new uint[] { 1000, 2000 }); for (int i = 0; i < model.Count; ++i) { running ^= model[i]; CheckEqual(running, tree.PrefixSum(i + 1)); } } public void DoubleSums_FindByDoubleTargets() { var tree = new FenwickTree(); foreach (double item in new[] { 0.5, 1.5, 2.0, 0.25 }) { tree.Append(item); } int found = tree.Find(2.25, out double remainder); CheckEqual(2, found); CheckEqual(0.25, remainder); } } public class Int32SumTests : UnitTestSharp.TestFixture { public void Adds() { CheckEqual(7, new Int32Sum().Add(3, 4)); } public void AddingPastTheTop_Throws() { CheckThrow(typeof(OverflowException)); new Int32Sum().Add(int.MaxValue, 1); } public void Subtracts() { CheckEqual(-1, new Int32Sum().Subtract(3, 4)); } public void SubtractingPastTheBottom_Throws() { CheckThrow(typeof(OverflowException)); new Int32Sum().Subtract(int.MinValue, 1); } public void ComparesWithLessThanOrEqual() { CheckTrue(new Int32Sum().IsAtMost(3, 4)); CheckTrue(new Int32Sum().IsAtMost(4, 4)); CheckFalse(new Int32Sum().IsAtMost(5, 4)); } } public class Int64SumTests : UnitTestSharp.TestFixture { public void Adds() { CheckEqual(7L, new Int64Sum().Add(3, 4)); } public void AddingPastTheTop_Throws() { CheckThrow(typeof(OverflowException)); new Int64Sum().Add(long.MaxValue, 1); } public void Subtracts() { CheckEqual(-1L, new Int64Sum().Subtract(3, 4)); } public void SubtractingPastTheBottom_Throws() { CheckThrow(typeof(OverflowException)); new Int64Sum().Subtract(long.MinValue, 1); } public void ComparesWithLessThanOrEqual() { CheckTrue(new Int64Sum().IsAtMost(3, 4)); CheckTrue(new Int64Sum().IsAtMost(4, 4)); CheckFalse(new Int64Sum().IsAtMost(5, 4)); } } } }